Overview
An option in Verse is a value that may or may not be there. You write its type with a leading ?: ?agent, ?fort_character, ?creative_prop. Think of it as a box that is either empty (called false) or holds exactly one value.
You meet options constantly in Fortnite game code:
- A
listenable(?agent)event hands your handler anAgent : ?agent— the player might be null (e.g. environmental damage with no attacker). - An
elimination_resulthasEliminatingCharacter : ?fort_character— the eliminator may not exist. Map[Key]lookups,Array[Index]access, and any<decides>call produce optional / failable results.
Option chaining is the discipline of unwrapping these safely before you touch the value. The core tool is the ? unwrap operator used inside a failure context (if, for, set ... :=). Value? succeeds and gives you the inner value if the box is full, and fails (skips the branch) if it's empty. This turns "might crash / might be garbage" into "only run when the data is real."
Reach for option chaining whenever:
- You subscribe to a device event that passes a
?agent. - You store a device or prop reference that could be unset.
- You want one guarded expression instead of nested null checks.
Because option chaining is a language feature, not a device, this article grounds its examples in real devices — button_device, trigger_device, and creative_prop — so you see the pattern in a working game scenario.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: A player presses a button. We grant them a "key" by showing a hidden creative_prop vault door prop and moving it — but ONLY if we can safely unwrap the pressing agent and confirm our prop reference is valid. This shows the whole option-chaining loop: an event gives us a ?agent, and we hold a prop we must validate before using.
using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
# A device that opens a vault when a button is pressed by a real player.
vault_controller := class(creative_device):
# The button the player interacts with.
@editable
OpenButton : button_device = button_device{}
# The prop we treat as the vault door. Starts hidden.
@editable
VaultDoor : creative_prop = creative_prop{}
OnBegin<override>()<suspends>:void =
# Hide the door until someone earns it.
VaultDoor.Hide()
# Subscribe our handler to the button's interaction event.
OpenButton.InteractedWithEvent.Subscribe(OnButtonPressed)
# InteractedWithEvent hands us Agent : ?agent — it MIGHT be empty.
OnButtonPressed(Agent : ?agent) : void =
# Option chaining: unwrap Agent with ?. If empty, the whole
# block is skipped and nothing happens.
if (Player := Agent?):
OpenVaultFor(Player)
# Now we have a real, non-optional agent.
OpenVaultFor(Player : agent) : void =
# Guard the prop too: IsValid is a <decides> call that fails
# if the prop was destroyed. Chain it into the same if.
if (VaultDoor.IsValid[]):
VaultDoor.Show()
# Nudge it upward so it visibly slides open.
VaultDoor.SetAngularVelocity(vector3{Z := 1.0})
Line by line:
@editable OpenButton : button_device/VaultDoor : creative_prop— the two placed things we control. They MUST be@editablefields on acreative_deviceclass, or Verse can't call their methods.VaultDoor.Hide()inOnBeginhides the prop at match start (a realcreative_propmethod).OpenButton.InteractedWithEvent.Subscribe(OnButtonPressed)wires the button press to our method.OnButtonPressed(Agent : ?agent)— the signature the event demands.Agentis an option.if (Player := Agent?):— the heart of option chaining.Agent?unwraps the box. If a player is inside,Playerbecomes a realagentand the branch runs. If the box is empty (no player), the branch is skipped safely — no crash.if (VaultDoor.IsValid[]):—IsValidis a failable<decides>method (note the[]failable call brackets). If the prop still exists, we proceed; otherwise we skip. This is option/failure chaining on a device method.VaultDoor.Show()andSetAngularVelocity(...)— real prop methods that make the door appear and spin open.
The key takeaway: two potentially-missing things (the agent and the prop) are each unwrapped in a failure context before use.
Common patterns
Pattern 1 — Unwrapping a ?agent from a trigger
A trigger_device's TriggeredEvent also passes ?agent. Same unwrap, different device.
using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Verse.org/Simulation }
trap_plate := class(creative_device):
@editable
Plate : trigger_device = trigger_device{}
@editable
TrapProp : creative_prop = creative_prop{}
OnBegin<override>()<suspends>:void =
Plate.TriggeredEvent.Subscribe(OnStepped)
OnStepped(Agent : ?agent) : void =
# Only spring the trap if a real player stepped on the plate.
if (Player := Agent?):
# A real agent exists; make the trap prop drop by hiding it.
TrapProp.Hide()
Pattern 2 — Chaining through elimination_result.EliminatingCharacter
Game structs expose options too. Here we read the optional eliminator off an elimination and only score if it exists.
using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Fortnite.com/Game }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
kill_reward := class(creative_device):
@editable
RewardProp : creative_prop = creative_prop{}
OnBegin<override>()<suspends>:void =
RewardProp.Hide()
# Call this from your own elimination handler, passing the result.
HandleElimination(Result : elimination_result) : void =
# EliminatingCharacter is ?fort_character — could be empty
# (environmental death). Chain-unwrap before rewarding.
if (Eliminator := Result.EliminatingCharacter?):
# A real killer exists — flash the reward prop into view.
RewardProp.Show()
Pattern 3 — Failure-chaining a <decides> device call
Option chaining and failable-call chaining share the same failure context. Here we validate a prop and only then read + apply its velocity.
using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
bouncer := class(creative_device):
@editable
LaunchButton : button_device = button_device{}
@editable
Ball : creative_prop = creative_prop{}
OnBegin<override>()<suspends>:void =
LaunchButton.InteractedWithEvent.Subscribe(OnLaunch)
OnLaunch(Agent : ?agent) : void =
# Chain BOTH the agent unwrap AND the failable IsValid[]
# in one guard. Everything after runs only if both succeed.
if (Player := Agent?, Ball.IsValid[]):
Ball.ApplyLinearImpulse(vector3{Z := 500.0})
Gotchas
?only works in a failure context. You can't writePlayer := Agent?as a plain statement. It must live insideif (...),for (...), a<decides>function body, or another failable position. Outside those, the compiler rejects it.?agenthandlers must match the event signature exactly. Alistenable(?agent)event callsOnStepped(Agent : ?agent). Don't declare the param asagent— it won't match and won't compile.- Failable device calls use
[], not().VaultDoor.IsValid[]andVaultDoor.IsDisposed[]are<decides>— call them with square brackets inside a failure context. Using()gives a wrong-effect error. - Don't confuse the empty option with
falsethe boolean. An empty option in Verse is literally the valuefalse, but you don't test it withif (Agent = false)— you unwrap withAgent?and let the branch fail. - Comma-chaining is AND, and short-circuits. In
if (Player := Agent?, Ball.IsValid[]):if the first clause fails, the second never runs and the block is skipped. Order cheap/likely-to-fail checks first. - A field like
VaultDoor : creative_propis never itself an option — it's a concrete reference you set in the editor. UseIsValid[](a failable check) to confirm it still exists, not?unwrapping. messageparams still need localization. Unrelated to options but a common co-occurring trap: if you later pass text to a device method, declareMyText<localizes>(S:string):message = "{S}"— there is noStringToMessage.