Constants in Verse: Lock In Your Values
A constant is a named value that never changes while your island is running — perfect for things like spawn heights, score thresholds, or timer durations that you set once and rely on everywhere. Verse enforces this guarantee at compile time, so you can't accidentally overwrite a wave count or a damage multiplier mid-game. This article walks you through every form of constant declaration using a sunny clifftop cove scenario where constants drive the whole experience.
constants in ~90 seconds.Overview
A constant in Verse is a binding between an identifier and a value that cannot be reassigned after it is initialized. Unlike a variable (declared with var), a constant has no set expression — the compiler rejects any attempt to change it.
Reach for constants when:
- A value is fixed for the entire session (max players, a door's unlock score, a tide-timer duration).
- You want a single source of truth so changing one number updates every place it is used.
- You are naming a magic number to make code self-documenting (
MaxWaves := 5beats a bare5scattered across your file).
Constants are the backbone of readable, maintainable Verse. Every @editable field on a creative_device is also a constant — the editor sets it once before OnBegin runs and it never changes.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: A clifftop cove island. When the round starts a countdown timer ticks down from a constant number of seconds. If a player reaches the dock trigger before time runs out, a prop-mover swings the harbour gate open. Constants control every magic number — the countdown, the score needed, the gate's open delay — so a designer only edits one place.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# ─── Localized helper ────────────────────────────────────────────────────────
# Constants of type `message` must be declared at module scope with <localizes>.
RoundStartMsg<localizes>(S : string) : message = "{S}"
TimeUpMsg<localizes>(S : string) : message = "{S}"
GateOpenMsg<localizes>(S : string) : message = "{S}"
clifftop_cove_manager := class(creative_device):
# ── Editable device references ────────────────────────────────────────────
# These are constants too — the editor initialises them once.
@editable DockTrigger : trigger_device = trigger_device{}
@editable HarbourGate : prop_mover_device = prop_mover_device{}
@editable HUD : hud_message_device = hud_message_device{}
# ── Named game constants ──────────────────────────────────────────────────
# Explicit type annotation: Identifier : type = expression
RoundDurationSeconds : float = 90.0 # how long the round lasts
GateOpenDelaySecs : float = 1.5 # pause before the gate swings open
WarnAtSeconds : float = 30.0 # when to flash the low-time warning
# Integer constant — note: no auto-conversion to float, keep types consistent
MaxActivations : int = 3 # gate can only open this many times per round
# ── Mutable state (needs var because it changes) ──────────────────────────
var ActivationCount : int = 0
# ── Entry point ───────────────────────────────────────────────────────────
OnBegin<override>()<suspends> : void =
# Inferred-type constant inside a function — Identifier := expression
StartMessage := RoundStartMsg("The cove is open! Reach the dock in time!")
HUD.SetText(StartMessage)
HUD.Show()
# Subscribe to the dock trigger
DockTrigger.TriggeredEvent.Subscribe(OnDockReached)
# Countdown using our named constant — easy to tweak in one place
Sleep(WarnAtSeconds)
WarnMessage := RoundStartMsg("30 seconds left — hurry!")
HUD.SetText(WarnMessage)
# Wait out the remainder
RemainingSeconds : float = RoundDurationSeconds - WarnAtSeconds
Sleep(RemainingSeconds)
# Round over
EndMessage := TimeUpMsg("Time's up! The harbour gate is sealed.")
HUD.SetText(EndMessage)
HarbourGate.Disable()
# ── Event handler ─────────────────────────────────────────────────────────
OnDockReached(Agent : ?agent) : void =
set ActivationCount = ActivationCount + 1
if (ActivationCount <= MaxActivations):
# Local inferred constant — captures the current count as a label
CountLabel := GateOpenMsg("Gate opening!")
HUD.SetText(CountLabel)
# Spawn an async task so we can Sleep without blocking the handler
spawn { OpenGateAfterDelay() }
OpenGateAfterDelay()<suspends> : void =
Sleep(GateOpenDelaySecs) # uses the named constant
HarbourGate.Begin()```
### Line-by-line highlights
| Line(s) | What it teaches |
|---|---|
| `RoundDurationSeconds : float = 90.0` | **Explicit-type constant** at class scope — identifier, colon, type, equals, value. |
| `MaxActivations : int = 3` | Integer constant — Verse won't silently cast this to `float`, so arithmetic stays in the right type. |
| `StartMessage := RoundStartMsg(...)` | **Inferred-type constant** inside a function — the compiler deduces `message` from the right-hand expression. |
| `RemainingSeconds : float = RoundDurationSeconds - WarnAtSeconds` | Constant derived from other constants — still immutable, computed once. |
| `Sleep(GateOpenDelaySecs)` | Using a named constant instead of a magic number makes the intent obvious. |
## Common patterns
### Pattern 1 — Module-scope constants as shared config
Declare constants **outside** any class when multiple devices on the island need the same value. They act like a config file at the top of your script.
```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Module-scope constants — visible to every class in this file
CoveSpawnHeight : float = 512.0
MaxRoundPlayers : int = 8
TideIntervalSecs : float = 20.0
tide_controller := class(creative_device):
@editable TideTimer : timer_device = timer_device{}
OnBegin<override>()<suspends> : void =
# Use the shared module-scope constant directly
TideTimer.SetTime(TideIntervalSecs)
TideTimer.Start()
# MaxRoundPlayers is available here too — no need to re-declare it
LogMessage := "Tide cycle started. Max players: {MaxRoundPlayers}"
Print(LogMessage)
Pattern 2 — Constant arrays as lookup tables
A constant can hold any Verse value, including an array. Useful for fixed wave compositions, spawn-point indices, or reward tiers.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
RewardMsg<localizes>(S : string) : message = "{S}"
cove_reward_manager := class(creative_device):
@editable Granters : []item_granter_device = array{}
# Constant array — the tier thresholds never change at runtime
ScoreThresholds : []int = array{ 10, 25, 50, 100 }
OnBegin<override>()<suspends> : void =
# Read the constant array to decide which granter to activate
FirstThreshold : int = ScoreThresholds[0] # inferred constant, value = 10
Print("Bronze tier unlocks at score: {FirstThreshold}")
# Iterate the constant array
for (Threshold : ScoreThresholds):
Print("Threshold: {Threshold}")
# Activate the first granter as a demo
if (G := Granters[0]):
G.GrantItem(false)
Pattern 3 — Constants as named boolean flags
Boolean constants document intent and make if conditions readable.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
StatusMsg<localizes>(S : string) : message = "{S}"
cove_gate_flags := class(creative_device):
@editable Gate : prop_mover_device = prop_mover_device{}
@editable HUD : hud_message_device = hud_message_device{}
# Named boolean constants — self-documenting, never accidentally flipped
GateStartsOpen : logic = false
FriendlyFireOn : logic = false
ShowDebugOverlay : logic = false
OnBegin<override>()<suspends> : void =
if (GateStartsOpen?):
Gate.Activate()
HUD.SetText(StatusMsg("Harbour gate is open from the start."))
else:
HUD.SetText(StatusMsg("Harbour gate is sealed. Earn your way in."))
if (ShowDebugOverlay?):
Print("Debug overlay enabled")
Gotchas
1. Constants cannot be reassigned — use var for anything that changes
If you write MyCount : int = 0 and later try set MyCount = 1, the compiler errors. Only var fields support set. Constants are a promise to the compiler that the value is fixed.
2. No implicit int ↔ float conversion
Verse is strict about numeric types. RoundDurationSeconds : float = 90 will fail — you must write 90.0. Similarly, you cannot pass an int constant where a float is expected without an explicit conversion.
3. message parameters need <localizes> — not raw strings
A hud_message_device.SetText() takes a message, not a string. Declare a helper at module scope:
MyMsg<localizes>(S : string) : message = "{S}"
Then pass MyMsg("Hello cove!"). There is no StringToMessage function.
4. Inferred constants (:=) are only available inside functions
At class scope you must use the explicit form Identifier : type = expression. The short := form is only valid inside a function body or OnBegin.
5. @editable fields are constants, not variables
Fields marked @editable are set by the editor before runtime and behave exactly like constants — you cannot set them. This is a feature: it guarantees the designer's configuration is stable throughout the session.
6. Constants are evaluated once at initialization
A constant like RemainingSeconds : float = RoundDurationSeconds - WarnAtSeconds is computed when the binding is first evaluated, not lazily. If you need a value that depends on runtime state, use a var or compute it inside a function.
Build your own lesson with constants
Generate a personalized, compile-checked lesson grounded in this exact reference.
Build a lesson →