7.2 Bindables

BindableEvent

By their very nature games are interactive. This requires that scripts only run if and when some occurrence takes place in the game world. And because of that, events are incredibly useful for making things happen.

We’ve learned how to connect our own functions to events and have them run when that event takes place. However the limitation with the events we’ve encountered thus far is that they are coupled to a class or library and can only trigger within the context of said class or library.

The Part.Touched event for instance is available only with BaseParts and can only trigger as a result of the part coming into contact with another physical object.

So what if there was a way to define a generic event of which you, the user, can trigger and other scripts can connect and listen for that event?

That is purpose of the BindableEvent. Think of it as a generic event that has been abstracted into its own class.

Like any other event, one or more scripts can connect to it and have code be executed on their behalf.

But unlike normal events, BindableEvents can only be fired from a script. This allows you to define your own conditions for when to fire that event.


Let’s create a BindableEvent example consisting of two scripts. The first script will connect and listen for the event. And from the other, trigger that event.

Start by inserting a BindableEvent object into ReplicatedStorage. Then place two scripts into the workspace.

Lua
local bindableEvent = game.ReplicatedStorage.Event

bindableEvent.Event:Connect(function(arg)
	print("Event received!", arg)
end)

Connect to the event in the same manner as you would with any other event.

I’m also defining a parameter so that we can pass in an argument when the other script triggers the event.

To trigger the event, call the ‘Fire’ method on the event.

Lua
local bindableEvent = game.ReplicatedStorage.Event

while task.wait(1) do
	bindableEvent:Fire(time())
end

We loop through and call the fire method at one second intervals, passing in the time. If you run the game, the time since the session began will print to the out.

Something to note is that BindableEvents (and BindableFunctions) do not cross the client-server boundary. This means that an event generated from a server script will only trigger on server side scripts.

Similarly, client side BindableEvents remain only on that specific client.


Okay, got it.

So BindableEvents are a way to define and generate custom events.

But why go through all that trouble?

Wouldn’t it be easier to forgo the BindableEvent entirely and just have the would-be firing script directly invoke functions from would-be listener scripts instead? From something like a ModuleScript?

If you only had a few functions that never need to be changed and you knew what those would be ahead of time. Probably.

But what happens if there are a not insignificant number of scripts? And what if that number is subject to change, dynamically during runtime or as new features are added to the game?

Let’s say, for instance, in your game there is a skeleton boss monster. And after defeating him, you have a script that gives the player experience points, one to reward them loot, another that unlocks the door to the dungeon exit, and maybe also another that gives the player an achievement.

Four scripts that “observe” for that occurrence and want to know if it took place.

Contrast that to when the player defeats a non-boss monster. Now, only the XP script and maybe the loot script are interested in that occurrence.

If you were to use a central location for managing the scripts needed to run during these occurrences, it gets real messy real quick.

What events do is move that responsibility into the listener scripts themselves by allowing the script to “subscribe” to only the ones they are interested in.

This decouples the observers from the event.

BindableEvents take this one step further by also decoupling the event from the script(s) that trigger the event.

Not only does this system allow an event to notify multiple observers, it also allows multiple sources to fire that event while incurring minimal additional overhead.

One thing to keep in mind is that BindableEvents can only send notifications in one direction. Observers cannot directly respond to notifications it receives.


BindableFunction

If a script needs to respond to notification from an external source, BindableFunctions may be better suited.

The tradeoff is that there can only be one “listener” assigned to a BindableFunction at any time since multiple scripts attempting to simultaneously respond to a notification could be disastrous.

Because of this, BindableFunctions have a ‘callback’ which you need to assign a function to be ran when the BindableFunction is invoked. If there is not one set and the BindableFunction is invoked, it will yield indefinitely.

Although BindableFunctions cannot execute callbacks from multiple sources simultaneously, the decoupling between communicating scripts can still make them tremendously useful.

In ReplicatedStorage, insert a BindableFunction object and then place one script into ServerScriptService and another in the workspace.

Use one of the scripts to assign the callback:

Lua
local bindableFunction = game.ReplicatedStorage.Function

bindableFunction.OnInvoke = function(arg)
	print("OnInvoke Recieved:", arg)
	return math.random()
end

Then in the other script, create a loop that invokes the BindableFunction at regular intervals:

Lua
local bindableFunction = game.ReplicatedStorage.Function

while task.wait(1) do
	local result = bindableFunction:Invoke(time())
	print(result, '\n')
end

Note that unlike normal functions, BindableFunctions (and BindableEvents) do have certain argument Limitations to keep in mind.