
Verse Race: First Trigger Wins, Announce It on the HUD
What you'll learn
You'll learn how the race expression runs two Await() calls concurrently and lets you react to whichever trigger fires first. Just as important, we do something real with the result: build a canvas widget, add it to each player's screen with GetPlayerUI[] and AddWidget, and overwrite a text_block to announce the winner.
How it works
A race block runs two or more async expressions simultaneously. The instant one completes, race returns that expression's value and cancels every other branch — no polling, no manual cancellation. It is the idiomatic way to express "wait for A OR B".
The pieces we need:
- Two
trigger_devicereferences — each exposesTriggeredEvent, a listenable whoseAwait()is<suspends>and resumes when the trigger fires. BecauseAwait()returns an optional agent (?agent), we ignore that value and instead have each branch end in astringlabel. - A
race— we place each branch in ablock:so the last expression of the winning branch (astring) becomes theraceresult. The result type is the common type of every branch, so all branches must yield astring. - Player UI — for each player we call
GetPlayerUI[Player](fallible, so it lives inside anif), build acanvasholding atext_block, andAddWidgetit. When a trigger wins we update the text withSetText.
Two correctness details drive this code:
GetPlayerUI[Player]is fallible (note the[]), so it MUST appear inside anif (...)condition — never as a bare statement.text_blocktext fields (DefaultText,SetText) want amessage, not astringâ so the banner’s default comes from a<localizes>message constant, and the winner label goes through a tiny<localizes>helper that converts the text.
Let's build it
Place two trigger_device actors and bind the @editable fields TriggerA and TriggerB.
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
# DefaultText needs a message (not a string), so define a <localizes> constant.
WaitingLabel<localizes> : message = "Waiting for a trigger..."
race_trigger_demo := class<concrete>(creative_device):
@editable TriggerA : trigger_device = trigger_device{}
@editable TriggerB : trigger_device = trigger_device{}
# Shared banner whose text we overwrite with the race winner.
Banner : text_block = text_block{DefaultText := WaitingLabel}
# text_block.SetText needs a message, so wrap the string here.
StringToMessage<localizes>(Value : string) : message = "{Value}"
OnBegin<override>()<suspends>: void =
# Build one HUD banner per player using the real player-UI API.
for (Player : GetPlayspace().GetPlayers()):
# GetPlayerUI[] is fallible, so it lives inside an if condition.
if (UI := GetPlayerUI[Player]):
Root : canvas = canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.1}, Maximum := vector2{X := 0.5, Y := 0.1}}
Alignment := vector2{X := 0.5, Y := 0.0}
Offsets := margin{Top := 50.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
SizeToContent := true
Widget := Banner
# AddWidget makes the canvas visible on this player's screen.
UI.AddWidget(Root)
# Keep racing so every press is announced.
loop:
# race runs both Awaits at once; the first trigger to fire wins and
# its block's string becomes the race result. The loser is canceled.
Winner : string =
race:
block:
TriggerA.TriggeredEvent.Await()
"Trigger A won the race!"
block:
TriggerB.TriggeredEvent.Await()
"Trigger B won the race!"
Print("{Winner}")
# Overwrite the on-screen banner with the winner.
Banner.SetText(StringToMessage(Winner))
Sleep(0.5) # Brief debounce before re-arming the race.
Try it yourself
- Add a third branch that races a
Sleep(10.0)timeout: if neither trigger fires within ten seconds, set the banner to"Nobody raced in time!". Remember every branch must still end in astring. - Give each player their OWN
text_blockinstead of a sharedBannerso only the player who steps on a trigger sees the result — store them in a map keyed by player. - Swap
Sleep(0.5)for a longer debounce, or remove it, and observe how quickly the race re-arms.
Recap
race runs its sibling async branches concurrently, returns the first to finish, and cancels the rest — a clean structured early-exit. We wrapped each branch in a block: so it produces a string label, captured that as the winner, and drove a real HUD by pairing GetPlayerUI[] (fallible, hence inside if), a canvas with a text_block, and SetText fed by a <localizes> string-to-message helper. That is race doing genuine work end to end.
Check your understanding
Test yourself with an interactive quiz and track your progress + earn XP — free for members.
Turn this into a guided course
Add Implementing Race Expressions for Concurrent Verse Tasks to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.
Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.