Effect
Binds an effect asset to the entity that carries it.
entity.spawn("fuel_drum").component.add("Effect", {
effect = "@builtin::systems.effects.combat.explosion",
playOnStart = true,
looping = false,
scale = 2.0,
params = { coreColor = { 0.2, 0.5, 1.0 } },
})
effects.play fires an effect and forgets it. This is the durable form of the
same thing: the effect starts when the component becomes live, follows the
entity as it moves, and goes when the entity goes — despawned, cleared
with the scene, unloaded with its layer, reloaded, or removed as a component.
Nothing has to be remembered by the author, and nothing is left leased when the
entity is gone.
It is a binding, not a second implementation. The component plays through the
same runtime effects.play does, so an effect looks the same whichever of the
three ways it is played: the asset's own fx:play, the one-shot call, or this.
Fields
| Field | What it is |
|---|---|
effect | The effect asset to play. |
playOnStart | Start the effect when the component becomes live, and again when it is re-enabled. |
looping | Start the effect again each time it runs out — a fire, a smoke column, anything that runs while the entity stands. |
scale | Multiplier on the bound effect's own scale parameter. 1 leaves it as authored. |
intensity | Multiplier on the bound effect's own intensity parameter. |
tint | Replaces the bound effect's tint parameter. Unset leaves the effect's own colour. |
duration | Seconds the play runs for. 0 takes the length the effect declares. |
params | The bound effect's own overrides, by declared name — everything the shared fields above do not cover. |
Every one of these is a declared field, which is what makes an authored
value survive world.save / world.load. An override stashed in an undeclared
key is dropped by the serializer with not declared in component schema — refusing to write table from snapshot data, and the effect comes back at
parameters nobody set.
scale, intensity and tint are the parameters effects share, and they reach
the bound effect by name. An effect that declares no parameter of that name
has the field reported back by describe().ignored rather than applied:
local fx = drum.component.get("Effect")
fx:describe()
--> { identity = "@builtin::systems.effects.combat.explosion",
--> playing = true,
--> overrides = { scale = 3.0, coreColor = { 0.2, 0.5, 1.0 } },
--> ignored = { "intensity" } }
The explosion declares scale and no intensity, so scale = 2 reaches it as
3.0 — twice the 1.5 the effect declares — and intensity is named as a
field that would draw nothing.
Methods
| Call | What it does |
|---|---|
fx:play() | Start the effect, or restart it from the top. |
fx:stop() | End the effect and release everything it holds. A looping effect stays down until play() starts it again. |
fx:isPlaying() | Whether a play is still running. |
fx:setParam(name, value) | Re-tune one declared parameter while it plays. The override is kept on params, so it is what the next play starts with and it survives save and load. |
fx:whySilent() | Why the play is producing nothing, from the closed set effects.silenceReasons() enumerates. |
fx:stats() | The live play's own numbers — age, duration, what it holds. |
fx:describe() | What is bound, what is playing, the overrides it sends, the shared fields the bound effect has no parameter for, and fault — why the bound effect did not start, when it did not. |
When nothing starts
A component that reads as configured and draws nothing carries the reason on itself rather than only in the engine log:
fx:describe().fault --> "effect 'myFire': could not load its definition …"
fx:whySilent() --> "noBackends", "the bound effect did not start: …"
fx:play() --> raises with the same message
fault is cleared the moment a play starts.
Which values change what is on screen without restarting
Changing scale, intensity, tint or params re-tunes the play that is
already running — the blast does not restart from its opening flash every time a
slider moves. Changing effect binds a different asset and starts it. duration
and looping are read by the next play.
Edit mode
The effect draws in the editor, not only in play. A component is present every
frame for as long as its effect runs, so it owns that effect's clock: the play
is started held — the runtime's own word for a play whose caller advances it —
and walked forward from update in play mode and editorUpdate in edit. That
is the same clock the runtime's driver would have given it, taken over by the
one thing that is guaranteed to tick in both modes.
whySilent() still answers notSimulating in edit mode: the reason resolves
from the engine's mode, which is a conservative reading of whether particles are
advancing rather than a reading of this play. Read stats().age climbing and the
emitters' own particle counts when what you want to know is whether an effect in
the editor is running.
Disabled is not despawned
Disabling the component ends its effect and releases what it held; the component keeps everything it was authored with, so enabling it again plays the same effect at the same parameters. Despawning the entity ends the effect the same way, and the component goes with it.
What it costs
Nothing beyond what the effect itself costs. The play leases its backends from
the same pool effects.play leases from, and gives them back when it ends —
effects.observe() reports leased / pooled / bytes and the peakLive /
peakLeased the pool is sized by. N entities carrying the component and then
despawned leave leased where it started and pooled holding what the pool
keeps until effects.drain().
Scoped to this part · feeds back into the world's score.