Canvas Layout: The Rally HUD Panel
In this lesson you'll learn to
- βCompose a canvas with anchored slots for lap, boost meter, and position.
Compose a canvas with anchored slots for lap, boost meter, and position.
π Reference & full walkthrough: Canvas Layout: The Rally HUD Panel
π Builds on: lesson 31 widget code (extends the same ui module)
π§© Your capstone piece: Full rally HUD: laps + boosts + race position in one canvas
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
floatcomponents.
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β twotrigger_devices you wire up in the editor by clicking the fields on this device. Without the@editablefield a placed device can't be called from Verse.WelcomeText<localizes>β a helper that turns a plain string into amessage. UI text params require a localized value; there is noStringToMessage.var ShownCanvases : [player]canvasβ a map so each player's canvas is remembered independently. That's howRemoveWidgetlater knows which widget to pull.OnBeginsubscribes both plates'TriggeredEventto handler methods. Handlers are methods on the class, subscribed here.BuildCoveCanvasconstructs twotext_blocks, then acanvaswhoseSlotsarray holds twocanvas_slots. Each slot'sAnchorsplace it as a fraction of the screen (0.5 = horizontal center),Offsetsgive a pixel-sized box, andAlignmentcenters the widget on its anchor point.OnArrivedunwraps the optional agent withAgent?, casts toplayer, and grabsGetPlayerUI[Player]inside the sameifβ all three must succeed. ThenAddWidgetdraws the canvas on that one player's screen, and we store it in the map.OnDepartedlooks up the stored canvas and callsRemoveWidgetto 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.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Colors }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
reset_zone_device := class(creative_device):
@editable
ResetPlate : trigger_device = trigger_device{}
@editable
HudMessage : hud_message_device = hud_message_device{}
var Shown : [player]canvas = map{}
OnBegin<override>()<suspends> : void =
ResetPlate.TriggeredEvent.Subscribe(OnReset)
OnReset(Agent : ?agent) : void =
if (A := Agent?, Player := player[A], UI := GetPlayerUI[Player]):
HudMessage.Clear()
if (Old := Shown[Player]):
UI.RemoveWidget(Old)
Gotchas
GetPlayerUIcan fail. It's a<decides>function, so you must call it inside anif (... = GetPlayerUI[Player])(square brackets) or afor/ifconditional block. Calling it like a plain function won't compile.- UI is per-player.
AddWidgetadds 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.
RemoveWidgetneeds the exact widget reference you passed toAddWidget. Store it (a[player]canvasmap or a?canvasvar) at add-time or you'll have no handle to remove. - Message params, not strings.
DefaultTextwants amessage. Use a<localizes>helper likeWelcomeText(S:string):message; passing a bare"..."string fails to typecheck. - Anchors vs. Offsets.
Anchorsare screen fractions (0.0β1.0);Offsetsare 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 (likebutton_widget) or from placed devices (trigger_device,button_device) that you subscribe to.
Keep going β free
You've read the intro. The rest of this lesson is free for members. Sign in to continue and track your progress.
Sign in free to continue this lessonSources
/guides/canvas-layoutBiloxi Studios original lesson
Add this lesson to your free study plan.
π§ The Keeperβs log
Quest complete? Chart your next heading from the π» North Jungle expedition.
β΅ Take me there