Verse Race: First Trigger Wins, Announce It on the HUD
Tutorialintermediatecompiles

Verse Race: First Trigger Wins, Announce It on the HUD

Updated intermediateCode verified

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:

  1. Two trigger_device references — each exposes TriggeredEvent, a listenable whose Await() is <suspends> and resumes when the trigger fires. Because Await() returns an optional agent (?agent), we ignore that value and instead have each branch end in a string label.
  2. A race — we place each branch in a block: so the last expression of the winning branch (a string) becomes the race result. The result type is the common type of every branch, so all branches must yield a string.
  3. Player UI — for each player we call GetPlayerUI[Player] (fallible, so it lives inside an if), build a canvas holding a text_block, and AddWidget it. When a trigger wins we update the text with SetText.

Two correctness details drive this code:

  • GetPlayerUI[Player] is fallible (note the []), so it MUST appear inside an if (...) condition — never as a bare statement.
  • text_block text fields (DefaultText, SetText) want a message, not a string — 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 a string.
  • Give each player their OWN text_block instead of a shared Banner so 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.

Comments

    Sign in to vote, comment, or suggest an edit.Sign in