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. Signalit from your game logic when something meaningful happens.Awaitit in a watcher loop — the custom-event twin of theSubscribeyou use on devices.- Have the handler show a real HUD widget on the winner's screen with
GetPlayerUI[]andAddWidget.
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 nextSignal, and receive the payload. Wrap it in aloopand you have a handler that runs on every Signal — your own version of theSubscribeyou call onbutton.InteractedWithEvent. (Only device events arelistenable, which is what carriesSubscribe; your ownevent(t)pairsSignalwithAwait.)
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
- Place a Button Device in your level and assign it to the
BossButtonfield in the Details panel. - Push this Verse device, launch a session, and press the button.
- Watch the log (
The boss is down!thenVictory!) and the HUD — the victory text appears on the presser's screen. - Extend it:
spawna secondAwaitloop onBossDefeatedEvent(e.g. one that grants a reward, one that shows the widget) and watch both wake from a singleSignal. - Cleanup practice: store the device subscription (
ButtonSub := BossButton.InteractedWithEvent.Subscribe(...)) and callButtonSub.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 inAwait(); aloop:aroundAwaitis your handler — it runs once per Signal.- Only device events (
listenable) have.Subscribe; your ownevent(t)pairsSignalwithAwait. text_block.DefaultTexttakes amessage— make one with a<localizes>function, never a raw string.GetPlayerUI[...]is fallible — bind it in anif, thenAddWidgetacanvasto 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 lessonSources
/guides/verse-events-customBiloxi Studios original lesson
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