【Roblox】UGC機能の実装(1)~ステージ作成からデータストアへの保存~
はじめに
今回は弊社からリリースした『UGC Romance Obby Fall Game』で実装したUGC機能について紹介します。
このゲームで実装したUGC機能はユーザーがステージを作成し、ほかユーザーが遊べるようにするという機能です。この記事ではステージの作成方法から作成ステージ情報をDataStoreに保存する方法について紹介します。
Robloxバージョン 0.658.0.6580461
ステージ作成
ステージ作成ではオブジェクトの配置に加えて、プレイ中の動作命令(移動、回転、拡縮など)を追加できるようになっています。
生成するオブジェクトについて
生成するオブジェクトの子オブジェクトにDragDetectorとModuleScriptを追加しています。
DragDetectorは生成したオブジェクトの位置を調整するのに使用し、ModuleScriptはオブジェクトに設定した命令情報を保持するのに使用します。
命令情報の保持の方法には他にもAttributeを使用したり、マネージャーなどを使用して一括管理したりといろいろな手法がありますが、今回はオブジェクトを破棄したときのデータの管理が不要になる点とデータの取得をrequireをするだけで一括で行える点でオブジェクト毎にデータ保持用のModuleScriptを持たせることにしました。
DragDetectorについては以前にこちらの記事で紹介しているので、読んでいない方はそちらも読んでいただけるとより分かりやすいかと思います。
DragDetectorの記事
オブジェクトの配置
オブジェクトの配置にはDragDetectorを使用します。
基本的にオブジェクトを動かしたりするのはDragDetectorのデフォルト機能を使用しているので、専用にスクリプトを組まなくてもステージの作成自体は行えるようになっています。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--プレハブフォルダ
local prefabFolder = ReplicatedStorage:WaitForChild("Prefab")
--配置できるオブジェクトを格納しているフォルダ
local objectFolder = prefabFolder:WaitForChild("Object")
--配置したオブジェクトに追加するDragDetector
local objectDetector = prefabFolder:WaitForChild("ObjectDetector")
--配置したオブジェクトに追加するModuleScript
local directiveObject = prefabFolder:WaitForChild("DirectiveObject")
--生成したオブジェクトの親になるModelインスタンス
local BaseModel = workspace:WaitForChild("BaseModel")
--オブジェクトのドラッグ中フラグ
local isDrag = false
--選択中のオブジェクト
local selectedObject = nil
local CreateStageManager = {}
--オブジェクトの生成処理
function CreateStageManager:CreateObject(objectId:number)
--オブジェクトを生成
local object = prefabFolder:WaitForChild("Object_"..objectId):Clone()
object.Parent = BaseModel
--オブジェクト識別用のIDをAttributeに追加
object:SetAttribute("ID", objectId)
--オブジェクトにDragDetectorを追加
local detector = objectDetector:Clone()
detector.Parent = object
local DragStartPos --ドラッグ開始位置保持用変数
detector.DragStart:Connect(function()
DragStartPos = object.Position
isDrag = true
end)
detector.DragEnd:Connect(function()
--ドラッグしていないとみなしてオブジェクトを選択した判定とする。
if (object.Position - DragStartPos).Magnitude < 1 then
selectedObject = object
end
isDrag = false
end)
--オブジェクトにModuleScriptを追加
local module = directiveObject:Clone()
module.Parent = object
end
return CreateStageManager
上記のコードではドラッグ操作中のフラグとオブジェクトの選択処理を追加しました。
UGC機能を作成するときにはUndo,Redoといった機能を実装することも必要になってくることがあると思います。そういったときにはDragEnd内に現在のステージ情報を取得する処理を追加して、tableに持たせておくことで簡単に実装できます。
オブジェクトに命令を追加
オブジェクトに追加する命令の保持に関してですが、移動命令を例に解説します。
前述したようにこのモジュールはデータを保持するだけのために使用しているので、メソッドなどは一切含めず、外部から自由に参照できる値のみを保持するように作っています。
local DirectiveObject = {}
DirectiveObject.Move =
{
IsActive = false,
TargetPos = {0, 0, 0},
Speed = 0
}
--以下回転と拡縮も追加
return DirectiveObject
このように移動命令を実行するのに必要なデータだけを持たせておくことで、requireをして取得したtableをそのままステージ情報としてDataStoreに保存することができるようになっています。
このときにDataStoreで保存できるデータ型になっていないと別途変換処理を追加する必要が出てきます。DataStoreに保存できるデータ型と変換処理については次のトピックで紹介します。
DataStoreでのステージ情報の管理
RobloxではHttpServiceなどを利用してステージ情報などを外部データベースに保存することもできますが、今回はステージ情報をDataStoreに保存していきます。DataStoreに保存できるデータ型には制限があるので、データを保存できる形に修正する必要があります。
DataStoreで保存できるデータ型
- number
- string
- boolean
- table
- nil
DataStoreでは上記の5種類のデータ型のみが保存できます。
ステージ情報にはオブジェクトの配置位置を含める必要がありますが、Robloxで使用されているCFrameはそのままだとDataStoreに保存できないので、保存できる形に変換する必要があります。
CFrameをtableに変換する
CFrameにはGetComponent
というメソッドが存在します。このメソッドはオブジェクトのX座標、Y座標、Z座標、3x3回転行列(R00,R01,R02,R10,R11,R12,R20,R21,R22)を返します。
例 Position(0,10,20),Rotation(0,90,0)の場合、GetComponent
は
0 10 20 0.49995946884155273 0 0.8660488128662109 0 1 0 -0.8660488128662109 0 0.49995946884155273
をタプル型として返します。
このメソッドの戻り値を{}で囲んでtable型に変換することで、DataStoreで保存できるようになります。
配置されているオブジェクトの情報を取得し保存する
ステージ情報をデータストアに保存します。
オブジェクトはすべてBaseModel下に配置されているので、BaseModelの子オブジェクトを参照するだけでまとめてステージデータを取得することができます。
local DataStoreService = game:GetService("DataStoreService")
local StageDataStore = DataStoreService:GetDataStore("StageDataStore")
--生成したオブジェクトの親になっているModelインスタンス
local BaseModel = workspace:WaitForChild("BaseModel")
local StageDataManager = {}
function StageDataManager:SaveData(player)
local stageData = {}
local objects = BaseModel:GetChildren()
for i,object in ipairs(objects) do
local directiveObject = require(object:WaitForChild("DirectiveObject"))
local objectData =
{
objectId = object:GetAttribute("ID") ,
cframe = {object.CFrame:GetComponent()},
directive = table.clone(directiveObject),
}
table.insert(stageData, objectData)
end
local key = "Stage_"..player.UserId.."_0001"
StageDataStore:SetAsync(key, stageData)
end
return StageDataManager
これでステージデータをDataStoreに保存することができました。
keyにUserIdを含めることでListKeysAsync
のprefixにUserIdを設定するだけでプレイヤーが作成したステージのkeyをまとめて取得することが可能になっています。
まとめ
- オブジェクトを配置するにはDragDetectorを使用すると便利
- オブジェクトごとにデータを持たせるにはModuleScriptに必要なデータだけを持たせることで参照とオブジェクト破棄時の管理が楽になる
- DataStoreに保存できるデータ型に注意し、必要があれば変換する
- StageDataを保存するKeyにはフィルター要素(UserId)を含めておくと管理が楽になる
今回は弊社で制作したゲームで実装したUGC機能のステージを作成して、DataStoreに保存する方法について紹介しました。
次回の記事では今回保存したStageDataを呼び出して実際にステージを生成する方法について紹介したいと思います。
最後までお読みいただき、ありがとうございました!
参考

当社ではRobloxを活用したゲームの開発、 また企業の商品やサービスの認知度拡大に寄与する3Dワールドの制作など、 Robloxにおける様々な活用支援を行っております。 Robloxのコンテンツ開発をご検討されている企業様は、お気軽にご相談ください。 landho.co.jp/
Discussion