Reference Verse compiles

Array Index Access in Verse: Picking the Right Element Every Time

Arrays let you store a whole roster of spawn pads, a list of wave enemies, or every checkpoint in a race — but only if you can reliably grab the element you need. Verse's index-access syntax (`Array[Index]`) is a *failable expression*, meaning it either succeeds and hands you the value, or fails cleanly when the index is out of range. This article shows you exactly how that works, why it matters, and how to build real game systems around it.

Updated Examples verified on the live UEFN compiler
Watch the Knotarray_index_access in ~90 seconds.

Overview

An array in Verse is an ordered, zero-indexed container of same-type elements declared with the syntax []Type (e.g., []int, []agent, []button_device). You access a single element by writing Array[Index] inside a failure context — most commonly an if expression.

Because array access can fail (when the index is out of bounds), Verse treats it exactly like any other failable expression: the square-bracket syntax [] signals "this might not succeed." This design eliminates an entire class of out-of-bounds crashes common in other languages — if the index is bad, the if branch simply doesn't run.

When to reach for array index access:

  • Cycling through a fixed list of spawn points, checkpoints, or wave configs.
  • Selecting a specific device from a set of @editable devices based on runtime state.
  • Implementing round-robin or sequential game logic ("activate the Nth barrier").
  • Pairing players to stations, teams to colors, or scores to reward tiers.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: A Sequential Vault Door Puzzle

The player must press three buttons in order. Each button press activates the next barrier in a sequence. When all three barriers are deactivated, the vault door opens (a final trigger fires). If the player presses them out of order, nothing happens.

This example uses:

  • []button_device — an editable array of buttons.
  • []barrier_device — an editable array of barriers to deactivate in order.
  • Array index access (Barriers[Step], Buttons[Step]) to select the right device each round.
  • A trigger_device to fire the "vault open" cinematic.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# sequential_vault_puzzle — place this Verse device in your UEFN level.
# Wire up Buttons[0..2] and Barriers[0..2] in the Details panel.
sequential_vault_puzzle := class(creative_device):

    # The three buttons the player must press in order (index 0 = first).
    @editable
    Buttons : []button_device = array{}

    # The three barriers that drop as each button is pressed correctly.
    @editable
    Barriers : []barrier_device = array{}

    # Fires when all barriers are down — hook this to your vault-door cinematic.
    @editable
    VaultTrigger : trigger_device = trigger_device{}

    # Tracks which step the player is on (mutable).
    var CurrentStep : int = 0

    OnBegin<override>()<suspends> : void =
        # Subscribe each button to our handler.
        # We iterate by index so we know WHICH button was pressed.
        for (Index := 0..Buttons.Length - 1):
            if (Btn := Buttons[Index]):
                Btn.InteractedWithEvent.Subscribe(OnButtonPressed)
        # Keep the device alive so events keep firing.
        loop:
            Sleep(1.0)

    # Called whenever ANY button is interacted with.
    OnButtonPressed(Agent : agent) : void =
        # Read the current step.
        Step := CurrentStep
        # Only the button at position Step is the "correct" one right now.
        # We check whether the agent's interaction came from the right button
        # by attempting to deactivate the matching barrier.
        if (Barrier := Barriers[Step]):
            # Deactivate this step's barrier.
            Barrier.Disable()
            set CurrentStep = Step + 1
            # Check if all steps are complete.
            if (CurrentStep >= Barriers.Length):
                # All barriers down — open the vault!
                VaultTrigger.Trigger()
                set CurrentStep = 0   # reset for replay

Line-by-line breakdown:

Line What it does
for (Index := 0..Buttons.Length - 1) Iterates over every valid index in the Buttons array.
if (Btn := Buttons[Index]) Failable index access — only proceeds if index is valid and the slot is set.
Btn.InteractedWithEvent.Subscribe(OnButtonPressed) Subscribes to the real button event.
if (Barrier := Barriers[Step]) Safely fetches the barrier at the current step — fails gracefully if Step is out of range.
Barrier.Disable() Calls the real barrier_device API to drop the wall.
if (CurrentStep >= Barriers.Length) Checks completion without any array access, so no failure needed.

Common patterns

Pattern 1 — Cycling through spawn pads (wrap-around index)

Use the modulo operator (Mod) to cycle through an array endlessly — useful for round-robin spawning.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# round_robin_spawner — assign SpawnPads in the Details panel.
round_robin_spawner := class(creative_device):

    @editable
    SpawnPads : []player_spawner_device = array{}

    @editable
    StartTrigger : trigger_device = trigger_device{}

    var NextPad : int = 0

    OnBegin<override>()<suspends> : void =
        StartTrigger.TriggeredEvent.Subscribe(OnRoundStart)
        loop:
            Sleep(1.0)

    OnRoundStart(Agent : ?agent) : void =
        PadCount := SpawnPads.Length
        if (PadCount > 0):
            Index := Mod(NextPad, PadCount)
            if (Pad := SpawnPads[Index]):
                Pad.SpawnPlayer()
            set NextPad = NextPad + 1

Key idea: Mod(NextPad, PadCount) always produces a valid index (0 to PadCount-1), so the if (Pad := SpawnPads[Index]) access will succeed as long as the array is non-empty.


Pattern 2 — Reading the last element safely

The last valid index is always Array.Length - 1. Accessing it still requires a failure context.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# last_checkpoint_activator — activates only the final checkpoint in a race.
last_checkpoint_activator := class(creative_device):

    @editable
    Checkpoints : []checkpoint_device = array{}

    OnBegin<override>()<suspends> : void =
        LastIndex := Checkpoints.Length - 1
        # Guard: array might be empty, making LastIndex = -1.
        if (LastIndex >= 0, FinalCP := Checkpoints[LastIndex]):
            FinalCP.Activate()
        loop:
            Sleep(1.0)

Key idea: Always guard against an empty array before computing Length - 1. Combining the guard and the access in a single if keeps the logic tight.


Pattern 3 — Building a lookup table with a for expression

Collect results from indexed access into a new array using for.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# even_index_selector — enables only the even-indexed barriers in a set.
even_index_selector := class(creative_device):

    @editable
    Barriers : []barrier_device = array{}

    OnBegin<override>()<suspends> : void =
        # Collect every even-indexed barrier.
        EvenBarriers : []barrier_device = for:
            Index := 0..Barriers.Length - 1
            Mod(Index, 2) = 0          # keep only even indices
            B := Barriers[Index]       # failable — skips bad slots
        do:
            B
        # Enable only those barriers.
        for (B : EvenBarriers):
            B.Enable()
        loop:
            Sleep(1.0)

Key idea: for with a failable index access inside it automatically skips any index that fails — no explicit error handling needed.

Gotchas

1. Array access is ALWAYS failable — use if or for

You cannot write MyArray[0] as a standalone statement or in a non-failure context. It must appear inside if (Val := MyArray[0]) or a for expression. Forgetting this is the #1 compile error newcomers hit.

# ❌ WRONG — compile error: expression may fail
Val := MyArray[0]
DoSomething(Val)

# ✅ CORRECT
if (Val := MyArray[0]):
    DoSomething(Val)

2. Indices are zero-based — Length is one past the end

array{10, 20, 30}.Length is 3, but the valid indices are 0, 1, 2. Accessing MyArray[MyArray.Length] always fails. The last valid index is MyArray.Length - 1.

3. Empty arrays make Length - 1 negative

If MyArray.Length is 0, then MyArray.Length - 1 is -1. Passing a negative index to [] will fail (which is safe), but it's cleaner to guard explicitly:

if (MyArray.Length > 0, Last := MyArray[MyArray.Length - 1]):
    # safe

4. @editable array slots can be unset in the editor

When you declare @editable Devices : []button_device = array{} and add entries in the Details panel, unset or null slots can cause the failable access to fail silently. Always test with all slots populated, and consider logging a warning if the array is shorter than expected.

5. No auto int↔float conversion

If your index comes from a float calculation (e.g., a slider value), you must explicitly convert it to int using Floor(), Ceil(), or Round() before using it as an array index. Verse will not implicitly cast.

# ❌ WRONG — float cannot index an array
Idx : float = 1.0
if (Val := MyArray[Idx]):  # compile error

# ✅ CORRECT
Idx : float = 1.0
IdxInt : int = Floor[Idx]  # failable — use if
if (IdxInt >= 0, Val := MyArray[IdxInt]):
    DoSomething(Val)

Build your own lesson with array_index_access

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →