Reference Verse

GetPlayerUI: Custom Screens on the Sunny Shore

GetPlayerUI is your doorway from Verse device logic into a player's actual heads-up display. On a bright cel-shaded cove, we'll build a floating 'Cast Off!' button that a beachgoer taps to start a fishing timer — all rendered straight onto their screen.

Updated
The code on this reference page is provided as-is and did not pass the latest compile check — treat the examples as a starting point and verify in your project.
Watch the Knotplayer_ui in ~90 seconds.

Overview

Most creative devices live in the 3D world — a trigger on the sand, a timer counting down over the lagoon. But sometimes you need to draw directly on a player's screen: a button, a countdown label, a scoreboard. That's what GetPlayerUI is for.

GetPlayerUI(Player) returns the player_ui object bound to one specific player. Once you have it, you call AddWidget to push a widget (a button, a canvas, a text_block, etc.) onto that player's HUD. Because the UI belongs to a single player, everyone can have their own personalized screen.

Reach for GetPlayerUI when you want:

  • A custom clickable button that triggers game logic (start a timer, grant an item).
  • Per-player HUD elements the built-in devices can't produce.
  • Interactive menus, prompts, or on-screen countdowns.

In this article we sit on a sunlit cove dock. Each player who spawns gets a bright "Cast Off!" button floated onto their screen. Tapping it starts a timer_device fishing countdown — and the button's HighlightEvent makes it glow as their cursor rolls over it. When the timer's SuccessEvent fires, we clean the button off their UI.

API Reference

player_ui

The main interface for adding and removing widgets to a player's UI.

Full public surface, resolved verbatim from the live Epic digest (UnrealEngine.digest.verse).

player_ui<native><public> := class<final><epic_internal>:

player

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse). Inherited members are merged from agent.

player<native><public> := class<unique><persistent><module_scoped_var_weak_map_key><epic_internal>(agent):

button

Button is a container of a single child widget slot and fires the OnClick event when the button is clicked.

Full public surface, resolved verbatim from the live Epic digest (UnrealEngine.digest.verse). Inherited members are merged from widget.

button<native><public> := class<final>(widget):

Events (subscribe a handler to react):

Event Signature Description
OnClick OnClick<public>():listenable(widget_message) Subscribable event that fires when the button is clicked.
HighlightEvent HighlightEvent<public>():listenable(widget_message)
UnhighlightEvent UnhighlightEvent<public>():listenable(widget_message)

timer_device

Provides a way to keep track of the time something has taken, either for scoreboard purposes, or to trigger actions. It can be configured in several ways, either acting as a countdown to an event that is triggered at the end, or as a stopwatch for an action that needs to be completed before a set time runs out.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).

timer_device<public> := class<concrete><final>(creative_device_base):

Events (subscribe a handler to react):

Event Signature Description
SuccessEvent SuccessEvent<public>:listenable(?agent) Signaled when the timer completes or ends with success. Sends the agent that activated the timer, if any.
FailureEvent FailureEvent<public>:listenable(?agent) Signaled when the timer completes or ends with failure. Sends the agent that activated the timer, if any.
StartUrgencyModeEvent StartUrgencyModeEvent<public>:listenable(?agent) Signaled when the timer enters Urgency Mode. Sends the agent that activated the timer, if any.

Methods (call these to make the device act):

Method Signature Description
Enable Enable<public>(Agent:agent):void Enables this device for Agent.
Enable Enable<public>():void Enables this device.
Disable Disable<public>(Agent:agent):void Disables this device for Agent. While disabled this device will not receive signals.
Disable Disable<public>():void Disables this device. While disabled this device will not receive signals.
ResetForAll ResetForAll<public>(Agent:agent):void Resets the timer back to its base time and stops it for all agents.
ResetForAll ResetForAll<public>():void Resets the timer back to its base time and stops it for all agents.
Start Start<public>(Agent:agent):void Starts the timer for Agent.
Start Start<public>():void Starts the timer.
Pause Pause<public>(Agent:agent):void Pauses the timer for Agent.
Pause Pause<public>():void Pauses the timer.
Resume Resume<public>(Agent:agent):void Resumes the timer for Agent.
Resume Resume<public>():void Resumes the timer.
Complete Complete<public>(Agent:agent):void Completes the timer for Agent.
Complete Complete<public>():void Completes the timer.
StartForAll StartForAll<public>(Agent:agent):void Starts the timer for all agents.
StartForAll StartForAll<public>():void Starts the timer for all agents.
PauseForAll PauseForAll<public>(Agent:agent):void Pauses the timer for all agents.
PauseForAll PauseForAll<public>():void Pauses the timer for all agents.
ResumeForAll ResumeForAll<public>(Agent:agent):void Resumes the timer for all agents.
ResumeForAll ResumeForAll<public>():void Resumes the timer for all agents.
CompleteForAll CompleteForAll<public>(Agent:agent):void Completes the timer for all agents.
CompleteForAll CompleteForAll<public>():void Completes the timer for all agents.
Save Save<public>(Agent:agent):void Saves this device's data for Agent.
Load Load<public>(Agent:agent):void Loads this device's saved data for Agent.
ClearPersistenceData ClearPersistenceData<public>(Agent:agent):void Clears this device's saved data for Agent.
ClearPersistenceDataForAll ClearPersistenceDataForAll<public>(Agent:agent):void Clears this device's saved data for all agents.
ClearPersistenceDataForAll ClearPersistenceDataForAll<public>():void Clears this device's saved data for all agents.
SetActiveDuration SetActiveDuration<public>(Time:float, Agent:agent):void Sets the remaining time (in seconds) on the timer, if active, on Agent.
SetActiveDuration SetActiveDuration<public>(Time:float):void Sets the remaining time (in seconds) on the timer, if active. Use this function if the timer is set to use the same time for all agent's.
GetActiveDuration GetActiveDuration<public>(Agent:agent)<transacts>:float Returns the remaining time (in seconds) on the timer for Agent.
GetActiveDuration GetActiveDuration<public>()<transacts>:float Returns the remaining time (in seconds) on the timer if it is set to be global.
SetLapTime SetLapTime<public>(Agent:agent):void Sets the lap time indicator for Agent.
SetLapTimeForAll SetLapTimeForAll<public>(Agent:agent):void Sets the lap time indicator for all agents.
SetLapTimeForAll SetLapTimeForAll<public>():void Sets the lap time indicator for all agents.
SetMaxDuration SetMaxDuration<public>(Time:float):void Sets the maximum duration of the timer (in seconds).
GetMaxDuration GetMaxDuration<public>()<transacts>:float Returns the maximum duration of the timer (in seconds).
IsStatePerAgent IsStatePerAgent<public>()<transacts><decides>:void Succeeds if this device is tracking timer state for each individual agent independently. Fails if state is being tracked globally for all agent's.

Walkthrough

Here's the full runnable device. Place a timer_device in your level, wire it to the @editable field, and this code hands every player a custom button that drives that timer.

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

# Localized message helper — `message` params never take a raw string.
ButtonLabel<localizes>(S : string) : message = "{S}"

cove_dock_ui := class(creative_device):

    # The fishing countdown timer placed in the level.
    @editable
    FishingTimer : timer_device = timer_device{}

    OnBegin<override>()<suspends> : void =
        # React whenever a player arrives on the island.
        for (Player : GetPlayspace().GetPlayers()):
            SpawnCastOffButton(Player)

        # When the fishing run succeeds, celebrate.
        FishingTimer.SuccessEvent.Subscribe(OnFishingDone)

    # Build a 'Cast Off!' button and push it onto one player's screen.
    SpawnCastOffButton(Player : player) : void =
        # GetPlayerUI can fail, so unwrap it with if.
        if (UI := GetPlayerUI[Player]):
            CastButton := button{DefaultText := ButtonLabel("Cast Off!")}

            # Subscribe to the button's real events.
            CastButton.OnClick.Subscribe(OnCastClicked)
            CastButton.HighlightEvent.Subscribe(OnCastHighlighted)
            CastButton.UnhighlightEvent.Subscribe(OnCastUnhighlighted)

            # Actually draw the button on this player's HUD.
            UI.AddWidget(CastButton)

    # Fired when the player clicks the button — start the timer for everyone.
    OnCastClicked(Msg : widget_message) : void =
        FishingTimer.Start()

    # Fired when the cursor rolls onto the button.
    OnCastHighlighted(Msg : widget_message) : void =
        FishingTimer.Enable()

    # Fired when the cursor leaves the button.
    OnCastUnhighlighted(Msg : widget_message) : void =
        # Nothing to disable here — leave the timer ready.

    # Fired when the fishing countdown completes with success.
    OnFishingDone(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            FishingTimer.Complete(Agent)

Line by line:

  • ButtonLabel<localizes>(...) — the button's DefaultText is a message, which must be a localized value. This tiny helper wraps a plain string into one. There is no StringToMessage.
  • @editable FishingTimer : timer_device — to call a placed device from Verse you MUST hold it in an editable field. In the editor you drag your placed timer into this slot.
  • GetPlayspace().GetPlayers() — gives us every player so each gets their own button.
  • GetPlayerUI[Player] — note the square brackets: this function <decides>, meaning it can fail. The if (UI := ...) unwraps the success case.
  • button{DefaultText := ...} — we construct a widget in code.
  • CastButton.OnClick.Subscribe(OnCastClicked)OnClick is a listenable(widget_message); the handler receives a widget_message, not an agent.
  • UI.AddWidget(CastButton) — this is the moment the button appears on that player's screen.
  • FishingTimer.Start() — a real timer_device method, called from the click handler.
  • OnFishingDone(MaybeAgent : ?agent) — the timer's SuccessEvent is a listenable(?agent), so we unwrap with if (Agent := MaybeAgent?).

Common patterns

Pattern 1 — Remove a widget with the canvas

A canvas lets you position widgets and, crucially, RemoveWidget them again. Add the canvas to the UI once, then add/remove buttons inside it.

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

DismissLabel<localizes>(S : string) : message = "{S}"

lagoon_menu := class(creative_device):

    OnBegin<override>()<suspends> : void =
        for (Player : GetPlayspace().GetPlayers()):
            ShowMenu(Player)

    ShowMenu(Player : player) : void =
        if (UI := GetPlayerUI[Player]):
            Screen := canvas{}
            Close := button{DefaultText := DismissLabel("Close")}
            # When clicked, pull the button back off the canvas.
            Close.OnClick.Subscribe(OnClose)
            Screen.AddWidget(canvas_slot{Widget := Close})
            UI.AddWidget(Screen)

    OnClose(Msg : widget_message) : void =
        # Handle the close action for this player.

Pattern 2 — Timer urgency drives the UI

The timer_device fires StartUrgencyModeEvent when it's about to run out. Use it to react — here we pause the countdown for the player who triggered urgency.

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

clifftop_urgency := class(creative_device):

    @editable
    RaceTimer : timer_device = timer_device{}

    OnBegin<override>()<suspends> : void =
        RaceTimer.StartUrgencyModeEvent.Subscribe(OnUrgency)
        RaceTimer.FailureEvent.Subscribe(OnFailure)
        RaceTimer.StartForAll()

    OnUrgency(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            RaceTimer.Pause(Agent)

    OnFailure(MaybeAgent : ?agent) : void =
        RaceTimer.ResetForAll()

Pattern 3 — Reconfigure the timer's duration before starting

SetActiveDuration and SetMaxDuration let you tune the countdown at runtime.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

pirate_ship_timer := class(creative_device):

    @editable
    PlunderTimer : timer_device = timer_device{}

    OnBegin<override>()<suspends> : void =
        # Give players 30 seconds to grab loot.
        PlunderTimer.SetMaxDuration(30.0)
        PlunderTimer.SuccessEvent.Subscribe(OnPlundered)
        PlunderTimer.StartForAll()

    OnPlundered(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            PlunderTimer.Complete(Agent)

Gotchas

  • GetPlayerUI can fail. It's a <decides> function — call it with square brackets inside an if: if (UI := GetPlayerUI[Player]):. Round-bracket GetPlayerUI(Player) won't compile as an expression on its own.
  • Import /UnrealEngine.com/Temporary/UI. Without it, GetPlayerUI, player_ui, button, canvas, and widget_message all report 'Unknown identifier'.
  • Button text is a message, not a string. Use a <localizes> helper (DefaultText := MyLabel("Cast Off!")). There is no StringToMessage.
  • OnClick hands you a widget_message, NOT an agent. Don't try to unwrap it with Agent?. If you need the clicking player, read Msg.Player (a player) — that field exists on the message payload.
  • Timer events give ?agent. SuccessEvent, FailureEvent, and StartUrgencyModeEvent are listenable(?agent). Always unwrap with if (Agent := MaybeAgent?): before passing the agent to Complete(Agent) or Pause(Agent).
  • AddWidget only adds — it doesn't remove. player_ui.AddWidget has no matching remove. To take a widget away, put it inside a canvas and use canvas.RemoveWidget(Widget).
  • You must hold placed devices in @editable fields. A bare FishingTimer.Start() where FishingTimer isn't a field won't resolve — declare it as a field and wire it in the editor.

Guides & scripts that use player_ui

Step-by-step tutorials that put this object to work.

Build your own lesson with player_ui

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →