Reference Verse compiles

remove-widget: Clearing UI from the Player's Screen

Every great island moment deserves clean UI — a message that appears when a player reaches the sun-drenched dock, then vanishes the moment they dive into the cove. In Verse, `player_ui` gives you `AddWidget` and `RemoveWidget` to do exactly that: show a `text_block` on arrival, strip it away on departure. This article teaches you the full add-and-remove lifecycle so your HUD never overstays its welcome.

Updated Examples verified on the live UEFN compiler
Watch the Knotremove_widget in ~90 seconds.

Overview

When a player steps onto your clifftop dock or wades into a glowing cove, you often want to flash a contextual message — "Dive in!" or "Treasure found!" — then clear it when they leave. The player_ui class (from /UnrealEngine.com/Temporary/UI) is the main interface for adding and removing widget objects from a single player's screen.

The key methods are:

  • AddWidget(Widget) — pushes a widget onto the player's HUD.
  • RemoveWidget(Widget) — pulls it back off cleanly.

Because widgets are per-player, you must track which widget belongs to which player. A [player]?text_block map is the idiomatic pattern: the player is the key, the optional text block is the value. When you need to remove it, you look up the player's widget and call RemoveWidget.

Reach for this pattern whenever you need:

  • Contextual HUD prompts that appear and disappear based on zone entry/exit.
  • Countdown timers or score popups that clear after a delay.
  • Tutorial hints that dismiss once the player acts.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: A sunny dock sits at the edge of a cove. When a player walks onto a trigger plate on the dock, a cel-shaded "🌊 Dive In!" message appears on their screen. When they walk off the plate (a second trigger), the message is removed. Two trigger_devices — one for entry, one for exit — drive the whole thing.

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

# Localizer helper — Verse requires `message` params, not raw strings
dock_message<localizes>(S : string) : message = "{S}"

dock_hud_device := class(creative_device):

    # Place a trigger on the dock surface (player walks ON)
    @editable DockEntryTrigger : trigger_device = trigger_device{}

    # Place a trigger at the water's edge (player walks OFF / dives)
    @editable DockExitTrigger : trigger_device = trigger_device{}

    # Per-player widget storage: key = player, value = optional text_block
    var MaybeWidgetPerPlayer : [player]?text_block = map{}

    OnBegin<override>()<suspends> : void =
        # Subscribe to both triggers
        DockEntryTrigger.TriggeredEvent.Subscribe(OnDockEntered)
        DockExitTrigger.TriggeredEvent.Subscribe(OnDockExited)

    # Called when a player steps onto the dock plate
    OnDockEntered(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            if (P := player[A]):
                # Build the text block with a drop-shadow for that cel-shaded pop
                MyText := text_block:
                    DefaultText := dock_message("🌊 Dive In!")
                    DefaultShadowColor := NamedColors.Black
                    DefaultShadowOpacity := 0.8
                    DefaultShadowOffset := option{vector2{X := 2.0, Y := 2.0}}

                # Add the widget to this player's UI
                if (UI := GetPlayerUI[P]):
                    UI.AddWidget(MyText)

                # Store it so we can remove it later
                if (set MaybeWidgetPerPlayer[P] = option{MyText}) {}

    # Called when the player leaves the dock / reaches the cove edge
    OnDockExited(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            if (P := player[A]):
                # Look up the stored widget for this player
                if (StoredMaybeWidget := MaybeWidgetPerPlayer[P]):
                    if (W := StoredMaybeWidget?):
                        # Remove it from the player's screen
                        if (UI := GetPlayerUI[P]):
                            UI.RemoveWidget(W)
                    # Clear the map entry
                    if (set MaybeWidgetPerPlayer[P] = false) {}

Line-by-line explanation:

Lines What's happening
dock_message<localizes> Verse's message type requires a <localizes> function — you cannot pass a raw string to DefaultText.
var MaybeWidgetPerPlayer A mutable map keyed by player. The value is ?text_block (an option) so we can store "no widget yet" as false.
DockEntryTrigger.TriggeredEvent.Subscribe(OnDockEntered) Hooks the trigger's event to our handler. The handler receives ?agent.
if (A := MaybeAgent?) Unwraps the optional agent — triggers fire with ?agent, not agent.
if (P := player[A]) Downcasts agent to player; fails gracefully if the agent is an NPC.
text_block: initializer Sets DefaultShadowColor, DefaultShadowOpacity, and DefaultShadowOffset at construction time — these fields are read-only after init; use the Set* methods to change them later.
GetPlayerUI[P] Returns ?player_ui; the if unwraps it.
UI.AddWidget(MyText) Pushes the widget onto this player's HUD.
set MaybeWidgetPerPlayer[P] = option{MyText} Stores the widget reference so OnDockExited can find it.
UI.RemoveWidget(W) The core remove call — cleanly pulls the widget off the player's screen.
set MaybeWidgetPerPlayer[P] = false Clears the map entry (false is the empty option in Verse).

Common patterns

Pattern 1 — Timed auto-dismiss (add, wait, remove)

Show a "Treasure Found!" splash on the clifftop for 3 seconds, then auto-remove it without needing a second trigger.

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

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

timed_splash_device := class(creative_device):

    @editable TreasureTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        TreasureTrigger.TriggeredEvent.Subscribe(OnTreasureFound)

    OnTreasureFound(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            if (P := player[A]):
                if (UI := GetPlayerUI[P]):
                    Splash := text_block:
                        DefaultText := timed_splash_message("💎 Treasure Found!")
                        DefaultShadowColor := NamedColors.Yellow
                        DefaultShadowOpacity := 1.0
                        DefaultShadowOffset := option{vector2{X := 3.0, Y := 3.0}}
                    UI.AddWidget(Splash)
                    # Suspend for 3 seconds, then remove
                    Sleep(3.0)
                    UI.RemoveWidget(Splash)

Note: OnTreasureFound is called synchronously by Subscribe, but Sleep requires a suspending context. Spawn a new async task for the timed removal using spawn{ TimedRemove(UI, Splash) } if you need to keep the handler non-suspending. The pattern above works when the handler itself is allowed to suspend (UEFN runs subscribed handlers in their own fiber).

Pattern 2 — Updating shadow style at runtime with SetShadowOpacity / SetShadowColor

After the widget is on screen, pulse its shadow to draw the player's eye — useful for a cove countdown.

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

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

cove_pulse_device := class(creative_device):

    @editable AlertTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        AlertTrigger.TriggeredEvent.Subscribe(OnAlert)

    OnAlert(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            if (P := player[A]):
                if (UI := GetPlayerUI[P]):
                    Alert := text_block:
                        DefaultText := cove_alert("⚠️ Storm Incoming!")
                        DefaultShadowColor := NamedColors.Red
                        DefaultShadowOpacity := 0.0
                        DefaultShadowOffset := option{vector2{X := 2.0, Y := 2.0}}
                    UI.AddWidget(Alert)

                    # Pulse opacity high → low twice, then remove
                    Alert.SetShadowOpacity(1.0)
                    Sleep(0.5)
                    Alert.SetShadowOpacity(0.2)
                    Sleep(0.5)
                    Alert.SetShadowOpacity(1.0)
                    Sleep(0.5)
                    Alert.SetShadowOpacity(0.2)
                    Sleep(0.5)

                    # Done — remove the widget
                    UI.RemoveWidget(Alert)

Pattern 3 — Reading shadow state before removing

Log the final shadow offset before clearing the widget, useful for debugging layout.

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

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

debug_remove_device := class(creative_device):

    @editable DebugTrigger : trigger_device = trigger_device{}
    var MaybeDebugWidget : ?text_block = false

    OnBegin<override>()<suspends> : void =
        DebugTrigger.TriggeredEvent.Subscribe(OnDebugTrigger)

    OnDebugTrigger(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            if (P := player[A]):
                if (UI := GetPlayerUI[P]):
                    if (Existing := MaybeDebugWidget?):
                        # Read shadow offset before removal
                        MaybeOffset := Existing.GetShadowOffset()
                        # Remove the widget cleanly
                        UI.RemoveWidget(Existing)
                        set MaybeDebugWidget = false
                    else:
                        # First trigger press — add the widget
                        W := text_block:
                            DefaultText := debug_label("Shore Marker")
                            DefaultShadowColor := NamedColors.Blue
                            DefaultShadowOpacity := 0.9
                            DefaultShadowOffset := option{vector2{X := 1.0, Y := 1.0}}
                        UI.AddWidget(W)
                        set MaybeDebugWidget = option{W}

Gotchas

1. DefaultText requires a message, not a string

You cannot write DefaultText := "Dive In!". Verse's text_block.DefaultText is typed message, which is a localization-safe type. Always declare a <localizes> helper:

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

There is no StringToMessage function — the <localizes> function is the only bridge.

2. Default* fields are init-only — use Set* methods after construction

DefaultShadowColor, DefaultShadowOffset, and DefaultShadowOpacity are read at widget construction time only. To change them on a live widget, call SetShadowColor(...), SetShadowOffset(...), or SetShadowOpacity(...). Assigning to DefaultShadowColor after construction has no effect.

3. player_ui is per-player — never share a widget instance across players

AddWidget adds the widget to one player's UI. If two players step on the dock simultaneously, create two separate text_block instances and add each to its respective player_ui. Sharing a single widget instance between players is undefined behavior.

4. Unwrap ?agent before casting to player

Trigger events fire listenable(?agent). Your handler receives (MaybeAgent : ?agent). Always unwrap with if (A := MaybeAgent?) first, then downcast with if (P := player[A]). Skipping either step is a compile or runtime failure.

5. RemoveWidget on an already-removed widget is safe but track state anyway

Calling RemoveWidget on a widget that was never added, or was already removed, does not crash — but it does nothing. Tracking your map/option variable keeps your logic clean and prevents double-add bugs (adding the same widget twice stacks it).

6. GetPlayerUI returns an option — always unwrap it

GetPlayerUI[P] is a failable expression. Wrap every call in if (UI := GetPlayerUI[P]): or you will get a compile error about using a failable expression in a non-failable context.

Build your own lesson with remove_widget

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 →