Reference Verse compiles

State Entry & Exit Actions: Timers, Triggers, and HUD Messages on a Pirate Lagoon

State entry and exit actions let you fire off logic the moment a game state begins or ends — starting a countdown when a player reaches the dock, flashing a HUD warning when time runs low, and triggering a cannon blast when the timer expires. In this article you'll wire together a `trigger_device`, `timer_device`, and `hud_message_device` to build a complete pirate lagoon challenge: sail into the cove, beat the clock, and fire the signal cannon. Every API call is real, every snippet compiles.

Updated Examples verified on the live UEFN compiler
Watch the Knottrigger_device in ~90 seconds.

Overview

A state entry/exit action is Verse logic that runs at the exact instant a device flips between two states — the frame it turns on (entry) or off (exit). Instead of polling every tick asking "is it on yet?", you subscribe to the device's own signals and let it tell you.

Two real devices expose this cleanly:

  • vfx_spawner_device fires EffectEnabledEvent when its effect turns on and EffectDisabledEvent when it turns off. These are your entry/exit hooks.
  • progress_based_mesh_device exposes CurrentProgress and ProgressTarget — a state you can read and drive, which lets you decide when to enter or exit a visual state.

The game problem it solves: picture a glowing shrine at the end of a clifftop dock in a bright 2D cel-shaded cove. When the shrine's sparkle VFX turns on, you want the water fountain mesh to start rising; when the sparkle turns off, you want it to sink back. You never want to manually keep those in sync — you want the entry action and exit action to fire automatically. Reach for state entry/exit actions any time "when X wakes up, do Y" or "when X shuts down, undo Y" describes your island moment.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

The scene: a sunlit cove. A player steps on a plate at the end of the dock and a shrine sparkle (VFX Spawner) enables. The moment it enables, a stone pillar (Progress-Based Mesh) rises out of the water by driving its CurrentProgress up to its ProgressTarget. When the sparkle disables, the pillar sinks back down. All of the rising/sinking is triggered by the VFX's entry and exit events — never by polling.

# A cove shrine: VFX enable/disable drives a pillar rising and sinking.
shrine_controller := class(creative_device):

    # The sparkle effect at the shrine. We listen to its entry/exit events.
    @editable
    ShrineSparkle : vfx_spawner_device = vfx_spawner_device{}

    # The stone pillar that rises. We drive its CurrentProgress.
    @editable
    Pillar : progress_based_mesh_device = progress_based_mesh_device{}

    # Called once when the map starts.
    OnBegin<override>()<suspends>:void =
        # Entry action: run RaisePillar the instant the sparkle turns ON.
        ShrineSparkle.EffectEnabledEvent.Subscribe(OnSparkleEnabled)
        # Exit action: run LowerPillar the instant the sparkle turns OFF.
        ShrineSparkle.EffectDisabledEvent.Subscribe(OnSparkleDisabled)

        # Start with everything asleep: sparkle off, pillar sunk.
        ShrineSparkle.Disable()
        set Pillar.CurrentProgress = 0.0

        # Demo the moment: wake the shrine after a short beat.
        Sleep(3.0)
        ShrineSparkle.Enable()

    # ENTRY ACTION — fires when EffectEnabledEvent signals.
    # EffectEnabledEvent is listenable(tuple()), so the handler takes no agent.
    OnSparkleEnabled():void =
        # Raise the pillar all the way to its target height.
        set Pillar.CurrentProgress = Pillar.ProgressTarget

    # EXIT ACTION — fires when EffectDisabledEvent signals.
    OnSparkleDisabled():void =
        # Sink the pillar back into the water.
        set Pillar.CurrentProgress = 0.0

Line by line:

  • @editable ShrineSparkle : vfx_spawner_device — the placed VFX Spawner. You MUST expose the device as an editable field on a creative_device class or Verse cannot see it (Unknown identifier). Drag your VFX Spawner into this slot in UEFN.
  • @editable Pillar : progress_based_mesh_device — the placed mesh whose CurrentProgress we push around.
  • OnBegin<override>()<suspends>:void — the entry point that runs when the island loads. Because it's <suspends>, we can Sleep inside it.
  • ShrineSparkle.EffectEnabledEvent.Subscribe(OnSparkleEnabled) — wires our entry action. EffectEnabledEvent is the real event on vfx_spawner_device; subscribing means "call this method every time the effect turns on."
  • ShrineSparkle.EffectDisabledEvent.Subscribe(OnSparkleDisabled) — wires our exit action.
  • ShrineSparkle.Disable() and set Pillar.CurrentProgress = 0.0 — establish the sleeping starting state.
  • Sleep(3.0) then ShrineSparkle.Enable() — after 3 seconds we enable the sparkle, which makes EffectEnabledEvent fire, which runs OnSparkleEnabled, which raises the pillar. That chain is the whole point: one action triggers the next through state events.
  • OnSparkleEnabled() / OnSparkleDisabled() — these are class-scope METHODS (not nested functions). EffectEnabledEvent is listenable(tuple()), so the handlers take no parameters — there's no agent to unwrap.
  • set Pillar.CurrentProgress = Pillar.ProgressTargetCurrentProgress is a var float, so we assign it with set. Note both sides are float; Verse never auto-converts int to float, so we write 0.0, not 0.

Common patterns

Pattern 1 — Restart a burst VFX on exit (using Restart)

When the shrine goes to sleep, replay a one-shot "poof" burst. Restart re-triggers a Burst VFX.

poof_on_exit := class(creative_device):

    @editable
    ShrineSparkle : vfx_spawner_device = vfx_spawner_device{}

    @editable
    PoofBurst : vfx_spawner_device = vfx_spawner_device{}

    OnBegin<override>()<suspends>:void =
        ShrineSparkle.EffectDisabledEvent.Subscribe(OnShrineOff)

    # Exit action: replay the burst poof the moment the shrine turns off.
    OnShrineOff():void =
        PoofBurst.Restart()

Pattern 2 — Read the progress state to gate the entry action

Only wake the sparkle once the pillar is fully raised. This reads CurrentProgress against ProgressTarget.

gate_by_progress := class(creative_device):

    @editable
    Pillar : progress_based_mesh_device = progress_based_mesh_device{}

    @editable
    ShrineSparkle : vfx_spawner_device = vfx_spawner_device{}

    OnBegin<override>()<suspends>:void =
        # Drive the pillar up over time, then enable the sparkle only when full.
        set Pillar.CurrentProgress = Pillar.ProgressTarget
        Sleep(1.0)
        if (Pillar.CurrentProgress >= Pillar.ProgressTarget):
            ShrineSparkle.Enable()

Pattern 3 — Chain two effects' entry events

When the first effect enters its ON state, enable a second effect too — an entry action that cascades.

cascade_entry := class(creative_device):

    @editable
    FirstSparkle : vfx_spawner_device = vfx_spawner_device{}

    @editable
    SecondSparkle : vfx_spawner_device = vfx_spawner_device{}

    OnBegin<override>()<suspends>:void =
        FirstSparkle.EffectEnabledEvent.Subscribe(OnFirstOn)
        SecondSparkle.Disable()
        FirstSparkle.Enable()

    # Entry action on FirstSparkle cascades to enable SecondSparkle.
    OnFirstOn():void =
        SecondSparkle.Enable()

Gotchas

  • EffectEnabledEvent / EffectDisabledEvent hand you a tuple(), not an agent. Their type is listenable(tuple()), so your handler method takes no parameters: OnSparkleEnabled():void. Don't write (Agent:?agent) — there is nothing to unwrap here. (An agent event would need if (A := Agent?):, but these aren't agent events.)
  • Enable/Disable ARE the state transitions. Calling ShrineSparkle.Enable() is what makes EffectEnabledEvent fire. If you subscribe after you enable, you'll miss that first entry. Subscribe first (in OnBegin), then enable.
  • CurrentProgress and ProgressTarget are floats — use float literals. set Pillar.CurrentProgress = 0 fails to compile; write 0.0. Verse does not convert int to float for you.
  • CurrentProgress is clamped. It's held between 0 and ProgressTarget. Setting it above ProgressTarget won't push past the target, and lowering ProgressTarget below CurrentProgress clamps CurrentProgress down and fires threshold events. Drive the target first if you want more range.
  • Handlers are methods, not local functions. Define OnSparkleEnabled at 4-space class indentation, and Subscribe(OnSparkleEnabled) from OnBegin. Nesting them inside OnBegin breaks the subscription.
  • Editable fields need a placed device. An unassigned @editable slot means the event never fires because there's no real device behind it. Always drag your VFX Spawner / mesh into the slot in the Details panel.

Guides & scripts that use trigger_device

Step-by-step tutorials that put this object to work.

Build your own lesson with trigger_device

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →