はじめに
この記事では BindableEvent と BindableFunction の違いや特徴、それらを使用する際の注意点などについて解説します。
この記事での Roblox Studio のバージョンは「Version 0.717.598.7172315 (64bit)」です。
使用用途
BindableEvent と BindableFunction はどちらも Instance の子クラスであり、手動で Explorer 内に配置したり、Instance.new() でスクリプトからインスタンス化したりすることができます。
これらと同じようなクラスとしては RemoteEvent や UnreliableRemoteEvent、RemoteFunction がありますが、これらはクライアント⇔サーバー間で通信したいときに使用します。
この記事で解説する BindableEvent と BindableFunction はそれらとは違い、そのクライアント or サーバー内のスクリプト間で通信したいときに使用します。
配置場所
公式ドキュメントで以下のように記載されている通り、サーバースクリプト(Script)間で通信したいときは ServerScriptService に、クライアントスクリプト(LocalScript)間で通信したいときは ReplicatedStorage に BindableEvent または BindableFunction を置くことが推奨されています。
We recommend using ServerScriptService for communication between server scripts and ReplicatedStorage for communication between client scripts.
Bindable events and callbacks
BindableEvent
まずは BindableEvent について解説します。
BindableEvent で新たに定義されているメンバは Event と Fire() のみです。
基本的な使い方
基本的な使い方は以下のコードの通りです。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent.Event:Connect(function()
print("Hello World")
end)
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent:Fire()
これらのコードをそれぞれ Script として ServerScriptService 内に置き、ServerScriptService 直下に「BindableEvent」という名前の BindableEvent を置きます。
この状態でゲームを実行するとログに「Hello World」と表示され、Script 間で通信できていることがわかります。
複数の値を引数で渡せる
BindableEvent:Fire() の引数は可変長引数であり、以下のコードのように複数の値を引数として渡すことができます。
このコードではログに「Hello World」と表示されます。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent.Event:Connect(function(sampleParameter1, sampleParameter2)
print(`{sampleParameter1} {sampleParameter2}`)
end)
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent:Fire("Hello", "World")
複数の関数を登録できるが、実行順は予測できない
公式ドキュメントに記載されているように1つの BindableEvent に複数の関数を登録することができますが、BindableEvent:Fire() が呼び出されたときのそれらの関数の実行順は予測できません。
You can connect multiple functions to the same BindableEvent, but Luau executes them in an unpredictable order. To ensure that functions execute in a particular order, combine them into a single function and connect it to the event.
Bindable events and callbacks
なので、それらの関数を特定の順番で実行させたい場合は1つにまとめた関数のみを登録しましょう。
例えば以下のコードでは firstFunction() と secondFunction()、thirdFunction() という3つの関数をそれぞれ BindableEvent に登録していますが、BindableEvent:Fire() が呼び出されたときにその登録順のまま実行されるとは限りません。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
local function firstFunction()
print("1")
end
local function secondFunction()
print("2")
end
local function thirdFunction()
print("3")
end
bindableEvent.Event:Connect(firstFunction)
bindableEvent.Event:Connect(secondFunction)
bindableEvent.Event:Connect(thirdFunction)
もし、必ず「firstFunction() → secondFunction() → thirdFunction()」という順番で実行させたい場合は以下のコードのようにそれらを1つの関数にまとめたものだけを BindableEvent に登録しましょう。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
local function firstFunction()
print("1")
end
local function secondFunction()
print("2")
end
local function thirdFunction()
print("3")
end
bindableEvent.Event:Connect(function()
firstFunction()
secondFunction()
thirdFunction()
end)
登録されたいずれかの関数でエラーが発生しても他の関数が止まることはない
先ほど、BindableEvent には複数の関数を登録できると説明しましたが、登録されたいずれかの関数でエラーが発生したとしても、他の関数の処理が止まることはありません。
例えば以下のコードでは validFunction() と invalidFunction() という2つの関数を定義して BindableEvent に登録しています。
validFunction() では1秒待機した後、ログに「Hello World」と表示しており、validFunction() ではエラーが発生しません。
しかし、invalidFunction() では Instance.new() に「Hello」という不正な文字列を渡しており、これを実行すると「Unable to create an Instance of type “Hello”」というエラーが発生します。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
local function validFunction()
task.wait(1)
print("Hello World")
end
local function invalidFunction()
Instance.new("Hello")
end
bindableEvent.Event:Connect(validFunction)
bindableEvent.Event:Connect(invalidFunction)
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent:Fire()
この状態でゲームを実行すると「Unable to create an Instance of type “Hello”」というエラーが発生し、その1秒後に「Hello World」とログに表示され、invalidFunction() で発生したエラーに関係なく validFunction() の処理が実行されているのがわかります。
発火側は登録された全ての関数の完了を待てない
これは後に説明する BindableFunction との大きな違いなのですが、BindableEvent の発火側は登録された全ての関数の完了を待つことができません。
例えば以下のコードでは1秒待機した後、ログに「Hello World」と表示するという処理が書かれた関数を BindableEvent に登録しています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent.Event:Connect(function()
task.wait(1)
print("Hello World")
end)
発火側では BindableEvent:Fire() を呼び出した直後、ログに「Fired」と表示しています。
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent:Fire()
print("Fired")
もし、発火側が関数の完了を待つのであれば「Hello World → Fired」という順番でログに表示されるはずですが、実際は「Fired → Hello World」という順番でログに表示されます。
つまり、BindableEvent:Fire() を呼び出した側は、登録された関数の完了を待たずに次の処理に進んでいることがわかります。
BindableFunction
次は BindableFunction について解説します。
BindableFunction で新たに定義されているメンバは OnInvoke と Invoke() のみです。
BindableFunction は BindableEvent と似ていますが、登録された関数の戻り値を発火側が受け取れる点や、登録された関数の完了を発火側が待つことができる点などが BindableEvent との大きな違いです。
基本的な使い方
基本的な使い方は以下のコードの通りです。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
return "Hello World"
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local returnedValue = bindableFunction:Invoke()
print(returnedValue)
これらのコードをそれぞれ Script として ServerScriptService 内に置き、ServerScriptService 直下に「BindableFunction」という名前の BindableFunction を置きます。
この状態でゲームを実行するとログに「Hello World」と表示され、Script 間で通信できていることがわかります。
複数の値を引数で渡せる
BindableFunction:Invoke() の引数は BindableEvent:Fire() と同様に可変長引数であるため、以下のコードのように複数の値を引数として渡すことができます。
このコードではログに「Hello World」と表示されます。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function(sampleParameter1, sampleParameter2)
return `{sampleParameter1} {sampleParameter2}`
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local returnedValue = bindableFunction:Invoke("Hello", "World")
print(returnedValue)
1つの関数のみを登録できる
BindableFunction.OnInvoke は RBXScriptSignal 型ではなく、関数型であるという特徴からもわかるように BindableFunction は1つの関数のみを登録できます。
もし、複数の関数を BindableFunction に登録した場合は最後に登録された関数のみが実行されます。
例えば以下のコードでは「1」という文字列を返す関数、「2」という文字列を返す関数、「3」という文字列を返す関数をそれぞれ BindableFunction に登録しています。
また、それぞれの関数では「○○ function was executed.」という文字列をログに表示するという処理も書かれています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
print("First function was executed.")
return "1"
end
bindableFunction.OnInvoke = function()
print("Second function was executed.")
return "2"
end
bindableFunction.OnInvoke = function()
print("Third function was executed.")
return "3"
end
発火側は BindableFunction:Invoke() の戻り値をログに表示しています。
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local returnedValue = bindableFunction:Invoke()
print(returnedValue)
この状態でゲームを実行するとログに「Third function was executed.」と「3」が表示され、最後に登録した関数のみが実行されていることがわかります。
登録された関数が値を返さない場合、BindableFunction:Invoke() は nil を返す
BindableFunction では登録された関数の戻り値を発火側が受け取れるわけですが、登録された関数が何も値を返さない場合、BindableFunction:Invoke() の戻り値は nil になります。
例えば以下のコードでは何も値を返さない関数が BindableFunction に登録され、発火側は BindableFunction:Invoke() の戻り値をそのままログに表示しています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local returnedValue = bindableFunction:Invoke()
print(returnedValue)
この状態でゲームを実行するとログに「nil」と表示され、BindableFunction:Invoke() の戻り値が nil になっていることがわかります。
発火側は登録された関数の完了を待つことができる
BindableEvent では発火側は登録された全ての関数の完了を待つことができませんでしたが、BindableFunction では登録された関数の完了を待つことができます。
例えば以下のコードでは1秒待機した後、ログに「Waited」と表示して「Hello World」という文字列を返すという処理が書かれた関数を BindableFunction に登録しています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
task.wait(1)
print("Waited")
return "Hello World"
end
発火側では BindableFunction:Invoke() を呼び出した直後、それの戻り値をログに表示しています。
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local returnedValue = bindableFunction:Invoke()
print(returnedValue)
もし、発火側が関数の完了を待たないのであれば「nil → Waited」という順番でログに表示されるはずですが、実際は「Waited → Hello World」という順番でログに表示されます。
つまり、BindableFunction:Invoke() を呼び出した側は関数の完了を待った後、次の処理に進んでいることがわかります。
テーブルの受け渡しについて
BindableEvent や BindableFunction でテーブルを受け渡しする際にいくつか注意点があるのでまとめておきます。
メタテーブルのみ消えてしまう
BindableEvent や BindableFunction でテーブルを受け渡しする際、そのテーブルにメタテーブルが設定されていると、テーブルの転送中にメタテーブルのみ消えてしまいます。
例えば以下のコードでは発火側で「metaTable」と「sampleTable」という2つのテーブルが定義されています。
metaTable は sampleTable のメタテーブルに設定されています。
metaTable では「metaTableKey」という Key と「metaTableValue」という Value が、sampleTable では「sampleTableKey」という Key と「sampleTableValue」という Value が定義されています。
発火側で sampleTable の各 Value をログに表示した後、sampleTable を BindableEvent:Fire() に渡しています。
BindableEvent に登録された関数では引数で受け取った sampleTable の各 Value をログに表示しています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent.Event:Connect(function(sampleTable)
print(`登録側: metaTableKey: {sampleTable.metaTableKey}`)
print(`登録側: sampleTableKey: {sampleTable.sampleTableKey}`)
end)
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
local metaTable = {}
metaTable.metaTableKey = "metaTableValue"
metaTable.__index = metaTable
local sampleTable = {}
sampleTable.sampleTableKey = "sampleTableValue"
setmetatable(sampleTable, metaTable)
print(`発火側: metaTableKey: {sampleTable.metaTableKey}`)
print(`発火側: sampleTableKey: {sampleTable.sampleTableKey}`)
bindableEvent:Fire(sampleTable)
この状態でゲームを実行するとログに以下のように表示され、発火側ではメタテーブルで定義した Value をしっかり取得できていますが、登録側(登録された関数の中)ではメタテーブルで定義した Value が nil になっており、メタテーブルのみが消失しているのがわかります。
発火側: metaTableKey: metaTableValue
発火側: sampleTableKey: sampleTableValue
登録側: metaTableKey: nil
登録側: sampleTableKey: sampleTableValue
以下のコードのように BindableFunction でテーブルを受け渡しした場合も上記と全く同じログが表示され、BindableFunction においてもメタテーブルのみが消失するのがわかります。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function(sampleTable)
print(`登録側: metaTableKey: {sampleTable.metaTableKey}`)
print(`登録側: sampleTableKey: {sampleTable.sampleTableKey}`)
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local metaTable = {}
metaTable.metaTableKey = "metaTableValue"
metaTable.__index = metaTable
local sampleTable = {}
sampleTable.sampleTableKey = "sampleTableValue"
setmetatable(sampleTable, metaTable)
print(`発火側: metaTableKey: {sampleTable.metaTableKey}`)
print(`発火側: sampleTableKey: {sampleTable.sampleTableKey}`)
bindableFunction:Invoke(sampleTable)
また、以下のコードのように BindableFunction に登録した関数の戻り値としてテーブルを返した場合も同様です。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
local metaTable = {}
metaTable.metaTableKey = "metaTableValue"
metaTable.__index = metaTable
local sampleTable = {}
sampleTable.sampleTableKey = "sampleTableValue"
setmetatable(sampleTable, metaTable)
print(`登録側: metaTableKey: {sampleTable.metaTableKey}`)
print(`登録側: sampleTableKey: {sampleTable.sampleTableKey}`)
return sampleTable
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local sampleTable = bindableFunction:Invoke()
print(`発火側: metaTableKey: {sampleTable.metaTableKey}`)
print(`発火側: sampleTableKey: {sampleTable.sampleTableKey}`)
この場合は発火側(BindableFunction:Invoke() の戻り値)のメタテーブルが消失します。
登録側: metaTableKey: metaTableValue
登録側: sampleTableKey: sampleTableValue
発火側: metaTableKey: nil
発火側: sampleTableKey: sampleTableValue
文字列以外の Key は文字列に変換される
BindableEvent や BindableFunction でテーブルを受け渡しする際、文字列ではない(stirng 型ではない)Key がそのテーブルにあると、その Key はテーブルの転送中に文字列(string 型)に変換されます。
例えば以下のコードでは発火側で sampleTable というテーブルを定義しています。
このテーブルでは Workspace 直下にある「SamplePart」という名前の Part(Instance)が Key として設定されています。
発火側でその Key の型をログに表示した後、そのテーブルを BindableEvent:Fire() に渡しています。
BindableEvent に登録された関数では引数で受け取った sampleTable の Key の型をログに表示しています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent.Event:Connect(function(sampleTable)
for key, _ in sampleTable do
print(`登録側: {typeof(key)}`)
end
end)
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local Workspace = game:GetService("Workspace")
local samplePart = Workspace.SamplePart
local bindableEvent = ServerScriptService.BindableEvent
local sampleTable = {
[samplePart] = "sampleTableValue",
}
for key, _ in sampleTable do
print(`発火側: {typeof(key)}`)
end
bindableEvent:Fire(sampleTable)
この状態でゲームを実行するとログに以下のように表示され、発火側では Key の型が Instance という本来の型のままになっていますが、登録側(登録された関数の中)では Key の型が string になっており、テーブルの転送中に Key が文字列に変換されたことがわかります。
発火側: Instance
登録側: string
以下のコードのように BindableFunction でテーブルを受け渡しした場合も上記と全く同じログが表示され、BindableFunction においても Key が文字列に変換されているのがわかります。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function(sampleTable)
for key, _ in sampleTable do
print(`登録側: {typeof(key)}`)
end
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local Workspace = game:GetService("Workspace")
local samplePart = Workspace.SamplePart
local bindableFunction = ServerScriptService.BindableFunction
local sampleTable = {
[samplePart] = "sampleTableValue",
}
for key, _ in sampleTable do
print(`発火側: {typeof(key)}`)
end
bindableFunction:Invoke(sampleTable)
また、以下のコードのように BindableFunction に登録した関数の戻り値としてテーブルを返した場合も同様です。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local Workspace = game:GetService("Workspace")
local samplePart = Workspace.SamplePart
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
local sampleTable = {
[samplePart] = "sampleTableValue",
}
for key, _ in sampleTable do
print(`登録側: {typeof(key)}`)
end
return sampleTable
end
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local sampleTable = bindableFunction:Invoke()
for key, _ in sampleTable do
print(`発火側: {typeof(key)}`)
end
この場合は発火側(BindableFunction:Invoke() の戻り値)の Key が文字列に変換されています。
登録側: Instance
発火側: string
配列と連想配列を1つのテーブルにまとめて受け渡ししてはいけない
最後の注意点は配列と連想配列を1つのテーブルにまとめて受け渡ししてはいけないということです。
配列(Key が number 型である要素)と連想配列(Key が number 型以外の要素)を1つのテーブルにまとめた状態で受け渡しすると、テーブルの転送中に連想配列部分が削除されてしまうことがあります。
例えば以下のコードでは発火側で sampleTable というテーブルを定義しています。
1つ目の Value である「sampleTableValue1」と、2つ目の Value である「sampleTableValue2」には Key が明示されていません。
なのでこれら2つの Value だけは string 型の配列になっており、内部では number 型の要素番号が Key になっています。
そして3つ目の Value である「sampleTableValue3」には「sampleTableKey1」、4つ目の Value である「sampleTableValue4」には「sampleTableKey2」という Key が明示されています。
なのでこれら2つの Value はそれぞれの Key が文字列になった {[stirng]: string} 型の連想配列(Key と Value がともに string 型の連想配列)になっています。
発火側でそのテーブルを BindableEvent:Fire() に渡した後、BindableEvent に登録されている関数の中でそれぞれの Key と Value をログに表示しています。
-- 登録側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
bindableEvent.Event:Connect(function(sampleTable)
for key, value in sampleTable do
print(`{key}: {value}`)
end
end)
-- 発火側
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService.BindableEvent
local sampleTable = {
"sampleTableValue1",
"sampleTableValue2",
sampleTableKey1 = "sampleTableValue3",
sampleTableKey2 = "sampleTableValue4",
}
bindableEvent:Fire(sampleTable)
この状態でゲームを実行するとログに以下のように表示され、配列部分(Key が number 型だった要素)の Key と Value のみが残り、連想配列部分(Key が string 型だった要素)が消えてしまっているのがわかります。
1: sampleTableValue1
2: sampleTableValue2
これは以下のコードのように BindableFunction を使用したときや、BindableFunction に登録した関数の戻り値としてテーブルを渡したときも同様です。
-- 登録側(引数でテーブルを渡すパターン)
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function(sampleTable)
for key, value in sampleTable do
print(`{key}: {value}`)
end
end
-- 発火側(引数でテーブルを渡すパターン)
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local sampleTable = {
"sampleTableValue1",
"sampleTableValue2",
sampleTableKey1 = "sampleTableValue3",
sampleTableKey2 = "sampleTableValue4",
}
bindableFunction:Invoke(sampleTable)
-- 登録側(戻り値でテーブルを渡すパターン)
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
bindableFunction.OnInvoke = function()
local sampleTable = {
"sampleTableValue1",
"sampleTableValue2",
sampleTableKey1 = "sampleTableValue3",
sampleTableKey2 = "sampleTableValue4",
}
return sampleTable
end
-- 発火側(戻り値でテーブルを渡すパターン)
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService.BindableFunction
local sampleTable = bindableFunction:Invoke()
for key, value in sampleTable do
print(`{key}: {value}`)
end
