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_devicefiresEffectEnabledEventwhen its effect turns on andEffectDisabledEventwhen it turns off. These are your entry/exit hooks.progress_based_mesh_deviceexposesCurrentProgressandProgressTarget— 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 acreative_deviceclass 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 whoseCurrentProgresswe push around.OnBegin<override>()<suspends>:void— the entry point that runs when the island loads. Because it's<suspends>, we canSleepinside it.ShrineSparkle.EffectEnabledEvent.Subscribe(OnSparkleEnabled)— wires our entry action.EffectEnabledEventis the real event onvfx_spawner_device; subscribing means "call this method every time the effect turns on."ShrineSparkle.EffectDisabledEvent.Subscribe(OnSparkleDisabled)— wires our exit action.ShrineSparkle.Disable()andset Pillar.CurrentProgress = 0.0— establish the sleeping starting state.Sleep(3.0)thenShrineSparkle.Enable()— after 3 seconds we enable the sparkle, which makesEffectEnabledEventfire, which runsOnSparkleEnabled, 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).EffectEnabledEventislistenable(tuple()), so the handlers take no parameters — there's no agent to unwrap.set Pillar.CurrentProgress = Pillar.ProgressTarget—CurrentProgressis avar float, so we assign it withset. Note both sides arefloat; Verse never auto-converts int to float, so we write0.0, not0.
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/EffectDisabledEventhand you atuple(), not an agent. Their type islistenable(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 needif (A := Agent?):, but these aren't agent events.)- Enable/Disable ARE the state transitions. Calling
ShrineSparkle.Enable()is what makesEffectEnabledEventfire. If you subscribe after you enable, you'll miss that first entry. Subscribe first (inOnBegin), then enable. CurrentProgressandProgressTargetare floats — use float literals.set Pillar.CurrentProgress = 0fails to compile; write0.0. Verse does not convert int to float for you.CurrentProgressis clamped. It's held between0andProgressTarget. Setting it aboveProgressTargetwon't push past the target, and loweringProgressTargetbelowCurrentProgressclampsCurrentProgressdown and fires threshold events. Drive the target first if you want more range.- Handlers are methods, not local functions. Define
OnSparkleEnabledat 4-space class indentation, andSubscribe(OnSparkleEnabled)fromOnBegin. Nesting them insideOnBeginbreaks the subscription. - Editable fields need a placed device. An unassigned
@editableslot 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.