いくつかのスクリプトを作成すると、すぐにそれらの間でコードを再利用したくなります。場所に応じて、ModuleScriptsを使うことで、クライアントとサーバーの境界の異なる側、または同じ側でスクリプト間でコードを再利用できます。
モジュールスクリプトはスクリプトを置く場所であればどこにでも置けますが、ReplicatedStorageは人気のある場所です。ここにモジュールスクリプトを保存することで、サーバーとクライアント間でコードを再利用できます。
モジュールスクリプトの構造
Roblox Studioで、ReplicatedStorageにモジュールスクリプトを追加し、それをPickupManagerに名前を変更します。各ModuleScriptは以下のコードで始まります:
local module = {}return module
このコードは空のLuau テーブルを作成し、モジュールスクリプトを必要とするスクリプトにそれを返します。
戻り値はnil以外の任意のデータ型で構いませんが、ほとんどのモジュールスクリプトは関数、テーブル、または関数のテーブルを返します。モジュールスクリプトは当然ですが、他のモジュールスクリプトを要求するなどの任意のコードを実行して戻り値を生成できます。
以下の例は、単一の関数getPickupBonusを持つテーブルを返します。これを新しいモジュールスクリプトに貼り付けます:
-- ReplicatedStorageのModuleScript
local PickupManager = {}
local defaultMultiplier = 1.25
local rarityMultipliers = {
common = 10,
uncommon = 20,
rare = 50,
legendary = 100
}
-- PickupManagerテーブルにgetPickupBonus関数を追加
PickupManager.getPickupBonus = function(rarity)
local bonus = rarityMultipliers[rarity] * defaultMultiplier
return bonus
end
return PickupManager
関数をテーブルに追加することは厳密には必要ありませんが、単に関数自体を返すこともできます。しかし、これは優れたパターンです;他のスクリプトから関数を呼び出す際に理解しやすい構文を提供し、時間が経つにつれてモジュールスクリプトにより多くの関数を簡単に追加できるようになります。
モジュールスクリプトの要求
モジュールスクリプトをロードするには、require()関数を呼び出します。ReplicatedStorageに新しいスクリプトを追加し、そのRunContextをClientに変更します。そして、以下のコードを追加してPickupManager.getPickupBonus関数を呼び出します:
ReplicatedStorageのクライアントスクリプトlocal ReplicatedStorage = game:GetService("ReplicatedStorage")-- ModuleScriptから返される値を取得local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))-- ModuleScript関数を呼び出すlocal bonus = PickupManager.getPickupBonus("legendary")print(bonus) --> 125
ReplicatedStorageにモジュールスクリプトを保存することで、サーバーとクライアント間でコードを共有できるため、ServerScriptServiceからスクリプトを要求する際に同じコードを使用できます:
ServerScriptServiceのスクリプトlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))
require()をModuleScriptに対して呼び出すと、一度だけ実行されてシングルアイテムとして参照を返します。再度require()を呼び出すと、同じ参照が返されるため、返されたテーブルやInstanceを変更すると、以降のrequire()呼び出しはその変更された参照を返します。モジュール自体は複数回実行されません。
クライアント-サーバーの境界の両側からModuleScriptを要求すると、上記の例のように、各側に対してユニークな参照を返します。
パターン
モジュールスクリプトには、ゲームが成長して大きく複雑になるにつれてコードを簡素化し、落とし穴を避けるために使用できる一般的なパターンがあります。
データ共有
個々のオブジェクトにデータを関連付けるには、それらに属性を割り当てるか、Configurationフォルダーを作成してStringValueやIntValueなどの値オブジェクトを作成できます。ただし、数十のオブジェクトやデータ値を追加または変更する場合、両方のアプローチは面倒です。また、テーブルや関数を保存することもできません。
同じデータを複数の同じオブジェクトのコピーで修正するか、異なるオブジェクトに同じデータを再利用したい場合は、ModuleScriptsにデータを保存してください。他のスクリプトでデータを再利用するのが簡単な方法ですし、テーブルや関数を保存できます。
以下の例は、ReplicatedStorage内のModuleScriptで、一般的な銃の設定値を保存しています:
ReplicatedStorageのModuleScriptlocal GunConfig = {}GunConfig.MagazineSize = 20GunConfig.AmmoCount = 100GunConfig.Firerate = 600GunConfig.Damage = {["Head"] = 50;["Torso"] = 40;["Body"] = 25;}return GunConfig
カスタムイベント
カスタムイベントにより、スクリプトが互いに通信できますが、個々のBindableEventオブジェクトへの参照を追跡し続ける必要があると、コードが煩雑になります。
ModuleScriptsを使用して、BindableEventsを保存し、ModuleScriptのメソッドに直接関連付けられたカスタムイベントハンドラーを提供できます。
以下のModuleScriptは、スイッチの状態が変わると発火するカスタムイベントを持っています:
ReplicatedStorageのModuleScript
local Switch = {}
-- スイッチが変更された時に任意のスクリプトがリッスンできるようにバインディングを作成
local bindableEvent = Instance.new("BindableEvent")
Switch.Changed = bindableEvent.Event
local state = false
function Switch.flip()
state = not state
bindableEvent:Fire(state)
end
return Switch
以下のReplicatedStorage内のクライアントスクリプトは、Switch.Changedイベントが発火したときに呼び出す関数を接続します。
ReplicatedStorageのスクリプト
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Switch = require(ReplicatedStorage:WaitForChild("Switch"))
Switch.Changed:Connect(function(newState)
print("スイッチの状態は現在", newState)
end)
-- 数回フリップをテスト
task.wait(1)
Switch.flip()
task.wait(1)
Switch.flip()
カプセル化
カプセル化は、オブジェクトやスクリプティングロジックの周囲に抽象化の層を作成して複雑性を隠す実践です。ModuleScriptsを使用して、RobloxオブジェクトをカスタムLuau関数でカプセル化し、コードを簡素化できます。
たとえば、カプセル化を使用して以下のことができます:
- 単一のRemoteEventオブジェクトを使用してネットワーク通信を簡素化する。
- DataStoreServiceなどの敏感なサービス周囲にエラーハンドリングコードをラッピングする。
- Robloxオブジェクトの機能を制御または拡張するためのカスタムメソッドを定義する。
ゲームのネットワークを実装するために数十の個々のRemoteEventオブジェクトを追跡するのは難しいです。ModuleScriptを使用して単一のRemoteEventをカプセル化することで、この問題を簡素化できます。ユニークなid引数を含めることで、単一のRemoteEventを使用しながら異なるネットワークメッセージを送信できます。
以下の例では、ModuleScript名NetworkManagerClientがRemoteEvent:FireServer()メソッドをカプセル化してこの追加のid引数を含めます。さらに、このModuleScriptはRemoteEventオブジェクト自体を参照しているため、コードの他の部分でこれを参照する必要がありません。このModuleScriptを要求するだけでネットワークメッセージを送信でき、他のコードベース内でRemoteEventオブジェクトに対処する必要はありません。
以下のModuleScriptはReplicatedFirst内にあり、クライアントスクリプトからネットワークメッセージを送信するために呼び出すことができるカプセル化された関数を提供します:
ネットワークモジュール
-- ReplicatedFirst内のNetworkManagerClientという名前のModuleScript
local NetworkManagerClient = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- リモートオブジェクトのFireServer関数をカプセル化
function NetworkManagerClient.FireServer(id, ...)
remoteEvent:FireServer(id, ...)
end
return NetworkManagerClient
以下のModuleScriptはServerScriptService内にあり、すべてのスクリプトが特定のIDに接続するためのBindableEventsを使用します。クライアントがネットワークメッセージを送信すると、指定されたIDに関連付けられた各BindableEventが発火します。
-- ServerScriptService内のNetworkManagerServerという名前のModuleScript
local NetworkManagerServer = {}
local networkSignalList = {}
function NetworkManagerServer.GetServerEventSignal(id)
local bindableEvent = Instance.new("BindableEvent")
-- 新しいBindableEventをidにリンク
table.insert(networkSignalList, {
id = id,
bindableEvent = bindableEvent,
})
return bindableEvent.Event
end
-- 接続する
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
remoteEvent.OnServerEvent:Connect(function(player, id, ...)
-- 受信したリモートイベントのIDに一致するすべてのバインダブルイベントを探す
for _, signal in networkSignalList do
if signal.id == id then
signal.bindableEvent:Fire(player, ...)
end
end
end)
return NetworkManagerServer
以下のLocalScriptは、ID RequestAを持つメッセージを送信し、任意のHello引数を含みます。
-- ReplicatedFirst内のLocalScriptlocal ReplicatedFirst = game:GetService("ReplicatedFirst")local NetworkManagerClient = require(ReplicatedFirst:WaitForChild("NetworkManagerClient"))NetworkManagerClient.FireServer("RequestA", "Hello")
以下のScriptは、ネットワークメッセージID RequestAに接続し、リクエストを受信したときに追加のパラメータを含むステートメントを印刷します。
-- ServerScriptServiceのスクリプト
local ServerScriptService = game:GetService("ServerScriptService")
local NetworkManagerServer = require(ServerScriptService:WaitForChild("NetworkManagerServer"))
NetworkManagerServer.GetServerEventSignal("RequestA"):Connect(function(player, ...)
print("Received RequestA from", player, ...)
end)