Overview
A Verse expression is the smallest unit of code that produces a value when evaluated. The revolutionary part: everything in Verse is an expression. if/else, blocks delimited by indentation, arithmetic, function calls — they all hand back a value the moment they finish running.
This matters on your island because:
- You can assign the result of an
if/elsedirectly to a variable instead of mutating a variable inside each branch. - You can pass an
if/elseas a function argument without a temporary. - Multi-line blocks evaluate to the value of their last expression, so you can chain setup logic and still capture a result.
- Failable expressions (those that can fail as well as succeed) compose cleanly with
if— the wholeifblock fails rather than crashing.
Reach for expression-oriented style whenever you find yourself writing:
var Result : int = 0
if (Condition):
set Result = 42
else:
set Result = 7
That pattern is valid Verse, but the expression form is shorter, clearer, and avoids a mutable variable entirely.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: Clifftop Cove — Timed Bonus Chest
You're building a sunny clifftop cove level. A glittering chest sits on the dock. When a player steps on a pressure plate, the game awards them a score bonus — double points if they arrive within the first 30 seconds of the round, normal points after that. A HUD message reflects which tier they earned.
The entire bonus-tier decision is handled with a single if/else expression assigned to a constant. No mutable temporaries, no duplicated grant calls.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
# Localized message helper — message is a special type, not a plain string.
EarnedMessage<localizes>(S : string) : message = "{S}"
clifftop_bonus_device := class(creative_device):
# Drag a trigger_device onto the dock pressure plate in the editor.
@editable
DockPlate : trigger_device = trigger_device{}
# Drag a hud_message_device into the editor for on-screen feedback.
@editable
HudDisplay : hud_message_device = hud_message_device{}
# Drag an item_granter_device into the editor (configured to grant score/gold).
@editable
BonusGranter : item_granter_device = item_granter_device{}
# How many seconds count as "early arrival" for the double-bonus window.
@editable
EarlyWindowSeconds : float = 30.0
# Tracks elapsed time since the round began.
var ElapsedSeconds : float = 0.0
OnBegin<override>()<suspends> : void =
# Subscribe to the plate — handler fires when any player steps on it.
DockPlate.TriggeredEvent.Subscribe(OnPlateTriggered)
# Tick elapsed time every second using a loop expression.
# The loop itself is an expression (it evaluates to false when it exits,
# but here we run it for its side-effects).
loop:
Sleep(1.0)
set ElapsedSeconds += 1.0
# Event handler — listenable(?agent) passes an optional agent.
OnPlateTriggered(MaybeAgent : ?agent) : void =
# Unwrap the optional agent before using it.
if (A := MaybeAgent?):
# ---- THE CORE LESSON ----
# if/else is an EXPRESSION. It evaluates to one of two strings.
# We assign that value directly — no mutable temp needed.
BonusTier := if (ElapsedSeconds <= EarlyWindowSeconds):
"DOUBLE BONUS — Early Arrival!"
else:
"Bonus Chest Claimed!"
# Pass the expression result straight into the localized helper.
HudDisplay.SetText(EarnedMessage(BonusTier))
# Grant the item — the granter handles the actual reward.
BonusGranter.GrantItem(A)
Line-by-line highlights
| Line | What it teaches |
|---|---|
BonusTier := if (...): "DOUBLE BONUS..." else: "Bonus Chest Claimed!" |
if/else is an expression. Both branches are string literals; the whole thing evaluates to one string value. |
HudDisplay.SetText(EarnedMessage(BonusTier)) |
The result is used immediately as a function argument — no intermediate variable. |
set ElapsedSeconds += 1.0 |
+= is also an expression (it evaluates to the new value), used here for its side-effect. |
if (A := MaybeAgent?) |
Failable expression: MaybeAgent? either succeeds (unwrapping the agent) or fails (skipping the block). |
loop: Sleep(1.0) ... |
loop is an expression too — it evaluates to false when it exits via break. |
Common Patterns
Pattern 1 — Block expressions evaluate to their last line
A multi-line indented block is itself an expression. Its value is whatever the last expression in the block evaluates to. Use this to do setup work and still capture a result.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
block_value_device := class(creative_device):
@editable
ScoreTrigger : trigger_device = trigger_device{}
@editable
RewardGranter : item_granter_device = item_granter_device{}
# Player's current streak — affects the multiplier.
var Streak : int = 0
OnBegin<override>()<suspends> : void =
ScoreTrigger.TriggeredEvent.Subscribe(OnScored)
# Keep the device alive.
Sleep(Inf)
OnScored(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
# A block expression: the last line (the int) is the value.
# All the lines before it are setup side-effects.
Multiplier :=
set Streak += 1
# Give a bigger multiplier every 5 hits.
if (Mod[Streak, 5] = 0):
3 # triple points on every 5th hit
else:
1
# Multiplier is now an int we can use.
if (Multiplier > 1):
# Grant a bonus item on multiplier rounds.
RewardGranter.GrantItem(A)
Key idea: The block assigned to Multiplier first increments Streak (side-effect), then evaluates the if/else expression as its last line. That integer — 3 or 1 — becomes the value of the whole block.
Pattern 2 — Failable expressions compose with if
Some expressions can fail (like array indexing or option unwrapping). Because they're expressions, you compose them inside if — the whole block is skipped on failure instead of crashing.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
shore_podium_device := class(creative_device):
# Three podium triggers along the shore — first, second, third place.
@editable
PodiumTriggers : []trigger_device = array{}
@editable
PrizeGranter : item_granter_device = item_granter_device{}
OnBegin<override>()<suspends> : void =
# Subscribe to each podium trigger.
for (T : PodiumTriggers):
T.TriggeredEvent.Subscribe(OnPodiumReached)
Sleep(Inf)
OnPodiumReached(MaybeAgent : ?agent) : void =
# Two failable expressions composed in one if:
# 1. MaybeAgent? — unwrap the option (fails if no agent)
# 2. PodiumTriggers[0] — array index (fails if array is empty)
# If EITHER fails, the whole block is skipped safely.
if:
A := MaybeAgent?
FirstPodium := PodiumTriggers[0]
then:
# We only reach here if both succeeded.
PrizeGranter.GrantItem(A)
Key idea: MaybeAgent? and PodiumTriggers[0] are both failable expressions. Composing them inside a single if means the body only runs when all of them succeed — no nested null-checks, no try/catch.
Pattern 3 — Arithmetic and comparison expressions as values
Arithmetic operators produce values just like if/else does. You can nest them, store them, or pass them directly.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
CoveScoreMessage<localizes>(S : string) : message = "{S}"
cove_score_device := class(creative_device):
@editable
CollectTrigger : trigger_device = trigger_device{}
@editable
ScoreHud : hud_message_device = hud_message_device{}
var BaseScore : int = 100
var BonusScore : int = 50
OnBegin<override>()<suspends> : void =
CollectTrigger.TriggeredEvent.Subscribe(OnCollect)
Sleep(Inf)
OnCollect(MaybeAgent : ?agent) : void =
if (MaybeAgent?):
# Arithmetic expression used directly as an argument.
# BaseScore + BonusScore evaluates to an int value inline.
TotalScore := BaseScore + BonusScore
# Comparison expression: > evaluates to logic (true/false),
# used directly inside if without a temp bool variable.
ResultLabel := if (TotalScore > 100):
"Cove Champion!"
else:
"Good run!"
ScoreHud.SetText(CoveScoreMessage(ResultLabel))
Key idea: BaseScore + BonusScore is an expression that evaluates to int. TotalScore > 100 is an expression that evaluates to a logic value. Neither needs a named temporary — both can be used exactly where their value is needed.
Gotchas
1. Both branches of if/else must produce the same type
When you use if/else as an expression to assign a value, both branches must evaluate to the same type. Mixing int and string in the two branches is a compile error.
# ❌ WRONG — type mismatch
Result := if (Condition): 42 else: "forty-two"
# ✅ CORRECT — both branches are string
Result := if (Condition): "42" else: "7"
2. A standalone if (no else) is failable, not a value expression
An if without an else can fail — it doesn't always produce a value. You cannot assign it to a plain variable. Use it inside another if block or add an else branch.
# ❌ WRONG — if without else can fail, can't assign to non-option
Label := if (Score > 10): "High"
# ✅ CORRECT — always has a value
Label := if (Score > 10): "High" else: "Low"
3. message is not string — use a localizes helper
Device methods like hud_message_device.SetText() take a message, not a string. You cannot pass a raw string literal or use a fictional StringToMessage. Declare a <localizes> helper function:
MyMsg<localizes>(S : string) : message = "{S}"
# Then: HudDisplay.SetText(MyMsg("Hello cove!"))
4. int and float do not auto-convert
Verse never silently converts between numeric types. If your expression produces an int and you need a float, use an explicit conversion or write a float literal (30.0 not 30).
# ❌ WRONG — ElapsedSeconds is float, EarlyWindowSeconds must also be float
var EarlyWindowSeconds : int = 30
if (ElapsedSeconds <= EarlyWindowSeconds): ...
# ✅ CORRECT
var EarlyWindowSeconds : float = 30.0
5. The value of a loop is false, not the last iteration's value
loop is an expression, but it evaluates to false (logic) when it exits via break. Don't try to capture a meaningful value from a loop expression — use a var that you set inside the loop instead.
6. Block expressions: only the last line is the value
If you want a block to produce a value, make sure the value-producing expression is the last line. Any expression that isn't last is evaluated purely for side-effects, and its value is discarded.