Build Live Score Dashboards with Verse Player UI
Tutorial intermediate compiles

Build Live Score Dashboards with Verse Player UI

Updated intermediate Code verified

What you'll learn

  • Subscribe to a trigger_device.TriggeredEvent and react in a handler.
  • Create a text_block widget and set its display text.
  • Reach every player's player_ui safely with the fallible GetPlayerUI[].
  • Call AddWidget / RemoveWidget to keep the dashboard current.
  • Do integer math correctly with Mod[...] (there is no % operator).

How it works

The dashboard is a feedback loop: an event fires, state changes, and the UI is rebuilt to reflect it. We keep a CurrentScore counter and, on each trigger, recompute a displayed value. To avoid the removed % operator we use Mod[A, B] inside a failure context.

The key correctness point is GetPlayerUI. Its signature is GetPlayerUI(Player:player)<transacts><decides>:player_ui — it is a fallible function, so it is called with square brackets inside an if condition: if (UI := GetPlayerUI[Player]). Calling it with () as a statement would not compile. Once we have the player_ui, AddWidget(Widget:widget):void is an ordinary call.

We also track the widgets we have shown per player so we can remove the previous label before adding the new one, keeping each player's screen tidy instead of stacking labels.

Let's build it

Wire a Trigger device to the EndGameTrigger editable field. Each time the trigger fires, the score increments and every player gets a refreshed dashboard label.

using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Simulation }
using { /Verse.org/Colors }
using { /Fortnite.com/UI }

# Live post-match score dashboard driven by a trigger event.
score_dashboard := class<concrete>(creative_device):

    # Link a Trigger device here in the Details panel.
    @editable EndGameTrigger : trigger_device = trigger_device{}

    # Running score, mutated on every trigger.
    var CurrentScore : int = 0

    # Remembers the widget currently shown to each player so we can replace it.
    var ActiveLabels : [player]text_block = map{}

    OnBegin<override>()<suspends>: void =
        # React to the trigger firing (game-end simulation).
        EndGameTrigger.TriggeredEvent.Subscribe(OnMatchEnd)

    # TriggeredEvent sends an optional agent; we ignore it and update all players.
    OnMatchEnd(MaybeAgent:?agent): void =
        set CurrentScore += 1

        # Displayed value wraps 0..9 using Mod (VC_IntMod(no, operator) exists in Verse).
        var Shown : int = CurrentScore
        if (Wrapped := Mod[CurrentScore, 10]):
            set Shown = Wrapped

        # Rebuild the dashboard for every connected player.
        for (Player : GetPlayspace().GetPlayers()):
            if (UI := GetPlayerUI[Player]):
                # Remove the previous label for this player, if any.
                if (Old := ActiveLabels[Player]):
                    UI.RemoveWidget(Old)

                # Build and populate a fresh text widget.
                Label : text_block = text_block{}
                Label.SetText(StringToMessage("Score: {Shown}"))
                UI.AddWidget(Label)
                if (set ActiveLabels[Player] = Label) {}

        Print("Dashboard updated: Score {CurrentScore}")

    # Helper: convert a string literal into the message type SetText expects.
    StringToMessage<localizes>(Value:string): message = "{Value}"

# Verse Cortex (VISL-6): total integer modulo — Verse has no `%` operator.
# Wraps the native fallible Mod[] so it can be used in any context.
VC_IntMod(A:int, B:int):int =
    if (R := Mod[A, B]):
        R
    else:
        0
# Verse Cortex (VISL-6): total integer division — Verse `/` on ints is
# fallible and yields a rational; this returns a plain int (floor), 0 when B<=0.
VC_IntDiv(A:int, B:int):int =
    if (B > 0, R := Mod[A, B], Q := Floor[(A - R) * 1.0 / (B * 1.0)]):
        Q
    else:
        0```

## Try it yourself

1. **Position it**: add the widget through a `canvas` slot instead of the default `AddWidget` to place it precisely on screen.
2. **Hide instead of remove**: call `Label.SetVisibility(...)` with a `widget_visibility` value to toggle the dashboard without rebuilding it.
3. **Per-player metrics**: track a separate score per player in another `[player]int` map and show each player their own value.

## Recap

You wired a trigger event to a live UI update, computed a wrapped metric with `Mod`, and pushed refreshed `text_block` widgets to each player's `player_ui`. The crucial detail is that `GetPlayerUI` is `<decides>`, so it must be called with `[]` inside a failure context  with `AddWidget`/`RemoveWidget` doing the actual on-screen work.

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 Data-Driven UI Widgets and Post-Match State Management in Verse 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