Custom Events: The CheckpointPassed Event Bus

In this lesson you'll learn to

  • ✓Declare event(payload), Signal it, and Await/Subscribe across classes — decoupling checkpoints from the race manager.

Declare event(payload), Signal it, and Await/Subscribe across classes — decoupling checkpoints from the race manager.

📖 Reference & full walkthrough: Custom Events: The CheckpointPassed Event Bus

🧩 Your capstone piece: CheckpointPassedEvent(racer, index) event bus


What you'll learn

So far you've been a listener — subscribing to events that devices broadcast. Now you become the announcer. You'll learn how to:

  • Declare your own event(agent) that carries a player as its payload.
  • Signal it from your game logic when something meaningful happens.
  • Await it in a watcher loop — the custom-event twin of the Subscribe you use on devices.
  • Have the handler show a real HUD widget on the winner's screen with GetPlayerUI[] and AddWidget.

How it works

Devices announce device things. But your game has moments no single device owns: a puzzle solved, a wave cleared, a boss defeated. Those are yours to announce.

Verse gives you a ready-made announcer type: event. You write the kind of data it carries inside parentheses. An event that carries a player (agent) looks like this:

BossDefeatedEvent : event(agent) = event(agent){}

Read it as: "BossDefeatedEvent is an event that carries an agent, starting as a fresh event."

There are two verbs:

  • Signal(Payload) — the moment of "it happened!" Every task waiting on the event wakes up, each receiving the payload.
  • Await() — inside a <suspends> context, pause (for free) until the next Signal, and receive the payload. Wrap it in a loop and you have a handler that runs on every Signal — your own version of the Subscribe you call on button.InteractedWithEvent. (Only device events are listenable, which is what carries Subscribe; your own event(t) pairs Signal with Await.)

To make the payoff visible, our handler reaches the winner's screen through the player-UI API. GetPlayerUI[Player] is fallible (note the []), so we bind it inside an if. Then we build a text_block, drop it in a canvas, and call PlayerUI.AddWidget(...).

Let's build it

A real button drives our own event. Pressing it calls DefeatBoss, which Signals BossDefeatedEvent, which wakes the Await loop that runs OnBossDefeated — so device event → your event → your handler → a widget on screen.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# text_block.DefaultText takes a `message`, not a string — a <localizes> function makes one.
VictoryMsg<localizes>() : message = "VICTORY! Boss Defeated!"

# A concrete device that owns a custom event and broadcasts it.
boss_arena := class<concrete>(creative_device):

    # Real device: players press this to "defeat the boss" for this demo.
    @editable
    BossButton : button_device = button_device{}

    # OUR OWN event. It carries an agent — the player who won.
    BossDefeatedEvent : event(agent) = event(agent){}

    OnBegin<override>()<suspends> : void =
        # Listen to a DEVICE event — device events are `listenable`, so they have Subscribe.
        BossButton.InteractedWithEvent.Subscribe(OnButtonPressed)
        Print("Boss arena ready. Press the button to defeat the boss!")
        # Listen to OUR OWN event: loop on Await. Each Signal wakes us with the payload.
        loop:
            Winner := BossDefeatedEvent.Await()
            OnBossDefeated(Winner)

    # Device handler: the button hands us the agent who pressed it.
    OnButtonPressed(Presser : agent) : void =
        DefeatBoss(Presser)

    # Your game logic: call this whenever the boss's health hits zero.
    DefeatBoss(Winner : agent) : void =
        Print("The boss is down!")
        # Shout it. Every subscriber to BossDefeatedEvent reacts with Winner.
        BossDefeatedEvent.Signal(Winner)

    # Custom-event handler: the winner arrives like a device hands you an agent.
    OnBossDefeated(Winner : agent) : void =
        Print("Victory! Rewarding the winner.")
        # GetPlayerUI[...] is FALLIBLE — bind it in an if before using it.
        if (PlayerUI := GetPlayerUI[player[Winner]]):
            # Build a real text widget and place it on a canvas.
            VictoryText := text_block{DefaultText := VictoryMsg()}
            VictoryCanvas := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.2}, Maximum := vector2{X := 0.5, Y := 0.2}}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := VictoryText
            # Show it on the winner's screen — the whole event loop is now visible.
            PlayerUI.AddWidget(VictoryCanvas)

Try it yourself

  1. Place a Button Device in your level and assign it to the BossButton field in the Details panel.
  2. Push this Verse device, launch a session, and press the button.
  3. Watch the log (The boss is down! then Victory!) and the HUD — the victory text appears on the presser's screen.
  4. Extend it: spawn a second Await loop on BossDefeatedEvent (e.g. one that grants a reward, one that shows the widget) and watch both wake from a single Signal.
  5. Cleanup practice: store the device subscription (ButtonSub := BossButton.InteractedWithEvent.Subscribe(...)) and call ButtonSub.Cancel() after the first press so the boss can only be defeated once.

Recap

  • event(agent) declares your own announcer that carries a player payload.
  • Signal(Winner) wakes every task paused in Await(); a loop: around Await is your handler — it runs once per Signal.
  • Only device events (listenable) have .Subscribe; your own event(t) pairs Signal with Await.
  • text_block.DefaultText takes a message — make one with a <localizes> function, never a raw string.
  • GetPlayerUI[...] is fallible — bind it in an if, then AddWidget a canvas to actually put your announcement on screen.
🔒

Keep going — free

You've read the intro. The rest of this lesson is free for members. Sign in to continue and track your progress.

Sign in free to continue this lesson
Pass the quiz above to chart this quest in your Journal.

Sources

/guides/verse-events-custom
Source

Biloxi Studios original lesson

Guided course

Add this lesson to your free study plan.

🧭 The Keeper’s log

Quest complete? Chart your next heading from the 🐻 North Jungle expedition.

⛵ Take me there