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
@editabledevices 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_deviceto 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)