canvas: Free-Positioned UI for a Sunny Cove HUD
Tutorialintermediatecompiles

canvas: Free-Positioned UI for a Sunny Cove HUD

Updated intermediateDevicesCode verified

Overview

A canvas is a container widget that lets you position child widgets at arbitrary coordinates on the screen. Where a stack or grid decides layout for you, a canvas hands you full 2D control: you place each child in a canvas_slot, give it an anchor and an offset, and it draws exactly there.

Reach for a canvas whenever your on-screen art needs precise placement — a banner pinned top-center, a coin counter tucked in a corner, a Welcome to the Cove! splash floating over the lagoon. The canvas itself never fires events and has no methods you call directly (it's just the container). The real work is: build the canvas, drop widgets into its slots, then hand the whole thing to a player's player_ui with AddWidget. To take it back down, you call RemoveWidget.

The key players in this workflow:

  • canvas — the container you fill with slots.
  • GetPlayerUI(Player) — grabs the per-player UI layer (it can fail, so it's a <decides> call).
  • player_ui.AddWidget / RemoveWidget — show and hide your canvas on one player's screen.

API Reference

canvas

Canvas is a container widget that allows for arbitrary positioning of widgets in the canvas' slots.

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

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

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):

vector3

3-dimensional vector with float components.

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

vector3<native><public> := struct<concrete><computes><persistable><uht_comparable>:

Walkthrough

Here's the sunny scenario: a trigger_device sits on the wooden dock at the edge of a cove. When a player steps onto it, we build a canvas holding a welcome banner and a small hint, and pin it to that player's screen. Step off (a second trigger) and we tear it back down.

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

cove_hud_device := class(creative_device):

    # The dock plate the player steps on.
    @editable
    ArrivalPlate : trigger_device = trigger_device{}

    # A second plate at the far end that clears the HUD.
    @editable
    DeparturePlate : trigger_device = trigger_device{}

    # Localized text helper — message params never take raw strings.
    WelcomeText<localizes>(S : string) : message = "{S}"

    # Remember which canvas we gave each player, so we can remove it later.
    var ShownCanvases : [player]canvas = map{}

    OnBegin<override>()<suspends> : void =
        ArrivalPlate.TriggeredEvent.Subscribe(OnArrived)
        DeparturePlate.TriggeredEvent.Subscribe(OnDeparted)

    # Builds a filled canvas for one player.
    BuildCoveCanvas() : canvas =
        Banner := text_block:
            DefaultText := WelcomeText("Welcome to the Cove!")
            DefaultTextColor := color{ R := 1.0, G := 0.9, B := 0.4 }
        Hint := text_block:
            DefaultText := WelcomeText("Grab a board and hit the waves")
            DefaultTextColor := color{ R := 1.0, G := 1.0, B := 1.0 }
        canvas:
            Slots := array:
                canvas_slot:
                    Anchors := anchors{ Minimum := vector2{ X := 0.5, Y := 0.15 }, Maximum := vector2{ X := 0.5, Y := 0.15 } }
                    Offsets := margin{ Left := -200.0, Top := 0.0, Right := 200.0, Bottom := 60.0 }
                    Alignment := vector2{ X := 0.5, Y := 0.5 }
                    Widget := Banner
                canvas_slot:
                    Anchors := anchors{ Minimum := vector2{ X := 0.5, Y := 0.25 }, Maximum := vector2{ X := 0.5, Y := 0.25 } }
                    Offsets := margin{ Left := -200.0, Top := 0.0, Right := 200.0, Bottom := 40.0 }
                    Alignment := vector2{ X := 0.5, Y := 0.5 }
                    Widget := Hint

    OnArrived(Agent : ?agent) : void =
        if (A := Agent?, Player := player[A], UI := GetPlayerUI[Player]):
            NewCanvas := BuildCoveCanvas()
            UI.AddWidget(NewCanvas)
            if (set ShownCanvases[Player] = NewCanvas) {}

    OnDeparted(Agent : ?agent) : void =
        if (A := Agent?, Player := player[A], UI := GetPlayerUI[Player]):
            if (Old := ShownCanvases[Player]):
                UI.RemoveWidget(Old)

Line by line:

  • @editable ArrivalPlate / DeparturePlate — two trigger_devices you wire up in the editor by clicking the fields on this device. Without the @editable field a placed device can't be called from Verse.
  • WelcomeText<localizes> — a helper that turns a plain string into a message. UI text params require a localized value; there is no StringToMessage.
  • var ShownCanvases : [player]canvas — a map so each player's canvas is remembered independently. That's how RemoveWidget later knows which widget to pull.
  • OnBegin subscribes both plates' TriggeredEvent to handler methods. Handlers are methods on the class, subscribed here.
  • BuildCoveCanvas constructs two text_blocks, then a canvas whose Slots array holds two canvas_slots. Each slot's Anchors place it as a fraction of the screen (0.5 = horizontal center), Offsets give a pixel-sized box, and Alignment centers the widget on its anchor point.
  • OnArrived unwraps the optional agent with Agent?, casts to player, and grabs GetPlayerUI[Player] inside the same if — all three must succeed. Then AddWidget draws the canvas on that one player's screen, and we store it in the map.
  • OnDeparted looks up the stored canvas and calls RemoveWidget to clear it.

Common patterns

Pattern 1 — Pin a single label to a screen corner. A minimal canvas with one slot anchored to the top-left, perfect for a lagoon coin counter.

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

corner_label_device := class(creative_device):

    @editable
    ShowButton : button_device = button_device{}

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

    OnBegin<override>()<suspends> : void =
        ShowButton.InteractedWithEvent.Subscribe(OnPressed)

    OnPressed(Agent : agent) : void =
        if (Player := player[Agent], UI := GetPlayerUI[Player]):
            Coins := text_block:
                DefaultText := LabelText("Shells: 0")
                DefaultTextColor := color{ R := 0.2, G := 0.8, B := 1.0 }
            Corner := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{ Minimum := vector2{ X := 0.0, Y := 0.0 }, Maximum := vector2{ X := 0.0, Y := 0.0 } }
                        Offsets := margin{ Left := 40.0, Top := 40.0, Right := 220.0, Bottom := 80.0 }
                        Widget := Coins
            UI.AddWidget(Corner)

Pattern 2 — Hide the HUD without removing it. Instead of RemoveWidget, keep the canvas mounted and toggle SetVisibility — cheaper when you'll show it again soon.

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

toggle_hud_device := class(creative_device):

    @editable
    ToggleButton : button_device = button_device{}

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

    var MaybeCanvas : ?canvas = false

    OnBegin<override>()<suspends> : void =
        ToggleButton.InteractedWithEvent.Subscribe(OnToggle)

    OnToggle(Agent : agent) : void =
        if (Player := player[Agent], UI := GetPlayerUI[Player]):
            if (Existing := MaybeCanvas?):
                # Already built — flip visibility.
                Existing.SetVisibility(widget_visibility.Hidden)
            else:
                Tip := text_block:
                    DefaultText := TipText("Watch the tide!")
                    DefaultTextColor := color{ R := 1.0, G := 1.0, B := 1.0 }
                Built := canvas:
                    Slots := array:
                        canvas_slot:
                            Anchors := anchors{ Minimum := vector2{ X := 0.5, Y := 0.85 }, Maximum := vector2{ X := 0.5, Y := 0.85 } }
                            Offsets := margin{ Left := -150.0, Top := 0.0, Right := 150.0, Bottom := 50.0 }
                            Alignment := vector2{ X := 0.5, Y := 0.5 }
                            Widget := Tip
                UI.AddWidget(Built)
                set MaybeCanvas = option{ Built }

Pattern 3 — Clear a placed device's state alongside the HUD. Many gameplay devices expose Clear() to reset. Here a trigger both wipes a hud_message_device's state and pulls the canvas.

Gotchas

  • GetPlayerUI can fail. It's a <decides> function, so you must call it inside an if (... = GetPlayerUI[Player]) (square brackets) or a for/if conditional block. Calling it like a plain function won't compile.
  • UI is per-player. AddWidget adds to one player's screen. There is no global screen — loop over the players you care about and add a fresh canvas to each. Sharing one widget instance across players is not supported; build one per player.
  • Track what you added. RemoveWidget needs the exact widget reference you passed to AddWidget. Store it (a [player]canvas map or a ?canvas var) at add-time or you'll have no handle to remove.
  • Message params, not strings. DefaultText wants a message. Use a <localizes> helper like WelcomeText(S:string):message; passing a bare "..." string fails to typecheck.
  • Anchors vs. Offsets. Anchors are screen fractions (0.0–1.0); Offsets are pixels relative to those anchors. If your widget vanishes off-screen, check that your anchor fraction and pixel offset agree — a 0.5 center anchor still needs a negative Left offset to appear centered.
  • The canvas has no events. Don't look for a canvas.OnClicked — the canvas is only a container. Interaction comes from the child widgets (like button_widget) or from placed devices (trigger_device, button_device) that you subscribe to.

Get the complete code — free

You've read the full walkthrough. The complete, copy-paste-ready Verse solution is free for members — sign in to unlock it.

Free with your BrainDead.TV / BrainDeadGuild Discord account. The walkthrough above is always free.

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 Building screen UI with the canvas container widget 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