Overview
In Verse, values are immutable by default. If you want a value that can change over time — a score counter, a flag that records whether the vault door is open, a team index — you must declare it with the var keyword. The set expression then updates that variable at runtime.
This matters the moment you build anything stateful:
- A clifftop lighthouse that toggles its beacon each time a player arrives.
- A cove gate that only opens after three players have reached the shore.
- A dock leaderboard that tracks how many fish each player has caught.
Without var and set, your device resets to its initial value every time — making persistent, reactive game logic impossible.
When to reach for it: Any time a value in your device needs to change after OnBegin runs, you need var. If the value never changes, a plain let-style binding (no var) is cleaner and safer.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: A sunny island cove has a pressure plate on the dock. Three players must step onto the plate to raise the harbour gate. A counter tracks arrivals; once it hits three the gate trigger fires and a message is broadcast. We use var to hold the arrival count and set to increment it.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
# Localised announcement helper
HarbourMessage<localizes>(S : string) : message = "{S}"
# dock_gate_controller — place this Verse device on your island,
# then assign the pressure plate and trigger in the Details panel.
dock_gate_controller := class(creative_device):
# The pressure plate on the dock planks.
@editable
DockPlate : trigger_device = trigger_device{}
# A trigger wired to the harbour gate's open animation / barrier device.
@editable
GateTrigger : trigger_device = trigger_device{}
# How many players must step on the plate before the gate opens.
@editable
RequiredPlayers : int = 3
# ── mutable state ──────────────────────────────────────────────
# Tracks how many distinct plate activations have occurred.
var ArrivalCount : int = 0
# Tracks whether the gate has already been opened this round.
var GateOpen : logic = false
# ── lifecycle ──────────────────────────────────────────────────
OnBegin<override>()<suspends> : void =
# Subscribe to the plate's triggered event.
# trigger_device.TriggeredEvent fires with ?agent each activation.
DockPlate.TriggeredEvent.Subscribe(OnPlayerArrived)
# ── event handler ──────────────────────────────────────────────
OnPlayerArrived(MaybeAgent : ?agent) : void =
# Gate already open? Nothing more to do.
if (GateOpen?):
return
# Increment the arrival counter.
set ArrivalCount = ArrivalCount + 1
Print("Dock arrivals: {ArrivalCount} / {RequiredPlayers}")
# Once we hit the required number, open the gate.
if (ArrivalCount >= RequiredPlayers):
set GateOpen = true
GateTrigger.Trigger()
Print("Harbour gate raised! The cove is open.")
Line-by-line explanation:
| Lines | What's happening |
|---|---|
var ArrivalCount : int = 0 |
Declares a mutable integer field. var is required — without it you cannot use set later. |
var GateOpen : logic = false |
A boolean flag. logic is Verse's bool type; false / true are its values. |
DockPlate.TriggeredEvent.Subscribe(OnPlayerArrived) |
Wires the plate's event to our handler. Every step on the plate calls OnPlayerArrived. |
if (GateOpen?): |
The ? suffix is the succeeds-if-true decider for logic. If already open, we bail early. |
set ArrivalCount = ArrivalCount + 1 |
set is the only way to mutate a var. Forgetting set is a compile error. |
if (ArrivalCount >= RequiredPlayers): |
Comparison works on int directly — no casting needed. |
GateTrigger.Trigger() |
Fires the gate trigger, which you wire in UEFN to whatever opens the barrier. |
Common patterns
Pattern 1 — Float var: tracking a rising tide timer
A clifftop alarm sounds when a float accumulator crosses a threshold — useful for timed events like a rising-water mechanic.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
clifftop_tide_alarm := class(creative_device):
@editable
AlarmTrigger : trigger_device = trigger_device{}
# Seconds of "danger" accumulated before the alarm fires.
@editable
DangerThreshold : float = 10.0
# Mutable float accumulator — starts at zero each round.
var DangerSeconds : float = 0.0
var AlarmFired : logic = false
OnBegin<override>()<suspends> : void =
# Tick every second, accumulating danger time.
loop:
Sleep(1.0)
if (not AlarmFired?):
set DangerSeconds = DangerSeconds + 1.0
Print("Danger level: {DangerSeconds}s")
if (DangerSeconds >= DangerThreshold):
set AlarmFired = true
AlarmTrigger.Trigger()
Print("Clifftop alarm! Evacuate the cove!")
Key point: var DangerSeconds : float = 0.0 — you must write the explicit type annotation. var X := 0.0 is not valid Verse syntax; var always needs : Type.
Pattern 2 — Optional var: remembering the last player to reach the shore
Store the most recent agent who triggered the dock plate so you can award them a prize if they were first.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
shore_first_arrival := class(creative_device):
@editable
ShorePlate : trigger_device = trigger_device{}
@editable
PrizeGranter : item_granter_device = item_granter_device{}
# Holds the first agent to reach the shore, or false (no one yet).
var FirstArrival : ?agent = false
OnBegin<override>()<suspends> : void =
ShorePlate.TriggeredEvent.Subscribe(OnShoreReached)
OnShoreReached(MaybeAgent : ?agent) : void =
# Only award the prize if no one has arrived yet.
if (FirstArrival = false):
if (A := MaybeAgent?):
# Record this agent as the first to reach the shore.
set FirstArrival = option{A}
PrizeGranter.GrantItem(A)
Print("First to the shore! Prize granted.")
Key point: var FirstArrival : ?agent = false uses false as the "none" value for an optional. Unwrap with if (A := MaybeAgent?) before passing to APIs that need a concrete agent.
Pattern 3 — Resetting a var: round-restart on the dock
At the end of each round, reset all mutable state so the next round starts fresh.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
dock_round_manager := class(creative_device):
@editable
RoundEndTrigger : trigger_device = trigger_device{}
@editable
DockPlate : trigger_device = trigger_device{}
@editable
GateTrigger : trigger_device = trigger_device{}
var RoundNumber : int = 0
var ArrivalCount : int = 0
var GateOpen : logic = false
OnBegin<override>()<suspends> : void =
DockPlate.TriggeredEvent.Subscribe(OnArrival)
RoundEndTrigger.TriggeredEvent.Subscribe(OnRoundEnd)
OnArrival(MaybeAgent : ?agent) : void =
if (not GateOpen?):
set ArrivalCount = ArrivalCount + 1
if (ArrivalCount >= 3):
set GateOpen = true
GateTrigger.Trigger()
OnRoundEnd(MaybeAgent : ?agent) : void =
# Reset all vars — the next round starts clean.
set RoundNumber = RoundNumber + 1
set ArrivalCount = 0
set GateOpen = false
Print("Round {RoundNumber} starting. Dock gate reset.")
Key point: set works on any var field from any method in the same class. Resetting vars in a round-end handler is the standard pattern for reusable game loops.
Gotchas
1. var requires an explicit type annotation
This fails:
var Count := 0 # ✗ compile error
This works:
var Count : int = 0 # ✓
Verse does not infer types for var declarations.
2. Forgetting set is a compile error
Writing ArrivalCount = ArrivalCount + 1 without set tries to create a new binding, not mutate the field — the compiler rejects it. Always write set ArrivalCount = ....
3. int and float do not auto-convert
var Seconds : float = 0.0 cannot be compared with an int literal directly in some contexts. Keep your types consistent: use 0.0 for floats and 0 for ints throughout.
4. logic uses ? as a decider, not = true
To branch on a logic var, write if (GateOpen?) — not if (GateOpen = true). The latter is an assignment attempt and will not compile as a condition.
5. var fields on a creative_device are per-instance
Each placed copy of your device has its own independent var fields. Two dock_gate_controller devices on the same island do not share ArrivalCount — which is usually what you want, but remember it when debugging multi-device setups.
6. var cannot be @editable
You cannot expose a var field to the UEFN Details panel with @editable. Editable fields are always immutable configuration values. Use a plain (non-var) field for designer-tunable parameters and a separate var for runtime state.
7. Localized text for Print-style messages
If you ever pass text to an API that expects message (not Print), raw strings don't work. Declare a localizes helper:
MyMsg<localizes>(S : string) : message = "{S}"
Then pass MyMsg("Hello cove!"). Print accepts plain strings, but device APIs like notification systems expect message.