effects — fire a finished effect in one line
effects is the global gameplay code reaches for when something should happen
on screen and then be gone. It plays the effect assets the catalogue
carries, owns their lifetime, and re-uses what the last shot left rather than
allocating again.
local h = effects.play("@builtin::systems.effects.combat.explosion", {
position = { 0, 2, 0 },
params = { scale = 4.0, coreColor = { 1, 0.4, 0.1 } },
})
That is the whole call. Nothing ticks it, nothing tears it down.
The calls
| Call | What it does |
|---|---|
effects.play(identity, opts) | Play once at a world position. |
effects.playOn(identity, entity, opts) | Play seated on an entity; ends if the entity leaves the world. |
effects.list() | Every canonical identity this world can play. |
effects.describe(identity) | Family, summary, every parameter with its type/default/range, and the measured cost. |
effects.observe() | Live plays, what the pool holds, and why a play is producing nothing. |
effects.silenceReasons() | The closed set of reasons a play produces nothing. |
effects.drain() | Free every backend the pool is holding idle. |
effects.backends() | The backend kinds an effect can be built out of. |
effects.registerBackend(kind, backend) | Add one. |
The handle
h:stop() -- stop producing; what is drawing finishes naturally
h:cancel() -- stop and take it away now
h:retarget(positionOrEntity) -- move it
h:setParam(name, value) -- re-tune a declared parameter while it runs
h:isPlaying() -- still producing or still drawing
h:isFinished() -- ended and gave back everything it held
h:stats() -- what it is doing, in numbers to assert on
h:whySilent() -- reason, detail — or nil while it is producing
h:setParam carries whatever the effect declares: a number, a range, a colour,
a vec2, a vec3, an entity or a flag. A beam's endpoints and an
impact's host are not scalars, and the channel carries them as themselves.
Cost, and the pool
Building an emitter is expensive and its VRAM is allocated per declared slot, whether or not those slots are ever filled — which is why one emitter per shot churns the renderer. The runtime leases every backend an effect is made of from one shared pool: the first play allocates, and every play after it re-seats what the last one gave back.
The pool is sized by concurrency, not by shot count. It grows to the highest
number of effects it has had to cover at the same time and then holds there.
Five at once allocate for five; the sixth round of five allocates nothing.
observe() reports peakLive and peakLeased beside bytes for exactly this:
a total climbing alongside the peak is a pool warming up, a total climbing while
the peak stands still is a leak.
The retention policy is explicit. The pool keeps what it has leased for as
long as the engine runs and returns it on effects.drain() and nowhere else.
There is no idle timer. effects.observe() reports leased, pooled and
bytes so a caller can see what is being held.
When nothing appears
Ask. effects.observe() and handle:whySilent() answer from a closed set:
| Reason | Means |
|---|---|
finished | The play has run out and given back everything it held. |
noBackends | The effect staged nothing at these parameters. |
targetMissing | The entity the play was seated on has left the world. |
notSimulating | The engine is not advancing the simulation, so nothing emitted moves or draws. |
noCamera | No camera is rendering the scene. |
backendSilent | Everything the play holds is allocated and producing nothing; the detail names which. |
notSimulating is the one to expect first: particles advance on the play clock,
so an effect fired in edit mode holds live particles and submits nothing.
See also
guides { topic = "visual-effects" }— the whole topic, including authoring.man /zero/source/libs/@builtin/assetTypes/effect.assetType— what an effect asset carries.man /zero/source/libs/@builtin/systems/effects.package— the runtime, its backends and its pool.
Scoped to this part · feeds back into the world's score.