ReferenceVerse

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.

Updated Compile verifiedExamples verified on the live UEFN compiler
Watch the Knot — 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 := 5 beats a bare 5 scattered 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 →