Reference Verse compiles

Events in Verse: Subscribe, Handle, and Unsubscribe

Every time a player steps on a pressure plate, grabs a chest, or crosses a finish line, Verse fires an *event*. Subscribing to that event is how your code wakes up and reacts — and unsubscribing is how you stop listening when the moment has passed. Master this pattern and every device on your island becomes a live wire you can tap into.

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

Overview

In Verse, an event is a signal that something happened in the game world. Devices expose events as listenable values (e.g. TriggeredEvent, InteractedWithEvent, ZoneOccupiedEvent). You connect your own function to one of these events by calling Subscribe, which returns a cancelable token. Hold onto that token — calling Cancel() on it later stops your handler from receiving any more signals.

This subscribe/unsubscribe cycle solves a very common island-design problem: you want logic to run only while a certain condition is active. For example, a clifftop watchtower that tracks every player who enters its detection zone, but stops tracking the moment the round ends. Subscribe when the round starts, cancel when it ends — clean and precise.

Reach for this pattern whenever:

  • Multiple independent systems need to react to the same game event.
  • You need to turn a behaviour on and off at runtime.
  • You want to avoid polling (checking state every tick) in favour of reactive, event-driven code.

API Reference

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

Walkthrough

Scenario: The Clifftop Cove Checkpoint

Your island has a sun-drenched cove with a clifftop trigger zone. When a player enters the zone they earn a point; when the round ends the tracker unsubscribes so no late arrivals are counted. A second trigger on the dock resets the score board and re-subscribes the zone listener, ready for the next round.

This example shows:

  1. Subscribing to a ZoneOccupiedEvent (a listenable(agent)) and storing the cancelable.
  2. Unwrapping the optional agent payload.
  3. Cancelling the subscription when the round ends.
  4. Re-subscribing when a new round begins from the dock trigger.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Clifftop Cove Checkpoint — subscribe / unsubscribe demo
clifftop_checkpoint_device := class(creative_device):

    # Drop a capture_area_device over the clifftop zone in your level
    @editable ClifftopZone : capture_area_device = capture_area_device{}

    # A trigger_device on the dock resets and reopens the checkpoint
    @editable DockResetTrigger : trigger_device = trigger_device{}

    # A trigger_device that fires when the round ends (e.g. wired to a round manager)
    @editable RoundEndTrigger : trigger_device = trigger_device{}

    # A score manager to award points
    @editable ScoreBoard : score_manager_device = score_manager_device{}

    # Store the cancelable so we can unsubscribe later
    var ZoneSubscription : ?cancelable = false

    OnBegin<override>()<suspends> : void =
        # Subscribe to the dock reset trigger — handler is a class method
        DockResetTrigger.TriggeredEvent.Subscribe(OnDockReset)

        # Subscribe to the round-end trigger
        RoundEndTrigger.TriggeredEvent.Subscribe(OnRoundEnd)

        # Open the checkpoint immediately at island start
        OpenCheckpoint()

    # Called when the dock reset trigger fires
    OnDockReset(Agent : ?agent) : void =
        # Re-open the checkpoint for the next round
        OpenCheckpoint()

    # Called when the round-end trigger fires
    OnRoundEnd(Agent : ?agent) : void =
        CloseCheckpoint()

    # Subscribe to the clifftop zone and store the cancelable
    OpenCheckpoint() : void =
        # Only subscribe if we are not already subscribed
        if (ZoneSubscription?):
            # Already subscribed
        else:
            set ZoneSubscription = option{ ClifftopZone.AgentEntersEvent.Subscribe(OnPlayerEnterZone) }

    # Cancel the subscription — no more points awarded
    CloseCheckpoint() : void =
        if (Sub := ZoneSubscription?):
            Sub.Cancel()
            set ZoneSubscription = false

    # Handler for AgentEntersEvent — payload is `agent` (not optional here)
    OnPlayerEnterZone(EnteredAgent : agent) : void =
        # Award a point to whoever stepped into the clifftop zone
        ScoreBoard.Activate(EnteredAgent)```

**Line-by-line highlights:**

| Line | What it teaches |
|---|---|
| `@editable ClifftopZone` | Every device you call must be declared as an `@editable` field  you cannot reference a placed device any other way. |
| `var ZoneSubscription : ?cancelable = false` | Store the `cancelable` in an optional so you can check whether you are currently subscribed. |
| `.Subscribe(OnDockReset)` | Pass a *method reference* (no parentheses, no arguments)  Verse wires the signature automatically. |
| `OnDockReset(Agent : ?agent)` | `TriggeredEvent` on a `trigger_device` sends `?agent`; the handler signature must match exactly. |
| `ZoneOccupiedEvent.Subscribe(OnPlayerEnterZone)` | `ZoneOccupiedEvent` sends a plain `agent` (not optional), so the handler takes `agent` directly. |
| `Sub.Cancel()` | Calling `Cancel()` on the stored `cancelable` stops the handler immediately. |
| `set ZoneSubscription = false` | Reset to `false` so `OpenCheckpoint` knows it is safe to re-subscribe. |

## Common patterns

### Pattern 1 — One-shot subscription (fire once, then cancel itself)

A seagull sound plays the first time any player reaches the shore dock, then never again.

```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Plays a one-shot effect the first time the dock trigger fires
dock_first_arrival_device := class(creative_device):

    @editable DockTrigger : trigger_device = trigger_device{}
    @editable AmbientSpeaker : audio_player_device = audio_player_device{}

    # We need a mutable slot to hold the cancelable
    var ArrivalSub : ?cancelable = false

    OnBegin<override>()<suspends> : void =
        set ArrivalSub = option{ DockTrigger.TriggeredEvent.Subscribe(OnFirstArrival) }

    OnFirstArrival(Agent : ?agent) : void =
        # Play the ambient sound once
        if (A := Agent?):
            AmbientSpeaker.Play()

        # Immediately cancel — this handler will never fire again
        if (Sub := ArrivalSub?):
            Sub.Cancel()
            set ArrivalSub = false

Key idea: The handler cancels its own subscription on first invocation — a self-removing one-shot listener.


Pattern 2 — Multiple independent subscribers to the same event

Both the score board and a visual effect device react to the same clifftop zone entry, registered independently.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Two independent handlers wired to one event
multi_subscriber_device := class(creative_device):

    @editable ClifftopZone : capture_area_device = capture_area_device{}
    @editable ScoreBoard   : score_manager_device = score_manager_device{}
    @editable VFXTrigger   : trigger_device       = trigger_device{}

    # Store both cancelables so either can be cancelled independently
    var ScoreSub : ?cancelable = false
    var VFXSub   : ?cancelable = false

    OnBegin<override>()<suspends> : void =
        set ScoreSub = option{ ClifftopZone.ZoneOccupiedEvent.Subscribe(OnZoneScore) }
        set VFXSub   = option{ ClifftopZone.ZoneOccupiedEvent.Subscribe(OnZoneVFX) }

    # Handler 1 — awards a point
    OnZoneScore(EnteredAgent : agent) : void =
        ScoreBoard.Activate(EnteredAgent)

    # Handler 2 — fires a visual trigger (e.g. confetti burst on the clifftop)
    OnZoneVFX(EnteredAgent : agent) : void =
        VFXTrigger.Trigger()

    # You can cancel just the VFX without touching the score
    StopVFX() : void =
        if (Sub := VFXSub?):
            Sub.Cancel()
            set VFXSub = false

Key idea: Each Subscribe call returns its own independent cancelable. You can cancel them separately — the score handler keeps running even after StopVFX() is called.


Pattern 3 — Subscribing inside a spawned async task

Sometimes you want to start listening only after an async condition is met (e.g. a countdown finishes). Spawn a task, await the condition, then subscribe.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Waits for a countdown trigger, then opens the cove zone
countdown_then_open_device := class(creative_device):

    @editable CountdownTrigger : trigger_device      = trigger_device{}
    @editable CoveZone         : capture_area_device = capture_area_device{}
    @editable ScoreBoard       : score_manager_device = score_manager_device{}

    var CoveSub : ?cancelable = false

    OnBegin<override>()<suspends> : void =
        # Spawn a background task that waits for the countdown
        spawn{ WaitThenOpen() }

    WaitThenOpen()<suspends> : void =
        # Block until the countdown trigger fires
        CountdownTrigger.TriggeredEvent.Await()

        # Now subscribe — the cove is open!
        set CoveSub = option{ CoveZone.ZoneOccupiedEvent.Subscribe(OnCoveEntered) }

    OnCoveEntered(EnteredAgent : agent) : void =
        ScoreBoard.Activate(EnteredAgent)

Key idea: Await() and Subscribe() are complementary tools on the same listenable. Use Await() inside a <suspends> function when you want to pause until one signal; use Subscribe() when you want to react to every signal.

Gotchas

1. You MUST store the cancelable — or the GC will eat your subscription

Subscribe returns a cancelable. If you discard it (assign to _ or ignore the return value), the garbage collector may eventually destroy the subscription object and your handler silently stops firing. Always store it in a var field on your creative_device.

# ❌ WRONG — subscription may be GC'd at any time
MyZone.ZoneOccupiedEvent.Subscribe(OnEnter)

# ✅ CORRECT — stored on the device, lives as long as the device
set MySub = option{ MyZone.ZoneOccupiedEvent.Subscribe(OnEnter) }

2. Handler signatures must match the event payload exactly

  • trigger_device.TriggeredEvent → payload is ?agent → handler is MyHandler(A : ?agent) : void
  • capture_area_device.ZoneOccupiedEvent → payload is agent → handler is MyHandler(A : agent) : void

Mixing these up is a compile error. Check the device's API surface for the exact payload type.

3. Unwrap ?agent before using it

Many events send ?agent (an optional). You cannot call methods on an optional directly:

# ❌ WRONG
OnTriggered(A : ?agent) : void =
    ScoreBoard.Activate(A)  # compile error — A is ?agent, not agent

# ✅ CORRECT
OnTriggered(A : ?agent) : void =
    if (RealAgent := A?):
        ScoreBoard.Activate(RealAgent)

4. Subscribing inside a transaction that fails rolls back the subscription

Verse's transactional system means that if a Subscribe call is inside a block that fails (e.g. a failing if branch with side effects), the subscription never takes effect. Keep your Subscribe calls in straightforward, non-failing code paths.

5. Cancel() is idempotent but Subscribe is not

Calling Cancel() more than once on the same cancelable is safe — nothing bad happens. But calling Subscribe twice without cancelling the first creates two active subscriptions, so your handler fires twice per event. Always cancel before re-subscribing, or guard with an if (ZoneSubscription = false) check as shown in the walkthrough.

6. Devices must be @editable fields — you cannot instantiate them inline

# ❌ WRONG — trigger_device{} is a default struct, not a placed device
MyTrigger := trigger_device{}
MyTrigger.TriggeredEvent.Subscribe(OnFire)

# ✅ CORRECT — declared as @editable, wired in the UEFN editor
@editable MyTrigger : trigger_device = trigger_device{}

Build your own lesson with event_subscribe_unsubscribe

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 →