Reference Unreal Engine compiles

canvas: Building a Cove Welcome Screen

The sun is up over the lagoon, palm shadows on the deck, and the moment a player spawns onto your pirate cove you want a big cel-shaded welcome banner floating in the corner. The canvas widget is how you place UI anywhere on the screen — this article shows you how to build, position, and animate widgets on a canvas in Verse.

Updated Examples verified on the live UEFN compiler

Overview

A canvas is a container widget. It doesn't draw anything itself — instead it holds other widgets (text, images, buttons) and lets you position each one anywhere on the screen using canvas slots. When a canvas sits at the top of the UI hierarchy, it represents the whole screen: (0.0, 0.0) is the top-left corner, (1.0, 1.0) is the bottom-right.

Reach for a canvas whenever you want free-floating, precisely-placed HUD elements: a welcome banner in the top-right when a player lands on your cove, a stack of objective text pinned to the left edge, a countdown timer centered at the top. Anything that isn't a simple vertical/horizontal list wants a canvas.

The flow is always the same:

  1. Get the player's player_ui with GetPlayerUI(Player).
  2. Build a canvas whose Slots array holds canvas_slot structs, each wrapping a child widget with an anchor + offset.
  3. Call PlayerUI.AddWidget(Canvas) to push it onto the screen.

Because player_ui is per-player, you show UI to one player at a time — perfect for that personal "Welcome to Sun-Kissed Cove" moment.

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

Walkthrough

Imagine a bright lagoon spawn pad. When a player steps on a trigger at the end of the dock, we build a canvas with a welcome banner pinned to the top and an objective line under it, then push it to that player's screen.

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

# A localized-message helper: message params never take raw strings.
WelcomeText<localizes>(S : string) : message = "{S}"

cove_welcome := class(creative_device):

    # The dock trigger the player steps on when they arrive.
    @editable
    DockTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        # When someone steps on the dock plate, greet them.
        DockTrigger.TriggeredEvent.Subscribe(OnPlayerArrived)

    # listenable(?agent) hands us (Agent : ?agent) — unwrap it.
    OnPlayerArrived(Agent : ?agent) : void =
        if (A := Agent?):
            if (Player := player[A]):
                ShowWelcome(Player)

    ShowWelcome(Player : player) : void =
        # GetPlayerUI fails if the player has no UI — guard with if.
        if (PlayerUI := GetPlayerUI[Player]):
            # A big banner and a smaller objective line.
            Banner := text_block{DefaultText := WelcomeText("WELCOME TO SUN-KISSED COVE")}
            Objective := text_block{DefaultText := WelcomeText("Loot the pirate ship before the tide comes in!")}

            # Build a canvas: two slots, anchored to the top of the screen.
            HUD := canvas:
                Slots := array:
                    canvas_slot:
                        # Anchor the pivot near the top-center.
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.05}, Maximum := vector2{X := 0.5, Y := 0.05}}
                        Alignment := vector2{X := 0.5, Y := 0.0}
                        Offsets := margin{Left := 0.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        SizeToContent := true
                        Widget := Banner
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.12}, Maximum := vector2{X := 0.5, Y := 0.12}}
                        Alignment := vector2{X := 0.5, Y := 0.0}
                        Offsets := margin{Left := 0.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        SizeToContent := true
                        Widget := Objective

            # Push the whole canvas onto this player's screen.
            PlayerUI.AddWidget(HUD)

Line by line:

  • WelcomeText<localizes>(...) — the text_block.DefaultText field is a message, which is localized. You cannot hand it a bare string; this helper wraps one. There is no StringToMessage.
  • @editable DockTrigger — the trigger is a placed device, so it MUST be an editable field. A bare Trigger.Method() fails with 'Unknown identifier'.
  • DockTrigger.TriggeredEvent.Subscribe(OnPlayerArrived) — we wire the event in OnBegin; the handler is a method at class scope.
  • OnPlayerArrived(Agent : ?agent) — the trigger event hands us an optional agent. if (A := Agent?) unwraps it, then player[A] narrows the agent to a player.
  • GetPlayerUI[Player] — returns that player's UI or fails, so we call it inside an if.
  • The canvas literal fills its Slots array with two canvas_slot structs. Each slot's Anchors picks the screen-relative pivot (0.5 = horizontal center), Alignment sets which point of the widget lands on that anchor, and Widget is the actual content.
  • PlayerUI.AddWidget(HUD) — this is the call that makes the canvas appear. Nothing shows until you add it.

Common patterns

Removing a widget from the canvas

The canvas exposes RemoveWidget(Widget). Keep a reference to the widget you added so you can pull it back off — perfect for a banner that fades out when the tide countdown ends.

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

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

cove_hint := class(creative_device):

    @editable
    HideButton : button_device = button_device{}

    # We hold onto the canvas and the widget so we can remove it later.
    var MaybeCanvas : ?canvas = false
    var MaybeHint : ?widget = false

    OnBegin<override>()<suspends> : void =
        HideButton.InteractedWithEvent.Subscribe(OnHidePressed)

    OnHidePressed(Agent : agent) : void =
        if (Player := player[Agent], PlayerUI := GetPlayerUI[Player]):
            Hint := text_block{DefaultText := HintText("Follow the gulls to the cove!")}
            HUD := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.4}, Maximum := vector2{X := 0.0, Y := 0.4}}
                        Alignment := vector2{X := 0.0, Y := 0.0}
                        Offsets := margin{Left := 40.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        SizeToContent := true
                        Widget := Hint
            PlayerUI.AddWidget(HUD)
            set MaybeCanvas = option{HUD}
            set MaybeHint = option{Hint}

    # Later, take the hint back off the canvas.
    ClearHint() : void =
        if (C := MaybeCanvas?, W := MaybeHint?):
            C.RemoveWidget(W)

Joining messages for a stacked objective board

Use Join to build one combined line from several message pieces, then drop it in a single left-anchored slot — like a mission board nailed to the pirate ship's mast.

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

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

cove_board := class(creative_device):

    @editable
    BoardTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        BoardTrigger.TriggeredEvent.Subscribe(OnRead)

    OnRead(Agent : ?agent) : void =
        if (A := Agent?, Player := player[A], PlayerUI := GetPlayerUI[Player]):
            Lines := array{Line("1. Grab the map"), Line("2. Sail past the reef"), Line("3. Dig at the palm")}
            Combined := Join(Lines, Line("\n"))
            Board := text_block{DefaultText := Combined}
            HUD := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.3}, Maximum := vector2{X := 0.0, Y := 0.3}}
                        Alignment := vector2{X := 0.0, Y := 0.0}
                        Offsets := margin{Left := 60.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        SizeToContent := true
                        Widget := Board
            PlayerUI.AddWidget(HUD)

Gotchas

  • The canvas draws nothing on its own. It's a container. If your screen is blank, check that (a) each canvas_slot has a real Widget assigned, and (b) you actually called PlayerUI.AddWidget(Canvas).
  • GetPlayerUI can fail. It's a <decides> function — always call it inside an if (PlayerUI := GetPlayerUI[Player]). Bots and mid-transition players may have no UI.
  • Slots is init-only. The Slots array is read when the canvas is built and is not touched by AddWidget/RemoveWidget afterward. To change content at runtime, use the canvas's AddWidget(canvas_slot) / RemoveWidget(widget) methods rather than editing Slots.
  • Messages are localized, not strings. text_block.DefaultText wants a message. Wrap raw strings with a <localizes> helper; there is no StringToMessage.
  • Anchors vs Alignment vs Offsets. Anchors picks the screen point (0..1). Alignment picks which point of the widget sits on that anchor (0.5,0.5 = center the widget on it). Offsets nudges in pixels. Mixing these up is the #1 reason a banner ends up off-screen.
  • player_ui is per-player. Adding a canvas shows it to exactly one player. To show the same banner to everyone, loop over all players and add a fresh canvas to each — you cannot reuse one widget instance across UIs.

Guides & scripts that use canvas

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

Build your own lesson with canvas

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 →