Overview
In Verse, many values are optional (?T). When you try to unwrap an empty option without a fallback, your expression simply fails — and in a UI context that usually means a blank screen or a silent no-op. The default-value-fallback pattern solves this with two complementary tools:
- The
oroperator —MaybeValue? or FallbackValue— unwraps an option and substitutes a safe default when it is empty. Default*fields on widgets —color_block,texture_block, and friends exposeDefaultColor,DefaultOpacity,DefaultDesiredSize, etc. These are the values the widget is initialized with; you set them at construction time and then useSetColor/SetOpacityat runtime to change them.
Together these two tools let you write UI widgets that always display something sensible — a fallback colour, a safe size — even when your game data hasn't loaded yet or a player slot is empty. Reach for this pattern whenever you build HUD elements that depend on runtime state that might be absent: health bars, score panels, zone indicators on a clifftop, tide-level meters on a dock.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: You're building a sunny cove island where up to four players race to collect pearls. A small HUD panel shows each player's "zone colour" — Coral for zone 1, Aquamarine for zone 2, etc. — as a solid color_block. If a player slot is empty the panel falls back to a dim grey so the layout never breaks.
The device below:
- Builds a
color_blockwith safeDefault*initialization values. - Uses
SetColorto apply a zone colour at runtime. - Uses
SetOpacityto dim the block when no player occupies that slot. - Demonstrates the
orfallback operator to pick a colour from an optional map lookup.
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Colors/NamedColors }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
# Localised helper — color_block doesn't need message, but kept for future text widgets
cove_hud_device := class(creative_device):
# ----------------------------------------------------------------
# The zone colours for up to 4 player slots on the cove island.
# Slot 0 = Coral (zone 1), Slot 1 = Aquamarine (zone 2),
# Slot 2 = CornflowerBlue (zone 3), Slot 3 = Crimson (zone 4).
# ----------------------------------------------------------------
# Fallback colour shown when a slot has no player.
FallbackColor : color = color{R:=0.15, G:=0.15, B:=0.15}
# Dimmed opacity for empty slots; full opacity for occupied slots.
DimOpacity : type{_X:float where 0.0 <= _X, _X <= 1.0} = 0.25
FullOpacity : type{_X:float where 0.0 <= _X, _X <= 1.0} = 1.0
# The four zone colours indexed 0-3.
ZoneColors : []color = array:
NamedColors.Coral
NamedColors.Aquamarine
NamedColors.CornflowerBlue
NamedColors.Crimson
# We keep a reference to each block so we can update it later.
var SlotBlocks : []color_block = array{}
OnBegin<override>()<suspends> : void =
# Build one color_block per slot.
var Built : []color_block = array{}
for (SlotIndex := 0..3):
# --- DEFAULT-VALUE FALLBACK: pick zone colour or grey ---
# ZoneColors[SlotIndex] can fail if the array is shorter
# than expected; `or FallbackColor` guarantees a safe value.
SlotColor := if (C := ZoneColors[SlotIndex]) then C else FallbackColor
Block := color_block:
# Default* fields set the INITIAL state at construction.
DefaultColor := SlotColor
DefaultOpacity := DimOpacity # start dimmed
DefaultDesiredSize := vector2{X:=64.0, Y:=64.0}
set Built = Built + array{Block}
set SlotBlocks = Built
# Simulate slot 0 and slot 2 being occupied at game start.
ActivateSlot(0)
ActivateSlot(2)
# After 5 seconds a third player arrives at the dock — slot 1 lights up.
Sleep(5.0)
ActivateSlot(1)
# After another 5 seconds slot 3 activates (player swims in from the cove).
Sleep(5.0)
ActivateSlot(3)
# Called when a player occupies a slot.
# Uses SetColor + SetOpacity — the runtime mutators (not Default* fields).
ActivateSlot(SlotIndex : int) : void =
if (Block := SlotBlocks[SlotIndex]):
# Pick the zone colour; fall back to FallbackColor with `or`.
ZoneColor := ZoneColors[SlotIndex] or FallbackColor
Block.SetColor(ZoneColor)
Block.SetOpacity(FullOpacity)
# Called when a player leaves — dims the slot back to grey.
DeactivateSlot(SlotIndex : int) : void =
if (Block := SlotBlocks[SlotIndex]):
Block.SetColor(FallbackColor)
Block.SetOpacity(DimOpacity)```
### Line-by-line highlights
| Lines | What's happening |
|---|---|
| `DefaultColor := SlotColor` | Sets the widget's **initial** colour at construction. This field is only read once — use `SetColor` for all later changes. |
| `DefaultOpacity := DimOpacity` | All slots start dimmed (`0.25`). The `Default*` field bakes this in at init time. |
| `ZoneColors[SlotIndex] or FallbackColor` | Classic `or` fallback: if the array lookup fails (index out of range), the expression yields `FallbackColor` instead of failing. |
| `Block.SetColor(ZoneColor)` | Runtime colour change — `Default*` fields are ignored after construction; always call the setter. |
| `Block.SetOpacity(FullOpacity)` | Brings the slot to full brightness when a player arrives. |
## Common patterns
### Pattern 1 — `GetColor` + `or` to read-then-modify
Read the current colour, nudge it slightly, and write it back. The `or` guards against any unexpected failure path.
```verse
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Colors/NamedColors }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
# Pulses a dock-warning color_block between its current colour and red.
dock_warning_pulse_device := class(creative_device):
# The color_block that acts as a warning light on the dock.
WarningBlock : color_block = color_block:
DefaultColor := NamedColors.Aquamarine
DefaultOpacity := 1.0
DefaultDesiredSize := vector2{X:=32.0, Y:=32.0}
OnBegin<override>()<suspends> : void =
# Store the original colour so we can restore it after each pulse.
OriginalColor := WarningBlock.GetColor()
# Pulse red three times to warn players the tide is coming in.
var PulseCount : int = 0
loop:
if (PulseCount >= 3):
break
WarningBlock.SetColor(NamedColors.Crimson)
Sleep(0.4)
WarningBlock.SetColor(OriginalColor)
Sleep(0.4)
set PulseCount = PulseCount + 1
Pattern 2 — GetDesiredSize + SetDesiredSize for responsive layout
Read the current size, double it for a "pearl collected" pop animation, then restore it.
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Colors/NamedColors }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
# Pops a pearl-counter block larger when a pearl is collected.
pearl_pop_device := class(creative_device):
PearlBlock : color_block = color_block:
DefaultColor := NamedColors.Cornsilk
DefaultOpacity := 1.0
DefaultDesiredSize := vector2{X:=48.0, Y:=48.0}
OnBegin<override>()<suspends> : void =
# Simulate three pearl pickups at the cove shore.
var Collected : int = 0
loop:
if (Collected >= 3):
break
Sleep(2.0)
PopBlock()
set Collected = Collected + 1
PopBlock() : void =
# Read current size, scale up, then restore — all via the getter/setter pair.
CurrentSize := PearlBlock.GetDesiredSize()
BigSize := vector2{X:=CurrentSize.X * 2.0, Y:=CurrentSize.Y * 2.0}
PearlBlock.SetDesiredSize(BigSize)
# In a real device you'd Sleep then restore; shown inline for clarity.
PearlBlock.SetDesiredSize(CurrentSize)
Pattern 3 — or operator on ?int game data
The or fallback isn't limited to widgets — it's the universal Verse tool for any optional value. Here it guards a pearl-count lookup.
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Colors/NamedColors }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
# Reads a player's pearl count from a map; falls back to 0 if not found.
pearl_score_reader_device := class(creative_device):
# Simulated pearl counts keyed by slot index.
PearlCounts : [int]int = map{0 => 12, 2 => 7}
# A colour block whose opacity reflects the score fraction (0-20 pearls max).
ScoreBlock : color_block = color_block:
DefaultColor := NamedColors.Aquamarine
DefaultOpacity := 0.1
DefaultDesiredSize := vector2{X:=80.0, Y:=16.0}
OnBegin<override>()<suspends> : void =
# Slot 1 has no entry — `or 0` provides the safe fallback.
SlotOnePearls := PearlCounts[1] or 0
SlotZeroPearls := PearlCounts[0] or 0
# Drive opacity from score fraction; clamp to [0.1, 1.0].
MaxPearls : float = 20.0
Fraction : float = SlotZeroPearls / MaxPearls
SafeFraction := if (Fraction > 1.0) then 1.0 else if (Fraction < 0.1) then 0.1 else Fraction
ScoreBlock.SetOpacity(SafeFraction)
# Slot 1 would show 0.1 opacity (the fallback minimum) — never crashes.
_ = SlotOnePearls # acknowledged; would drive a second block in a full device
Gotchas
Default* fields are init-only — always use setters at runtime
DefaultColor, DefaultOpacity, and DefaultDesiredSize are read once when the widget is constructed. Mutating them after construction has no effect. Use SetColor, SetOpacity, and SetDesiredSize for every runtime change. Think of Default* as the widget's "blueprint" and the setters as its "remote control".
or only works on failable expressions
The or operator requires the left-hand side to be a failable expression (one that can fail, like an option unwrap ? or an array index). Writing SomeNonFailableValue or Fallback is a compile error. Wrap your risky lookup first: ZoneColors[Index] or FallbackColor — the array index is failable, so or is valid.
DefaultOpacity is a constrained float, not a plain float
The type is type {_X:float where 0.000000 <= _X, _X <= 1.000000}. Passing a literal like 1.0 or 0.25 works fine, but passing a computed float variable requires you to ensure it is in range first — the compiler enforces the constraint. Use an explicit clamp (if (F > 1.0) then 1.0 else ...) before assigning to SetOpacity.
GetColor / GetOpacity return current runtime values, not Default* values
If you call GetColor() before any SetColor() call, you get the value that was set via DefaultColor at construction — which is correct. But if another system has called SetColor in between, GetColor reflects that change, not the original default. Cache your intended defaults in Verse fields if you need to restore them.
int and float do not auto-convert
When computing sizes or opacities from integer game data (pearl counts, scores), you must cast explicitly: SlotZeroPearls / MaxPearls only works if both sides are float. Divide an int pearl count by a float max by writing SlotZeroPearls * 1.0 / MaxPearls or storing the count as float from the start.