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>(...)— thebutton'sDefaultTextis amessage, which must be a localized value. This tiny helper wraps a plain string into one. There is noStringToMessage.@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. Theif (UI := ...)unwraps the success case.button{DefaultText := ...}— we construct a widget in code.CastButton.OnClick.Subscribe(OnCastClicked)—OnClickis alistenable(widget_message); the handler receives awidget_message, not an agent.UI.AddWidget(CastButton)— this is the moment the button appears on that player's screen.FishingTimer.Start()— a realtimer_devicemethod, called from the click handler.OnFishingDone(MaybeAgent : ?agent)— the timer'sSuccessEventis alistenable(?agent), so we unwrap withif (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
GetPlayerUIcan fail. It's a<decides>function — call it with square brackets inside anif:if (UI := GetPlayerUI[Player]):. Round-bracketGetPlayerUI(Player)won't compile as an expression on its own.- Import
/UnrealEngine.com/Temporary/UI. Without it,GetPlayerUI,player_ui,button,canvas, andwidget_messageall report 'Unknown identifier'. - Button text is a
message, not a string. Use a<localizes>helper (DefaultText := MyLabel("Cast Off!")). There is noStringToMessage. OnClickhands you awidget_message, NOT an agent. Don't try to unwrap it withAgent?. If you need the clicking player, readMsg.Player(aplayer) — that field exists on the message payload.- Timer events give
?agent.SuccessEvent,FailureEvent, andStartUrgencyModeEventarelistenable(?agent). Always unwrap withif (Agent := MaybeAgent?):before passing the agent toComplete(Agent)orPause(Agent). AddWidgetonly adds — it doesn't remove.player_ui.AddWidgethas no matching remove. To take a widget away, put it inside acanvasand usecanvas.RemoveWidget(Widget).- You must hold placed devices in
@editablefields. A bareFishingTimer.Start()whereFishingTimerisn't a field won't resolve — declare it as a field and wire it in the editor.