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.