Roblox Studioでマスタデータを実装してみた件

# はじめに
この記事は「Roblox Studioでデータをどう管理したらよいか分からず困っている」人向けに
**「Roblox Studio内の機能のみを用いて、疑似的なマスタデータを実装する方法」** を解説します。
今回は以下の機能を使います。

<dl>
  <dt>ServerStorage</dt>
  <dd>データを保管するサービスで、サーバーのスクリプトからのみアクセス可能。
クライアントからはこの場所に格納されたデータに直接アクセスできない。
クライアントから見えてはいけないデータや、ゲーム開始前にロードが必要ないデータを格納するのに用いられる。</dd>
  <dt>ServerScriptService</dt>
  <dd>サーバースクリプトで実行される処理を提供するサービス。
これらの処理はサーバでのみ実行される為、クライアントからはこの場所に格納された関数などには直接アクセスできない。
スクリプトとモジュールスクリプトを格納可能。</dd>
</dl>

### なぜマスタデータを実装したか

実は、
**Roblox Studioにはデータベースのようにデータを一か所に整理できる仕組みがない**です。

なのでマスタデータを使わずに実装すると...
* データが置かれている場所が煩雑になってしまう。
* データを変更するときに変更箇所が散らばっていて影響範囲が広くなる。


そのため **「Roblox Studio内の機能のみを用いて、疑似的なマスタデータを実装する」** 
という結論に至り、今回紹介している実装を行いました。

:::note warn
補足: 
 Roblox Studioの外部にデータベースを構築してマスタデータを管理する方法もありますが、実装と管理にコストがかかるため今回は見送りました。
:::


↓煩雑になる実装の例

例①: オブジェクト毎に個別に属性が設定されている場合。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3737695/d1efef65-2fe4-2260-34fd-a6c5c04663e9.png)

例②: オブジェクト毎にスクリプトを作ってそこで値を管理している場合。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3737695/ac17c86b-4ae3-4034-d6f8-e3a5f697ffc3.png)

これらの設定値を一つずつ確認して変更すると無駄に手間がかかってしまいます。
マスタデータを実装しておけば上記２つのような実装をした場合より確認・変更の手間が体感6割程度削減できます。

まとめると、マスタデータを実装することで
* **データベースに関わるコストを削減し、**
* **マスタデータを一括管理でき、**
* **確認・変更にかかる工数を削減**

する事が出来ます。



# 環境
この記事ではRoblox Studioの標準機能のみを使ってマスタデータを実装します。
そのためマスタデータの実装に必要な環境は以下の通りです。
-  Roblox Studio
※バージョンは最新でOK。

# 実装手順
以下のようなディレクトリ構造でマスタデータの定義、呼び出し処理を実装します。
今回は例としてMasterItemという名前で作成します。

```:フォルダ構造
ServerScriptService
└─ Master
　└─ MasterItem.lua　←　呼び出し処理
ServerStorage
├─ Master
│ └─ MasterItem.lua　←　マスタデータ
└─ MasterStruct
　└─ MasterItem.lua　←　マスタデータの構造体
```

## ServerStorageの実装
### 構造体を定義する
luaにはStruct型が存在しないのでTable型で代用します。

ServerStorage配下にMasterStructという名前でフォルダを作り、
その配下に任意のマスタデータ名でModuleScriptを作成します。

:::note warn
必ずModuleScriptを作成して下さい。
ScriptとModuleScriptは同じluaファイルですが、RobloxではScriptは外部から呼び出せません。
:::

```:フォルダ構造
ServerStorage
└─ MasterStruct
　└─ MasterItem.lua
```

作成したMasterItem.luaに以下のコードを記述します。

```lua:MasterItem.lua
-- マスタデータを定義する擬似的な構造体
local Struct = {
	new = function(id, value1, value2)
		local self = {}
		-- ID
		self.Id = id
		-- 値1
		self.Value1 = value1
		-- 値2
		self.Value2 = value2
		return self
	end
}

return Struct
```
Structという構造体のようなTable型を宣言し、中にNewという名前の関数を保持しています。
これはインスタンス作成関数になっており、Newを呼ぶ度に新しいテーブルが作成されます。

### マスタデータを作成する
ServerStorage配下にMasterという名前でフォルダを作り、
その配下に任意の名前でModuleScriptを作成します。
今回は構造体と同じMasterItemという名前で作成します。

```:フォルダ構造
ServerStorage
└─ Master
　└─ MasterItem.lua
```

作成したMasterItem.luaに以下のコードを記述します。

```lua:MasterItem.lua
-- マスタデータ構造体を呼び出す
local master = require(game.ServerStorage:WaitForChild("MasterItem"))

-- マスタデータをセット
local masterTable = {
    -- 値をセット
	master.new(1, 100, 10),
    master.new(2, 110, 20),
}

return masterTable
```

## ServerScriptServiceの実装
### 呼び出し処理を実装する

ServerScriptService配下にMasterという名前でフォルダを作り、
その配下に任意の名前でModuleScriptを作成します。
今回は構造体、マスタデータと同じMasterItemという名前で作成します。

```:フォルダ構造
ServerScriptService
└─ Master
　└─ MasterItem.lua
```

例として引数にIdを渡し一致するデータを取得する関数を追加します。

```lua:MasterItem.lua
local MasterItemModule = {}
local masterTable = require(game.ServerStorage:WaitForChild("Master"):WaitForChild("MasterItem"))

-- idが一致するデータを取得
function MasterItemModule.GetMasterItemById(Id)
	for _, data in ipairs(masterTable) do
		if data.Id == Id then
			return data
		end
	end
	return nil  -- 見つからなかった場合はnilを返す
end

return MasterItemModule
```

### 呼び出し処理の使用例
実際にServerScriptServiceからServerStorageのマスタを取得する処理を書いてみます。

今回は仮にReplicatedStorage内のToolオブジェクト配下のItemというScriptから呼び出すと仮定します。

```:フォルダ構造
ReplicatedStorage
└─ Item ← Toolオブジェクト
　└─ Item.lua　← Script
```


```lua:Item.lua
-- モジュールの取得
local MasterItemModule = require(ServerScriptService:WaitForChild("Master"):WaitForChild("MasterItem"))

-- 取得する関数
local function item(itemId)
	local Item = MasterItemModule.GetMasterItemById(itemId)

	print("Item", Item)
end
```

上記の引数に1を渡した場合以下のようになります。

```:結果
// itemId=1の場合
 item  ▼  {
     ["Id"] = 1,
     ["Value1"] = 100,
     ["Value2"] = 10
     }  -  サーバー - Item:8
```

このようにModuleScriptを経由してServerScriptService内のScriptからServerStorage内のマスタデータを取得する事が出来ました。

# おわりに
今回 **「Roblox Studio内の機能のみを用いて、疑似的なマスタデータを実装する方法」** を解説しました。
また今回コーディング例としてReplicatedStorage内から呼び出す例を書きましたが、ServerScriptService内から呼び出すことも勿論可能です。
より詳しくはModuleScriptについての[公式リファレンス](https://create.roblox.com/docs/ja-jp/reference/engine/classes/ModuleScript)を参照してください。

# リンク集
-  [Roblox公式リファレンス](https://create.roblox.com/docs/ja-jp/reference/engine)
-  [LuaのTable型について](http://marupeke296.com/LUA_No4_Table.html)
-  [モジュールスクリプトについて](https://roblox-jp.com/programming/e24/)

* * *
:::note info
株式会社サムライ・ソフトでは一緒に働く仲間を募集しています！
もし興味をお持ち頂ける方は[<INS>こちら</INS>](https://www.samurai-soft.com/#recruit)まで！
:::