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:
- Get the player's
player_uiwithGetPlayerUI(Player). - Build a
canvaswhoseSlotsarray holdscanvas_slotstructs, each wrapping a child widget with an anchor + offset. - 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>(...)— thetext_block.DefaultTextfield is amessage, which is localized. You cannot hand it a bare string; this helper wraps one. There is noStringToMessage.@editable DockTrigger— the trigger is a placed device, so it MUST be an editable field. A bareTrigger.Method()fails with 'Unknown identifier'.DockTrigger.TriggeredEvent.Subscribe(OnPlayerArrived)— we wire the event inOnBegin; the handler is a method at class scope.OnPlayerArrived(Agent : ?agent)— the trigger event hands us an optional agent.if (A := Agent?)unwraps it, thenplayer[A]narrows the agent to a player.GetPlayerUI[Player]— returns that player's UI or fails, so we call it inside anif.- The
canvasliteral fills itsSlotsarray with twocanvas_slotstructs. Each slot'sAnchorspicks the screen-relative pivot (0.5 = horizontal center),Alignmentsets which point of the widget lands on that anchor, andWidgetis 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_slothas a realWidgetassigned, and (b) you actually calledPlayerUI.AddWidget(Canvas). GetPlayerUIcan fail. It's a<decides>function — always call it inside anif (PlayerUI := GetPlayerUI[Player]). Bots and mid-transition players may have no UI.Slotsis init-only. TheSlotsarray is read when the canvas is built and is not touched byAddWidget/RemoveWidgetafterward. To change content at runtime, use the canvas'sAddWidget(canvas_slot)/RemoveWidget(widget)methods rather than editingSlots.- Messages are localized, not strings.
text_block.DefaultTextwants amessage. Wrap raw strings with a<localizes>helper; there is noStringToMessage. - Anchors vs Alignment vs Offsets.
Anchorspicks the screen point (0..1).Alignmentpicks which point of the widget sits on that anchor (0.5,0.5 = center the widget on it).Offsetsnudges 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.