Components#
A component is behaviour you attach to every instance that wears a tag.
You write it once, and it starts on each tagged instance when the instance appears and cleans up
when the instance loses the tag or is destroyed. Tag twenty parts spinner in the editor and one
component spins all of them, including the ones a script clones in later.
An obby's spinning bars are a good fit. Each bar is tagged spinner in the editor, with a speed
attribute in degrees a second, and hurts whoever it hits:
-- server
local component = require("@moud/component")
local Spinner = component.new({
tag = "spinner",
damage = 20,
start = function(self)
self.home = self.instance.cframe
self.angle = 0
self.speed = math.rad(self.instance:getAttribute("speed") or 90)
self:track(self.instance.touched:connect(function(other)
local body = other:firstAncestorOfClass("Character")
if body then
body.humanoid:takeDamage(self.damage)
end
end))
end,
step = function(self, dt)
self.angle += self.speed * dt
self.instance.cframe = self.home * cframe.angles(0, self.angle, 0)
end,
stop = function(self)
if self.instance.parent then
self.instance.cframe = self.home
end
end,
})startreads the bar's own attribute, so a fast bar and a slow one share the component.- The
touchedconnection is tracked, so it is disconnected when the bar stops. Withouttrack, a bar that lost its tag would keep hurting players. stopputs a bar that lost its tag back the way it started. A destroyed bar has no parent and is left alone.
The definition#
| Field | |
|---|---|
tag |
the tag the component follows |
start(self) |
runs when an instance with the tag appears, or gains the tag, in its own thread so it can wait |
stop(self) |
runs when the instance loses the tag, is destroyed or moves into storage |
step(self, dt) |
runs on every game.stepped for each running instance |
renderStep(self, dt) |
runs every frame on a client, for each running instance |
where(instance) |
return false to leave an instance out |
inStorage |
true to also run on instances inside ServerStorage, ReplicatedStorage, StarterPack or StarterCharacterScripts |
Any other field, like damage above, is shared by every object of the component, and functions you
add become methods: self:explode().
The object#
Each tagged instance gets its own object, self in every function:
self.instanceis the instance.self.runningis true from the start until it stops.self:track(thing)keepsthingand cleans it up when the object stops, newest first. A connection is disconnected, an instance destroyed, a function called, ataskhandle cancelled, and anything withcancel,disconnectordestroyhas that called. It handsthingback, so it wraps the line that makes it.- Fields you set on
selfare that instance's own state.
If start is still waiting when the instance goes away, its thread is stopped.
The component#
What component.new hands back follows every object it runs:
get(instance)is the object for that instance, or nil.all()lists every running object.startedandstoppedfire with the object.destroy()stops every object and stops following the tag.
Fields on an object can be changed from outside. A round that ends with every bar stopped:
-- server
local function freezeSpinners()
for _, spinner in Spinner:all() do
spinner.speed = 0
end
endWhere it runs#
A component runs on the side that made it. Tags replicate, so a server script can tag a part and a
client component can react to it: the server decides which doors are locked, and the client
component on locked draws the padlock and plays the sound.
Instances inside a storage container are kept, not in the world, so a template coin in
ServerStorage does not start a coin component. A copy cloned out of storage starts one, and an
instance moved out of storage starts one too. Set inStorage = true when you want the templates as
well.
