For the complete documentation index, see llms.txt. This page is also available as Markdown.

Effects

Effects temporarily take control of an entity for complex behaviors like dashes, attacks, eating animations, cutscenes, or death/respawn sequences.

Effects can be used on players, NPCs, or any entity. They’re especially useful when you want:

  • Movement you control in code (dash/knockback/roll)

  • “Lock out” player input for a short sequence (eat, revive, cutscene)

  • A timed window of state (invincibility, slow, stun)

  • UI that is tied to a temporary state (respawn countdown)

Active vs passive

All Out supports two kinds of effects:

  • Active effects: one at a time per entity. Setting a new active effect interrupts the current active effect.

  • Passive effects: multiple can be attached at once (stackable buffs/debuffs/status effects).

An active effect is also inserted into the entity’s effect list, so it shows up in effect_iterator just like passive effects.

Quick start (a simple timed effect)

Create a class that inherits from Effect_Base, then attach it to an entity.

Apple_Pop_Effect :: class : Effect_Base {
    sprite: Sprite_Renderer;
    start_scale: v2;
    start_pos: v2;

    effect_start :: method() {
        sprite = entity.get_component(Sprite_Renderer);
        start_scale = entity.local_scale;
        start_pos = entity.local_position;
        set_duration(0.4); // auto-removes after 0.4s
    }

    effect_update :: method(dt: float) {
        t := get_elapsed_time() / 0.4;

        // Scale up then shrink to nothing
        scale_curve: float;
        if t < 0.3 {
            scale_curve = lerp(1.0, 1.3, Ease.out_back(t / 0.3));
        } else {
            scale_curve = lerp(1.3, 0.0, Ease.in_back((t - 0.3) / 0.7));
        }

        entity.set_local_scale(start_scale * scale_curve);

        // Float upward slightly
        rise := Ease.out_quad(t) * 0.5;
        entity.set_local_position({start_pos.x, start_pos.y + rise});

        // Fade out near the end
        if sprite != null {
            alpha := 1.0 - Ease.in_quad(max(0.0, (t - 0.5) / 0.5));
            sprite.color.w = alpha;
        }
    }

    effect_end :: method(interrupt: bool) {
        // Clean up when done (or interrupted)
        entity.destroy();
    }
}

Attach it as an active effect:

Effect lifecycle

Effects are callback-based. Implement only what you need:

  • effect_start(): called once when the effect is attached

  • effect_update(dt): called every frame

  • effect_late_update(dt): called every frame after effect_update

  • effect_end(interrupt): called when the effect is removed

Active effects (exclusive control)

Use set_active_effect for effects that should be mutually exclusive (dash, attack windup, death sequence).

If the entity already has an active effect, it is ended with interrupt = true.

Passive effects (stackable buffs / status)

Use add_passive_effect for effects that can stack (slow, poison, shield, invincibility window).

API reference

Effect_Base

Applying / removing effects

Checking / iterating effects

freeze_player vs disable_movement_inputs

  • player_specific.freeze_player = true: locks player position entirely.

  • player_specific.disable_movement_inputs = true: ignores input, but your effect can still move the player (dash/roll).

Both only apply when player != null (i.e. the effect is on an entity that has a Player component).

Player emotes

Player emotes use a built-in active effect. Starting another emote interrupts the current emote, while any other active effect prevents a new emote from starting. Movement interrupts emotes and restores the main animation layer, except for Emote/T_Pose, which allows movement.

Use block reasons when gameplay should temporarily prevent the emote wheel from starting an emote:

Reasons are counted as entries, so matching add/remove calls are important. remove_emote_block_reason removes one matching entry and returns whether it found one. You can also query or control the built-in effect directly:

try_trigger_emote returns false when the animation is not equipped, emotes are blocked, or another kind of active effect owns the player. The normal emote wheel uses this same path.

Effect examples

Dash / roll (active effect)

Invincibility window (passive effect)

Use a passive effect to represent the invincible state, and check for it wherever you apply damage.

2x size for a few seconds (passive effect)

Death / respawn sequence (active effect with local UI)

Cutscene lock (active effect)

Smooth custom visuals

The simulation runs at a fixed rate while rendering can run faster. Drawing from a component callback is automatically anchored to that component's entity. For other cases:

  • Wrap world-space drawing for another entity in UI.begin_world_space_ui(entity) and UI.end_world_space_ui().

  • For a moving position that does not belong to an entity, keep a persistent Position_Interpolation_Helper. Pass the offset from update(position) to UI.push_interpolation_offset, then pop it after drawing.

  • Use a persistent Float_Interpolation_Helper with UI.quad_fill for a changing bar value.

  • Call entity.mark_teleported() after a discontinuous position change.

Keep interpolation helpers in persistent component or UI state. A helper created inside the draw method has no previous frame to interpolate from.

Best practices

  • Use set_active_effect when the effect “owns” the entity for a short sequence (dash/attack/death/cutscene).

  • Use add_passive_effect for stackable state (buffs/debuffs/status).

  • Use set_duration(...) for timed effects instead of manual timers.

  • Always restore any modified state in effect_end (friction, scale, animation triggers, etc).

  • Run gameplay effects in the shared predicted path.

  • Draw effect-related UI from the player's ao_late_update and guard it with is_local_or_server().

Last updated