The Binder Pattern

Let’s learn about Binders, a useful pattern that links CollectionService and OOP.

In this post, I’m featuring the Binder pattern, its uses, and reasons you should start using them in your Roblox projects.

A binder connects a CollectionService tag on Instance with an object in Lua code. When a tag is applied to an Instance, an object is constructed from a class with this Instance. During its lifetime, the object might read from various attributes on the instance to modify how it behaves. Finally, if the tag or entire Instance is removed, the object is cleaned up or destroyed accordingly.

For the purposes of this article, I’ll be using this implementation (I recommend you keep this open while reading).

Terminology Note
– When I say instance, I’m talking about a Roblox object like a Part or Humanoid. You see these in the Explorer window.
– When I say object, I’m talking specifically about an idiomatic Lua object as described in my Object-Oriented Programming in Lua article series. These are usually defined in a ModuleScript. See Person class example (Pastebin). Usually, I’ll use the word object in reference to both of these, but for this article I’ll be particular about it!

This powerful code pattern was shown in the 5 Powerful Code Patterns Behind Top Roblox Games talk from Roblox Developer Conference (RDC) 2020.

The section on binders is at the end – starts at 31:36! But you should totally watch the other four 🙂

When to Use a Binder

A binder is useful when many instances need to behave in a similar fashion, especially if you’re adding and removing those behaviors at will. To add behaviors like this dynamically, you’d normally need to go require the module containing your class, instantiate the object, then destroy it when you no longer need it. This means you need to manage the life cycle of that object, which can be annoying if there’s lots of instances involved, and downright hair-pulling if the script that applies the behavior isn’t the same as the script that un-applies the behavior.

Creating a Bindable Class

Writing a class that works with a Binder out-of-the-box only requires a few steps. First, ensure that the object’s constructor only requires the instance itself. Then, write a destroy function which disconnects connections and sets references to nil. Finally, decide on a tag name (I usually use the name of the class itself) and we’re all set to go! Consider this ModuleScript class, Deadly, which makes a BasePart kill any Humanoid that touches it:

local Deadly = {}
Deadly.__index = Deadly
Deadly.TAG = "Deadly"

function Deadly.new(part)
	assert(typeof(part) == "Instance" and part:IsA("BasePart"))
	local self = setmetatable({
		part = part;
	}, Deadly)
	self.touchConn = self.part.Touched:Connect(function (...) return self:onTouch(...) end)
	return self
end

function Deadly:destroy()
	self.touchConn:Disconnect()
	self.touchConn = nil
	self.part = nil
end

function Deadly:onTouch(otherPart)
	local human = otherPart.Parent and otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
	if not human then return end
	self:kill(human)
end

function Deadly:kill(human)
	human.Health = 0
end

return Deadly

Although this class doesn’t have any configurable bits, you could add attributes onto the wrapped instance. For example, you could add a configurable damage amount by adding a Damage attribute:

function Deadly:getDamage()
	-- use the "or" keyword to provide defaults
	return self.part:GetAttribute("Damage") or 100
end

function Deadly:kill(human)
	human.Health -= self:getDamage()
end

Binding a Class

Notice how I included a tag name as a class-level constant in Deadly. Now, we can set up a Binder in a Script to use the class, like this:

local Deadly = require(script.Parent.Deadly)
local Binder = require(script.Parent.Binder)

local binder = Binder.new(Deadly.TAG, Deadly)
binder:bind() -- Where the magic happens!

Since the Binder object is a sort of singleton on a per-class bases, it can be helpful to include it within the class itself. Expanding on Deadly:

Deadly.Binder = Binder.new(Deadly.TAG, Deadly)

Now all that’s left is to require it, and call Deadly.Binder:bind()!

Various Applications

I’m sure your mind is racing with ideas already, but just in case it isn’t, here’s a few useful and fun applications of the binder pattern. These examples were pulled from some projects I’ve been working on, so be sure to double check for dependencies.

Badges

Most Roblox games award a badge, a great majority of those games call BadgeService:AwardBadge after touching a part. Or clicking a ClickDetector. Or triggering a ProximityPrompt. What if simple badge code didn’t matter anymore, and you never had to edit an ID into a script again? Just use this bindable BadgeAwarder class and slap on the tag and BadgeId attribute!

(Note: This requires roblox-lua-promise as a dependency)

Humanoids

I’m sure you’ve wanted to add your own avatar to your own game before. Why not make a bindable class which automatically loads the appearance of a player? Just tag it “PlayerSpoof” and you’re off to the races:

(Note: This requires roblox-lua-promise as a dependency)

UI

A game’s interface hierarchy can be just as rich as its environment hierarchy. Instead of having a LocalScript sift through the UI tree, use a binder and save yourself the trouble of finding that one little TextBox that is only supposed to accept digits.

Tools

Tools like Melee weapons and hitscan projectile weapons no longer need to house their scripts, just their configuration. You could bind multiple classes, too: one to handle ammo, the other to handle weapon firing. Another benefit emerges for LocalScripts: since the LocalScript is no longer housed in the Tool, it continues running even if the Tool is dropped. Typically, the LocalScript would be stopped altogether.

Download

Author: Ozzypig

Roblox developer and computer scientist. I love all things coding and game design!