Greetings! I just felt like writing a small post that describes how event-based programming in Roblox works, and why it’s usually always king when deciding how to go about structuring your game.
So, what is event-based programming?
It’s exactly as it sounds! An event takes place, causing something to happen afterwards. Let’s take a quick look at the Touched
event for example:
local myPart = workspace.Part
local function onPartTouched()
workspace.Baseplate.BrickColor = BrickColor.Black()
end
myPart.Touched:Connect(onPartTouched)
When ever the specified part gets touched by another part, our baseplate will turn black. Now this might seem like scripting 101, but on a deeper level, could you imagine doing this instead of using the Touched
event?
while true do
local parts = myPart:GetTouchingParts()
if #parts > 0 then --//A part was found in the table returned, so we know something is touching our part
workspace.Baseplate.BrickColor = BrickColor.Black()
break
end
wait()
end
In the second example, we do what’s known as “polling” (essentially just looping control flow every single frame, instead of waiting for an actual event to fire when it occurs.) Broken down even further, this is how our communication looks with the script we’re writing here:
Are there parts touching?
no
Are there parts touching?
no
Are there parts touching?
no
I bet you can start to see where I’m going with this. Obviously looking at this, this looks like horribly bad practice and I bet you’re thinking, Well I’m never going to do that, I’d always use the Touched event for when I need to detect collisions on parts.
But what about other use cases where a Roblox-supplied event might not be an option?
BindableEvents!
That’s where BindableEvents come into play. Essentially, you can use them to define and create your own events, which work similarly to Roblox-supplied events. Here’s how it looks when we set up a “listener” for our BindableEvent (a listener is essentially just a function that will run when the event gets fired)
myBindableEvent.Event:Connect(function()
print("Something fired me!")
end)
As you can see, it looks quite similar to that of a Roblox-supplied event, with the main difference being the event is named ‘Event’.
One thing that makes BindableEvents great is that you can, much like with Roblox-supplied events, have multiple listeners (again, functions that run when the BindableEvent gets fired).
So now, here’s the big question. What do we do if we want to make something happen (like turning the baseplate black) when any of multiple conditions are met? Well, let’s see how we can implement this by using our BindableEvent instead of looping (or polling) for something to happen.
--//Script 1
local myBindable = workspace.BindableEvent
workspace.myPart.Touched:Connect(function()
--//We want our baseplate to change colors when this part gets touched, let's fire our BindableEvent
myBindable:Fire("Touch") --//We can also send in arguments describing what type of condition was met!
end)
What if we also want the part to change colors if the user presses a specific key? We can fire our BindableEvent from a different script no problem!
--//Script 2, not the same script as the script above
local contextActionService = game:GetService("ContextActionService")
local myBindable = workspace.myBindable
local function onKeyPressed()
--//This function is called when ever the user presses F
--//We received user input! Let's make it so the baseplate changes color, much like before.
myBindable:Fire("keyPressed")
end
--//We can use ContextActionService to easily handle user input for when the player presses the F key
contextActionService:BindAction("keyPressed", onKeyPressed, false, Enum.KeyCode.F)
Great! Now we can fire the same event, but when two different situations occur.
Say we have individual systems not affiliated with each other (two scripts for example) and for the one script, we need to run additional game logic for when this event fires; we’re not able to do this from the other listener. We can simply add another listener like the one we did before, for the script we need it to run in!
--//A new script
local myBindable = workspace.myBindable
local timesFired = 0
myBindable.Event:Connect(function()
--//This script needs to do something additionally when this event gets fired
--//Let's keep track of how many times this event gets fired!
timesFired += 1
print(timesFired)
end)
Have I convinced you yet?! A typical event breakdown might look like the following:
When the game round ends, we’ll fire a BindableEvent where all of its listeners will get called. We might need to do something in the lobby that handles turning lights on or off, replicating some local effects to all the players in the game telling them the game is over, and finally any other listeners that need to run when the event gets fired, if a specific script needs to do something when the game ends, like removing cached data in a table.
Closing Notes
Also keep in mind, BindableEvents are not like RemoteEvents/RemoteFunctions. They do not communicate across the client-server model. You can use them both server-sided or client-sided, though. And depending on where you place them, like ReplicatedStorage, you can utilize the same BindableEvent on both the client and server.
Hopefully this clears the air a little bit. I’ve recently dug more into BindableEvents for a more recent project of mine, and I’ve yet to be disappointed with them. Generally, there’s a ton of ways you can get the same end result, but from my experience BindableEvents helps a ton with code structure and keeping your code flexible; it’s super easy to just add another listener for the same event to do something from within a different script, rather than using polling or other means to wait until something happens.
Couldn’t I just use modules?
ModuleScripts are great options as well, and in some cases you definitely could. What I’ve found personally for me is that adding new BindableEvent listeners from within the scope of your ModuleScript is a little tidier than having say, a list of ModuleScript function calls that get ran when X takes place:
--//End of game
moduleA.onEvent()
moduleB.onEvent()
moduleC.onEvent()
Hope this helps!
Don’t hesitate to inform me of any typos or misinformation, I spent a decent amount of time on this so it should be pretty validated, but definitely let me know if not!