Overview
Every time you write MyArray[Index] in Verse, you are writing a failable expression. If Index is within bounds, the expression succeeds and produces the element. If Index is out of bounds — or the array is empty — the expression fails, and Verse's failure-propagation rules take over.
This is not an error or an exception. It is a first-class language feature. You handle it the same way you handle any other failable expression: inside an if, a for, or another failure context. The square-bracket syntax [] is Verse's visual signal that "this might not work — you must handle that possibility."
When do you reach for this?
- Cycling through a list of spawn pads, checkpoint devices, or wave configs.
- Picking the first available player from a roster.
- Distributing items or roles from a fixed pool.
- Any time you store references to placed devices in an array and need to access them by index.
Because there is no dedicated "array bounds" device — arrays are a core Verse language feature — this article teaches the pattern through a complete, realistic game scenario and several focused snippets.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: A Checkpoint Relay Race
You have placed four trigger_device objects in your level as checkpoints. When a player steps on checkpoint N, your device activates checkpoint N+1's visual cue (via Enable) and deactivates the previous one (via Disable). When the player steps on the last checkpoint, they win.
This requires safely reading from an array of devices by a computed index — exactly where failable indexing shines.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Manages a checkpoint relay: stepping on each trigger advances to the next.
checkpoint_relay := class(creative_device):
# Place your trigger_device actors in order in the editor.
@editable
Checkpoints : []trigger_device = array{}
# Tracks which checkpoint index is currently active.
var ActiveIndex : int = 0
OnBegin<override>()<suspends> : void =
# Guard: nothing to do if no checkpoints were wired up.
if (Checkpoints.Length = 0):
return
# Activate only the first checkpoint at the start.
ActivateCheckpoint(0)
# Subscribe every trigger to the same handler.
# We use index-based subscription so the handler can identify WHICH
# checkpoint fired by comparing against ActiveIndex.
for (Index -> Trigger : Checkpoints):
Trigger.TriggeredEvent.Subscribe(OnCheckpointTriggered)
# Keep the coroutine alive so subscriptions stay live.
loop:
Sleep(60.0)
# Called whenever ANY checkpoint trigger fires.
OnCheckpointTriggered(Agent : ?agent) : void =
# Compute the next index.
NextIndex := ActiveIndex + 1
# Try to access the next checkpoint — this FAILS if we are already
# at the last one, which is exactly how we detect "race complete".
if (NextCheckpoint := Checkpoints[NextIndex]):
# Deactivate current, activate next.
if (Current := Checkpoints[ActiveIndex]):
Current.Disable()
set ActiveIndex = NextIndex
ActivateCheckpoint(NextIndex)
else:
# NextIndex was out of bounds — player finished the last checkpoint!
OnRaceComplete(Agent)
# Enables the trigger at Index so it can fire.
ActivateCheckpoint(Index : int) : void =
if (Trigger := Checkpoints[Index]):
Trigger.Enable()
# Handles race completion.
OnRaceComplete(Agent : ?agent) : void =
# Disable all checkpoints so no one can re-trigger them.
for (Trigger : Checkpoints):
Trigger.Disable()
Line-by-line explanation:
Checkpoints.Length = 0— a safe early-exit guard.Lengthalways succeeds; it returns0for an empty array.for (Index -> Trigger : Checkpoints)— Verse's indexedforloop.Indexis the integer position,Triggeris the element. This never fails because the loop simply produces zero iterations on an empty array.if (NextCheckpoint := Checkpoints[NextIndex]):— this is the key pattern. The square-bracket index is failable. IfNextIndexequalsCheckpoints.Length, the expression fails and theelsebranch runs. No crash, no guard needed beyond theif.if (Current := Checkpoints[ActiveIndex]):— even thoughActiveIndexwas set by us and should be valid, defensive indexing costs nothing and prevents subtle bugs if logic ever changes.- The
elsebranch of the outerifis the "out of bounds = race complete" signal — turning a language mechanic into game logic.
Common patterns
Pattern 1: Safe "pick first available" from a pool
You have an array of item_granter_device objects. Give the first one to the first player who joins, the second to the next, and so on. If the pool is exhausted, do nothing.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
weapon_distributor := class(creative_device):
@editable
Granters : []item_granter_device = array{}
# Tracks how many granters have been assigned.
var NextGranterIndex : int = 0
OnBegin<override>()<suspends> : void =
Playspace := GetPlayspace()
Playspace.PlayerAddedEvent().Subscribe(OnPlayerAdded)
loop:
Sleep(60.0)
OnPlayerAdded(Player : player) : void =
# Failable index: if we have run out of granters, this fails silently.
if (Granter := Granters[NextGranterIndex]):
Granter.GrantItem(Player)
set NextGranterIndex = NextGranterIndex + 1
# If the index was out of bounds, we simply do nothing — no crash.
Key point: Granters[NextGranterIndex] fails the moment NextGranterIndex >= Granters.Length. The if block is skipped entirely. No explicit length check needed.
Pattern 2: Iterating with for — bounds are never an issue
When you use for (Item : Array), Verse iterates only over valid indices automatically. This is the safest way to touch every element.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
all_triggers_reset := class(creative_device):
@editable
AllTriggers : []trigger_device = array{}
OnBegin<override>()<suspends> : void =
# for never goes out of bounds — it stops when elements are exhausted.
for (Trigger : AllTriggers):
Trigger.Reset()
loop:
Sleep(60.0)
Key point: for over an array is always safe. Use direct indexing (Array[N]) only when you need a specific position, and always inside a failure context.
Pattern 3: Wrapping index with modulo for a cycling sequence
A common game pattern is cycling through items repeatedly (e.g., round 1 → weapon set A, round 2 → weapon set B, round 3 → weapon set A again). Modulo keeps the index in range, but you should still index inside an if for robustness.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
cycling_granter := class(creative_device):
@editable
Granters : []item_granter_device = array{}
var RoundNumber : int = 0
OnBegin<override>()<suspends> : void =
loop:
Sleep(30.0)
AdvanceRound()
AdvanceRound() : void =
set RoundNumber = RoundNumber + 1
Len := Granters.Length
# Guard against empty array before using modulo (division by zero is failable).
if (Len > 0):
Index := Int(Mod[Float(RoundNumber), Float(Len)])
if (Granter := Granters[Index]):
# Grant to all players — in a real device you'd iterate the playspace.
Granter.Enable()
Key point: Even with a mathematically valid modulo result, indexing inside if is a good habit. It also documents intent: "this might not resolve."
Gotchas
1. Square brackets [] mean "this can fail" — use them only inside a failure context
MyArray[0] outside an if, for, or other failure context is a compile error. You must write:
if (First := MyArray[0]):
# use First
Never try to use a bare index expression as a statement.
2. Length is always safe; indexing is not
MyArray.Length always succeeds and returns an int (0 for empty). Use it for guards and loop bounds. But MyArray[MyArray.Length - 1] still fails when the array is empty because Length - 1 would be -1 (or Length is 0 and the subtraction wraps). Always check Length > 0 before computing a last-element index.
3. Failure propagates — the whole if block is skipped
If you write:
if (A := MyArray[I], B := MyArray[I + 1]):
DoSomethingWith(A, B)
If either index fails, the entire if body is skipped. This is usually what you want, but be aware that partial success is not possible inside a single if condition chain.
4. for with index syntax vs. element syntax
for (Item : Array)— iterates elements, always safe.for (Index -> Item : Array)— iterates (index, element) pairs, also always safe.for (Index : array{0, 1, 2, 3})with a manualArray[Index]inside — the inner index is failable; put it in anif.
5. Negative indices always fail
Verse arrays are zero-indexed and do not support negative indexing. MyArray[-1] always fails. If you compute an index from subtraction (e.g., ActiveIndex - 1), guard with if (ComputedIndex >= 0) before indexing.
6. @editable arrays start empty unless you wire them in the editor
A common bug: you declare @editable MyDevices : []trigger_device = array{} but forget to add elements in the UEFN editor. Your device silently does nothing. Always add a Length > 0 guard or a log message in OnBegin to catch this during development.