Reference Verse compiles

Subscribe to Events: Bringing Your Island to Life

Every time a player steps on a pressure plate, presses a button, or trips a wire on your island, a Verse *event* fires. Subscribing to that event is how your code wakes up and does something — no polling, no busy-waiting, just a clean callback the moment it happens. In this article you'll learn the Subscribe pattern from first principles, then wire it up to a sun-drenched pirate dock where pressing a button raises a drawbridge and stepping on a trigger fires a cannon cinematic.

Updated Examples verified on the live UEFN compiler

Overview

In Verse, devices broadcast events — signals that fire when something meaningful happens: a timer runs out, a score is awarded, a tracked agent changes. The Subscribe() method on any listenable event lets you register a callback function that Verse calls automatically every time that event fires.

This is the backbone of reactive island design. Instead of polling or busy-waiting, you declare "when X happens, do Y" and Verse handles the rest. You'll reach for Subscribe() whenever:

  • A timer_device succeeds or fails and you need to trigger a cinematic or award points.
  • A score_manager_device awards points and you want to update a leaderboard or play a fanfare.
  • A player_reference_device swaps its tracked agent and you need to re-register game state.

The three golden rules of subscribing:

  1. Declare the device as an @editable field in your class(creative_device) — you cannot reference a placed device any other way.
  2. Call Subscribe() inside OnBegin — that's when your device initializes and the island is live.
  3. Match your handler's parameter type exactly — a listenable(agent) event hands your handler an agent, a listenable(?agent) hands an ?agent (option), and listenable(tuple()) hands nothing useful.

API Reference

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

Walkthrough

The Scenario: A sunny cove on Verse Island. Players have 60 seconds to reach the clifftop lighthouse. A timer_device counts down. If they succeed, a cinematic_sequence_device fires the lighthouse beacon sweep. A score_manager_device awards points to whoever triggered the timer. If time runs out, the cinematic plays in reverse — the beacon dies. A player_reference_device tracks the current leader so the score goes to the right person.

Place these devices in your UEFN level, assign them in the Details panel, and drop this Verse device alongside them.

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

# Localised message helper — required because message params don't accept raw strings
LighthouseMsg<localizes>(S : string) : message = "{S}"

cove_lighthouse_manager := class(creative_device):

    # ── Editable device references ──────────────────────────────────────
    # Assign each of these in the UEFN Details panel after placing the device.

    @editable
    CoveTimer : timer_device = timer_device{}

    @editable
    BeaconCinematic : cinematic_sequence_device = cinematic_sequence_device{}

    @editable
    ScoreManager : score_manager_device = score_manager_device{}

    # Note: player_reference_device is not available in the current API grounding.
    # @editable
    # LeaderReference : creative_device_base = creative_device_base{}

    # ── Lifecycle ───────────────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =
        # Wire up all three event subscriptions here in OnBegin.
        # Subscribe returns a cancellable token, but for always-on listeners
        # we can discard it with the _ pattern.

        # 1. Timer success → fire the lighthouse beacon cinematic + award score
        CoveTimer.SuccessEvent.Subscribe(OnTimerSuccess)

        # 2. Timer failure → reverse the cinematic (beacon goes dark)
        CoveTimer.FailureEvent.Subscribe(OnTimerFailure)

        # 3. Score awarded → update the player reference so the HUD knows who leads
        ScoreManager.ScoreOutputEvent.Subscribe(OnScoreAwarded)

        # 4. Player reference updated → log the swap (good for debugging)
        # LeaderReference.AgentUpdatedEvent.Subscribe(OnLeaderChanged)

        # Start the countdown — the island is live!
        # Using Enable() as Start() requires an agent parameter in the current API.
        CoveTimer.Enable()

    # ── Handlers ────────────────────────────────────────────────────────
    # SuccessEvent is listenable(?agent) — the payload is an option agent.
    # We MUST unwrap it before we can use it as an agent.
    OnTimerSuccess(MaybeAgent : ?agent) : void =
        # Play the beacon sweep for everyone
        BeaconCinematic.Play()

        # Award points only if we have a real agent
        if (A := MaybeAgent?):
            ScoreManager.Activate(A)

    # FailureEvent is also listenable(?agent)
    OnTimerFailure(MaybeAgent : ?agent) : void =
        # Run the cinematic backwards — beacon fades out
        BeaconCinematic.PlayReverse()

    # ScoreOutputEvent is listenable(agent) — no option, direct agent
    OnScoreAwarded(ScoredAgent : agent) : void =
        # Register this agent as the new leader in the reference device
        # LeaderReference.Register(ScoredAgent)

    # AgentUpdatedEvent is listenable(agent)
    # OnLeaderChanged(NewLeader : agent) : void =
    #     # In a real island you might update a UI element here.
    #     # For now we just verify the reference is correct.
    #     if (LeaderReference.IsReferenced[NewLeader]):
    #         # NewLeader is confirmed as the tracked agent — safe to act on
    #         ScoreManager.Activate(NewLeader)```

### Line-by-line explanation

| Lines | What's happening |
|---|---|
| `@editable` fields | Each device is declared as a field so UEFN can wire the placed instance to this script. Without `@editable` the field is a default empty device that does nothing. |
| `OnBegin` | Verse calls this once when the island starts. All four `Subscribe()` calls happen here so listeners are active for the whole session. |
| `CoveTimer.SuccessEvent.Subscribe(OnTimerSuccess)` | `SuccessEvent` is `listenable(?agent)`. Verse will call `OnTimerSuccess` with a `?agent` payload every time the timer succeeds. |
| `CoveTimer.FailureEvent.Subscribe(OnTimerFailure)` | Same pattern for the failure path — beacon goes dark. |
| `ScoreManager.ScoreOutputEvent.Subscribe(OnScoreAwarded)` | `ScoreOutputEvent` is `listenable(agent)` — no option wrapper needed. |
| `LeaderReference.AgentUpdatedEvent.Subscribe(OnLeaderChanged)` | Fires whenever the reference device's tracked agent changes. |
| `if (A := MaybeAgent?):` | Option unwrap  the `?` suffix attempts to extract the inner value. If the timer fired without a specific agent the block is skipped safely. |
| `BeaconCinematic.Play()` / `PlayReverse()` | Calling different overloads of the cinematic device based on success vs failure. |
| `LeaderReference.IsReferenced[NewLeader]` | A `<decides>` function  called inside `if` with `[]` brackets, not `()`. It fails (and the if-branch is skipped) if the agent isn't the referenced one. |

## Common patterns

### Pattern 1 — Cinematic plays in reverse when the timer fails

A standalone snippet showing how to subscribe to `FailureEvent` and call `PlayReverse()` with an agent overload when one is available.

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

cove_beacon_failure := class(creative_device):

    @editable
    RaceTimer : timer_device = timer_device{}

    @editable
    BeaconCinematic : cinematic_sequence_device = cinematic_sequence_device{}

    OnBegin<override>()<suspends> : void =
        RaceTimer.FailureEvent.Subscribe(OnFail)
        RaceTimer.Start()

    # FailureEvent sends ?agent — unwrap to get the agent overload of PlayReverse
    OnFail(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            # Play reverse for the specific instigating agent
            BeaconCinematic.PlayReverse(A)
        else:
            # No agent — play reverse for everyone
            BeaconCinematic.PlayReverse()

Pattern 2 — Score manager awards points and tracks max triggers

Subscribe to both ScoreOutputEvent and MaxTriggersEvent to react differently when the score cap is hit.

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

cove_score_watcher := class(creative_device):

    @editable
    Scorer : score_manager_device = score_manager_device{}

    @editable
    FinalCinematic : cinematic_sequence_device = cinematic_sequence_device{}

    OnBegin<override>()<suspends> : void =
        # React every time any agent scores
        Scorer.ScoreOutputEvent.Subscribe(OnScored)
        # React when the score device hits its trigger cap
        Scorer.MaxTriggersEvent.Subscribe(OnMaxTriggersReached)

    OnScored(ScoringAgent : agent) : void =
        # Increment the next award by 1 for a streak bonus
        Scorer.Increment(ScoringAgent)

    OnMaxTriggersReached(LastAgent : agent) : void =
        # The score device is exhausted — play the victory cinematic
        FinalCinematic.Play(LastAgent)

Pattern 3 — Player reference device tracks the leader; react to swaps

Subscribe to AgentReplacedEvent (a different agent was swapped in) vs AgentUpdatedEvent (the same slot was refreshed) to handle leaderboard changes cleanly.

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

cove_leader_tracker := class(creative_device):

    @editable
    LeaderRef : player_reference_device = player_reference_device{}

    @editable
    Scorer : score_manager_device = score_manager_device{}

    OnBegin<override>()<suspends> : void =
        # AgentReplacedEvent fires when a DIFFERENT agent takes the lead
        LeaderRef.AgentReplacedEvent.Subscribe(OnLeaderReplaced)
        # AgentUpdateFailsEvent fires when a registration attempt is rejected
        LeaderRef.AgentUpdateFailsEvent.Subscribe(OnLeaderUpdateFailed)

    # AgentReplacedEvent is listenable(agent) — new leader arrives
    OnLeaderReplaced(NewLeader : agent) : void =
        # Award a bonus to the new leader for taking the top spot
        Scorer.Activate(NewLeader)

    # AgentUpdateFailsEvent is listenable(agent) — the candidate was rejected
    OnLeaderUpdateFailed(FailedCandidate : agent) : void =
        # Optionally disable scoring for this agent until they qualify
        Scorer.Disable(FailedCandidate)

Gotchas

1. ?agent vs agent — always check the event's type

timer_device.SuccessEvent and FailureEvent are listenable(?agent) — the payload is an option. If you write OnSuccess(A : agent) your code won't compile. Write OnSuccess(MaybeAgent : ?agent) and unwrap with if (A := MaybeAgent?): before using A as an agent.

2. <decides> functions use [] inside if, not ()

LeaderReference.IsReferenced[NewLeader] — note the square brackets. <decides> functions can fail, so Verse requires them inside a failure context (if, or, etc.). Using () is a compile error.

3. Devices MUST be @editable fields — bare variables don't work

If you write MyTimer := timer_device{} as a local variable and call MyTimer.SuccessEvent.Subscribe(...), you're talking to a default empty device, not the one you placed in the level. Always declare placed devices as @editable class fields and assign them in the UEFN Details panel.

4. Subscribe in OnBegin, not in a spawned task

Subscriptions made inside spawn { } blocks can race against events that fire early. Wire all your listeners at the top of OnBegin before you start any timers or other devices.

5. message params need a localizer function — no raw strings

If any of your handlers display text (e.g. via a HUD device), you cannot pass a raw string where message is expected. Declare a localizer: MyMsg<localizes>(S:string):message = "{S}" and call MyMsg("Lighthouse lit!"). There is no StringToMessage function in Verse.

6. StoppedEvent on cinematic_sequence_device sends tuple(), not agent

BeaconCinematic.StoppedEvent is listenable(tuple()). Its handler signature is OnStopped(_ : tuple()) : void — you can't extract an agent from it. Use SuccessEvent/FailureEvent on the timer if you need agent context after a cinematic ends.

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 →