「何をどこに置くか」をきれいに決めておくと、人形の増殖・編集・保存が一気に楽になります。
ここでは「複数の人形(リグ+パーツ+メッシュ)を生成・編集(色付け)して、DataStoreに保存する」ためのスクリプト配置中心の設計を、一つの案として組み立ててみます。


全体構成のイメージ

まず、機能ごとに役割をはっきり分けます。

  • サーバー側
    • 生成・管理:人形リグの生成、パーツ・メッシュの差し替え
    • 保存・読み込み:DataStore による保存/ロード
  • クライアント側
    • UI操作:色変更、アセット選択などの入力
    • サーバーへ要求:RemoteEvent / RemoteFunction 経由でリクエスト

フォルダ/サービス構造とスクリプト配置

1. ReplicatedStorage(テンプレと通信)

  • ReplicatedStorage/RigTemplates
    • 中身:
      • BaseDoll(R15/R6リグをベースにしたモデル)
      • メッシュ差し替え用のパーツセットやアクセサリなど
  • ReplicatedStorage/Remotes
    • Remotes/RequestCreateDoll(RemoteEvent:新規人形生成リクエスト)
    • Remotes/RequestUpdateColor(RemoteEvent:色変更リクエスト)
    • Remotes/RequestSaveDoll(RemoteEvent:保存リクエスト)
    • Remotes/RequestLoadDolls(RemoteFunction:ロード要求)

ここには「クライアントとサーバーが共通で見るもの」だけ置く。


2. ServerScriptService(中枢ロジック)

2-1. DataStore & Doll管理メインスクリプト

  • 場所: ServerScriptService/DollController.server.lua
  • 責務:
    • RemoteEvents 受信
    • 人形の生成/編集実行
    • DataStore への保存/ロード
-- ServerScriptService/DollController.server.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")

local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local RigTemplates = ReplicatedStorage:WaitForChild("RigTemplates")

local createEvent = Remotes:WaitForChild("RequestCreateDoll")
local updateColorEvent = Remotes:WaitForChild("RequestUpdateColor")
local saveEvent = Remotes:WaitForChild("RequestSaveDoll")
local loadFunction = Remotes:WaitForChild("RequestLoadDolls")

local DollStore = DataStoreService:GetDataStore("PlayerDolls_v1")

local Players = game:GetService("Players")

-- プレイヤーごとの人形インスタンスを管理
local playerDolls = {}  -- [player] = { [dollId] = model }

local function createDollForPlayer(player, dollConfig)
    -- dollConfig = { TemplateName = "BaseDoll", Color = Color3.new(...), ... }
    local templateName = dollConfig.TemplateName or "BaseDoll"
    local template = RigTemplates:FindFirstChild(templateName)
    if not template then return end

    local newDoll = template:Clone()
    newDoll.Name = "Doll_" .. player.UserId .. "_" .. tick()
    newDoll.Parent = workspace  -- または専用フォルダ workspace.Dolls 等

    -- 初期色設定など
    if dollConfig.Color then
        for _, part in ipairs(newDoll:GetDescendants()) do
            if part:IsA("BasePart") then
                part.Color = dollConfig.Color
            end
        end
    end

    -- メモリ上の管理
    playerDolls[player] = playerDolls[player] or {}
    local dollId = newDoll.Name
    playerDolls[player][dollId] = newDoll

    return newDoll, dollId
end

-- 新規人形生成リクエスト
createEvent.OnServerEvent:Connect(function(player, dollConfig)
    -- dollConfigのバリデーションは本来きっちりやる
    createDollForPlayer(player, dollConfig)
end)

-- 色変更リクエスト
updateColorEvent.OnServerEvent:Connect(function(player, dollId, newColor)
    local dolls = playerDolls[player]
    if not dolls then return end
    local doll = dolls[dollId]
    if not doll then return end

    for _, part in ipairs(doll:GetDescendants()) do
        if part:IsA("BasePart") then
            part.Color = newColor
        end
    end
end)

-- 保存処理
local function serializeDollsForPlayer(player)
    local dolls = playerDolls[player]
    if not dolls then return nil end

    local data = {}
    for dollId, model in pairs(dolls) do
        -- 単純化のため「一色だけ保存」とする
        local color
        for _, part in ipairs(model:GetDescendants()) do
            if part:IsA("BasePart") then
                color = part.Color
                break
            end
        end
        table.insert(data, {
            DollId = dollId,
            TemplateName = "BaseDoll",  -- 実際は属性やタグ等から取得
            Color = color and {color.R, color.G, color.B},
        })
    end
    return data
end

saveEvent.OnServerEvent:Connect(function(player)
    local data = serializeDollsForPlayer(player)
    if not data then return end
    local success, err = pcall(function()
        DollStore:SetAsync(player.UserId, data)
    end)
    if not success then
        warn("Failed to save dolls for", player.Name, err)
    end
end)

-- ロード処理
local function loadDollsForPlayer(player)
    local success, data = pcall(function()
        return DollStore:GetAsync(player.UserId)
    end)
    if not success or not data then return end

    playerDolls[player] = {}
    for _, dollData in ipairs(data) do
        local color = dollData.Color and Color3.new(dollData.Color[1], dollData.Color[2], dollData.Color[3])
        local dollConfig = {
            TemplateName = dollData.TemplateName,
            Color = color,
        }
        local newDoll, dollId = createDollForPlayer(player, dollConfig)
        -- dollIdを固定再利用したいならNameを上書きするなど調整
    end
end

loadFunction.OnServerInvoke = function(player)
    loadDollsForPlayer(player)
    return true
end

Players.PlayerRemoving:Connect(function(player)
    -- 退出時にセーブする等の運用も可能
    local data = serializeDollsForPlayer(player)
    if not data then return end
    local success, err = pcall(function()
        DollStore:SetAsync(player.UserId, data)
    end)
    if not success then
        warn("Failed to auto-save dolls for", player.Name, err)
    end
    playerDolls[player] = nil
end)

3. StarterPlayerScripts / StarterGui(UIとクライアント側)

3-1. UI 用 LocalScript

  • 場所: StarterGui/DollUI/ColorPicker.local.lua など
  • 責務:
    • プレイヤーのUI操作をキャッチ
    • サーバーの RemoteEvent にリクエスト送信
-- 例: StarterGui/DollUI/ColorPicker.local.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local player = Players.LocalPlayer

local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local updateColorEvent = Remotes:WaitForChild("RequestUpdateColor")
local createEvent = Remotes:WaitForChild("RequestCreateDoll")
local saveEvent = Remotes:WaitForChild("RequestSaveDoll")
local loadFunction = Remotes:WaitForChild("RequestLoadDolls")

-- UIパーツは適宜取得
local screenGui = script.Parent
local createButton = screenGui:WaitForChild("CreateDollButton")
local colorPicker = screenGui:WaitForChild("ColorPicker")
local saveButton = screenGui:WaitForChild("SaveButton")
local loadButton = screenGui:WaitForChild("LoadButton")

local selectedDollId = nil  -- 選択中の人形ID(選択UIと連動させる)

createButton.MouseButton1Click:Connect(function()
    local chosenColor = Color3.fromRGB(255, 200, 200)  -- 仮
    local config = {
        TemplateName = "BaseDoll",
        Color = chosenColor,
    }
    createEvent:FireServer(config)
end)

colorPicker.Changed:Connect(function(property)
    if property ~= "Color" then return end
    if not selectedDollId then return end
    local color = colorPicker.Color
    updateColorEvent:FireServer(selectedDollId, color)
end)

saveButton.MouseButton1Click:Connect(function()
    saveEvent:FireServer()
end)

loadButton.MouseButton1Click:Connect(function()
    loadFunction:InvokeServer()
end)

「スクリプトの配置」に関するポイント整理

  • ServerScriptService
    • ゲームロジックの中枢(人形生成・編集・保存)
    • DataStoreへのアクセス
    • RemoteEvent / RemoteFunction のサーバーハンドラ
  • ReplicatedStorage
    • クライアントとサーバーの共有リソース
      テンプレートリグ、メッシュ、RemoteEvent/RemoteFunction
  • StarterGui / StarterPlayerScripts
    • プレイヤー操作とUI
    • Remoteのクライアント側呼び出しだけを書く
  • Workspace / 専用フォルダ
    • 実体としての人形モデル
    • 可能なら workspace.Dollsworkspace.PlayerDolls[player.Name] などにまとめる

ここから先の深掘り候補

必要なら、次は例えば:

  • 「メッシュアセットの選択」をどう表現・保存するか(AssetId / 名前 / Tag)
  • 人形ごとの専用フォルダ構造(Rig+着せ替えパーツ+メタ情報)
  • 「プレイヤーのアバター」と「人形」をどう区別するか(layering, ownership)

どのあたりをもう一段、構造レベルで詰めたいですか?
「人形=舞台用ドール」として、プレイヤーアバターとの関係も含めて設計しても面白そうです。