「何をどこに置くか」をきれいに決めておくと、人形の増殖・編集・保存が一気に楽になります。
ここでは「複数の人形(リグ+パーツ+メッシュ)を生成・編集(色付け)して、DataStoreに保存する」ためのスクリプト配置中心の設計を、一つの案として組み立ててみます。
全体構成のイメージ
まず、機能ごとに役割をはっきり分けます。
- サーバー側
- 生成・管理:人形リグの生成、パーツ・メッシュの差し替え
- 保存・読み込み:DataStore による保存/ロード
- クライアント側
- UI操作:色変更、アセット選択などの入力
- サーバーへ要求:RemoteEvent / RemoteFunction 経由でリクエスト
フォルダ/サービス構造とスクリプト配置
1. ReplicatedStorage(テンプレと通信)
ReplicatedStorage/RigTemplates- 中身:
BaseDoll(R15/R6リグをベースにしたモデル)- メッシュ差し替え用のパーツセットやアクセサリなど
- 中身:
ReplicatedStorage/RemotesRemotes/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.Dollsやworkspace.PlayerDolls[player.Name]などにまとめる
ここから先の深掘り候補
必要なら、次は例えば:
- 「メッシュアセットの選択」をどう表現・保存するか(AssetId / 名前 / Tag)
- 人形ごとの専用フォルダ構造(Rig+着せ替えパーツ+メタ情報)
- 「プレイヤーのアバター」と「人形」をどう区別するか(layering, ownership)
どのあたりをもう一段、構造レベルで詰めたいですか?
「人形=舞台用ドール」として、プレイヤーアバターとの関係も含めて設計しても面白そうです。