Overview
An if-expression in Verse evaluates one or more failable conditions and executes the matching block. Unlike many languages, if in Verse is an expression — it can produce a value — and its conditions use the [] decision syntax for things that can fail (like unwrapping an option). You'll reach for if-branching whenever your island needs to:
- React differently based on game state (e.g., "is this the last trigger remaining?")
- Safely unwrap an
?agentbefore using it - Enable or disable devices conditionally
- Gate logic behind a counter or a flag
The examples below all live on a cel-shaded pirate cove — a rickety dock, a cannon, and a glowing treasure trigger buried in the sand.
API Reference
trigger_device
Used to relay events to other linked devices.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.
trigger_device<public> := class<concrete><final>(trigger_base_device):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
TriggeredEvent |
TriggeredEvent<public>:listenable(?agent) |
Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code). |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Trigger |
Trigger<public>(Agent:agent):void |
Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent. |
Trigger |
Trigger<public>():void |
Triggers this device, causing it to activate its TriggeredEvent event. |
Enable |
Enable<public>():void |
Enables this device. |
Disable |
Disable<public>():void |
Disables this device. |
SetMaxTriggerCount |
SetMaxTriggerCount<public>(MaxCount:int):void |
Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20]. |
GetMaxTriggerCount |
GetMaxTriggerCount<public>()<transacts>:int |
Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count. |
GetTriggerCountRemaining |
GetTriggerCountRemaining<public>()<transacts>:int |
Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited. |
SetResetDelay |
SetResetDelay<public>(Time:float):void |
Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows). |
GetResetDelay |
GetResetDelay<public>()<transacts>:float |
Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows). |
SetTransmitDelay |
SetTransmitDelay<public>(Time:float):void |
Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered. |
GetTransmitDelay |
GetTransmitDelay<public>()<transacts>:float |
Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered. |
Walkthrough
Scenario: The Cannon Fires Only on the Third Step
A player walks across a pressure plate hidden under the dock. The first two times nothing visible happens (the cannon stays silent). On the third trigger the cannon fires — represented here by a second trigger_device that is wired to a VFX spawner in the editor. After the cannon fires, the plate is permanently disabled so it can never fire again.
Place in your level:
- PlateTrigger — a
trigger_devicethe player walks over (set Max Trigger Count to 0 in the editor so Verse controls it) - CannonTrigger — a
trigger_devicewired to your cannon VFX/sound in the editor
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Localized helper so we can pass message params
DockLog<localizes>(S : string) : message = "{S}"
cannon_dock_device := class(creative_device):
# The pressure plate hidden under the dock planks
@editable PlateTrigger : trigger_device = trigger_device{}
# Wired in the editor to the cannon VFX + sound
@editable CannonTrigger : trigger_device = trigger_device{}
# How many steps before the cannon fires
CannonThreshold : int = 3
OnBegin<override>()<suspends> : void =
# Allow exactly CannonThreshold activations, then we lock it ourselves
PlateTrigger.SetMaxTriggerCount(CannonThreshold)
PlateTrigger.Enable()
PlateTrigger.TriggeredEvent.Subscribe(OnPlateTriggered)
# Called every time a player steps on the plate
# The event sends ?agent so the param must be ?agent
OnPlateTriggered(MaybeAgent : ?agent) : void =
# How many presses are left after this one?
Remaining := PlateTrigger.GetTriggerCountRemaining()
if (Remaining = 0):
# This was the final allowed press — fire the cannon!
CannonTrigger.Trigger()
# Disable the plate so it can never be stepped on again
PlateTrigger.Disable()
else:
# Still counting down — the plate rumbles but nothing dramatic happens
# (wire a subtle dust VFX to PlateTrigger in the editor for feedback)
var StepsLeft : int = Remaining
# No action needed in code; editor-linked devices handle the rumble
Line-by-line explanation
| Lines | What's happening |
|---|---|
@editable PlateTrigger |
Declares the dock plate so the editor can assign the placed device. Without @editable the identifier is unknown at compile time. |
SetMaxTriggerCount(CannonThreshold) |
Caps the plate at exactly 3 presses. After the 3rd, GetTriggerCountRemaining() returns 0. |
PlateTrigger.TriggeredEvent.Subscribe(OnPlateTriggered) |
Hooks our method to the event. The event signature is listenable(?agent) so the handler receives ?agent. |
Remaining := PlateTrigger.GetTriggerCountRemaining() |
Reads the live counter after this press has been recorded. |
if (Remaining = 0): |
Plain equality check — no [] needed because = on int is not failable. |
CannonTrigger.Trigger() |
Fires the cannon trigger (no agent needed). |
PlateTrigger.Disable() |
Locks the plate forever. |
Common patterns
Pattern 1 — Unwrapping the optional agent before acting
The TriggeredEvent sends ?agent (an optional agent). You must unwrap it with if before you can use the agent — for example, to call Trigger(Agent) on a second device and pass the real player reference.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Rewards the specific player who stepped on the lagoon idol plate
lagoon_idol_device := class(creative_device):
@editable IdolPlate : trigger_device = trigger_device{}
@editable RewardTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
IdolPlate.Enable()
IdolPlate.TriggeredEvent.Subscribe(OnIdolStepped)
OnIdolStepped(MaybeAgent : ?agent) : void =
# Unwrap the optional — if no agent triggered it (e.g. code-triggered),
# the else branch fires instead
if (A := MaybeAgent?):
# Pass the real agent so the reward trigger knows WHO to reward
RewardTrigger.Trigger(A)
else:
# Triggered by code with no agent — fire without an agent reference
RewardTrigger.Trigger()
Key point: if (A := MaybeAgent?) uses the ? option-unwrap operator inside an if condition. This is the idiomatic Verse pattern for ?agent — never assume the agent is present.
Pattern 2 — Checking reset and transmit delays before reconfiguring
Your cove has a timed cannon volley: the plate resets every 10 seconds. Before the round starts you read the current delays and only reconfigure if they differ from your design targets.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Configures the cannon plate's timing at round start
cannon_timing_device := class(creative_device):
@editable CannonPlate : trigger_device = trigger_device{}
TargetResetDelay : float = 10.0
TargetTransmitDelay : float = 1.5
OnBegin<override>()<suspends> : void =
CurrentReset := CannonPlate.GetResetDelay()
CurrentTransmit := CannonPlate.GetTransmitDelay()
# Only write if the editor value doesn't already match
if (CurrentReset <> TargetResetDelay):
CannonPlate.SetResetDelay(TargetResetDelay)
if (CurrentTransmit <> TargetTransmitDelay):
CannonPlate.SetTransmitDelay(TargetTransmitDelay)
CannonPlate.Enable()
CannonPlate.TriggeredEvent.Subscribe(OnCannonFired)
OnCannonFired(MaybeAgent : ?agent) : void =
# Cannon fired — wait for the reset delay then log remaining count
Sleep(TargetResetDelay)
Remaining := CannonPlate.GetTriggerCountRemaining()
if (Remaining > 0):
# Still has shots left — keep the plate hot
CannonPlate.Enable()
else:
# Unlimited (0) or exhausted — disable for safety
CannonPlate.Disable()
Key point: <> is Verse's not-equal operator for floats and ints. The if guards prevent unnecessary writes when the editor value already matches.
Pattern 3 — Branching on max trigger count to decide difficulty
Before the match begins, read how many times the treasure trigger is allowed to fire and branch into easy / hard setup paths.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Reads the editor-configured MaxTriggerCount and sets difficulty accordingly
treasure_difficulty_device := class(creative_device):
@editable TreasureTrigger : trigger_device = trigger_device{}
@editable HardModeTrigger : trigger_device = trigger_device{}
@editable EasyModeTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
MaxCount := TreasureTrigger.GetMaxTriggerCount()
# 0 means unlimited — treat as easy mode
# 1 means one-shot — treat as hard mode
if (MaxCount = 1):
HardModeTrigger.Trigger()
TreasureTrigger.SetResetDelay(0.0)
else if (MaxCount = 0):
EasyModeTrigger.Trigger()
TreasureTrigger.SetResetDelay(5.0)
else:
# Some finite count > 1 — medium difficulty, no special setup
TreasureTrigger.Enable()
TreasureTrigger.TriggeredEvent.Subscribe(OnTreasureFound)
OnTreasureFound(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
TreasureTrigger.Trigger(A)
Key point: else if chains work exactly as you'd expect. Each branch is evaluated top-to-bottom; only the first matching branch executes.
Gotchas
1. ?agent is ALWAYS optional — always unwrap it
TriggeredEvent is typed listenable(?agent). If you write OnTriggered(A : agent) the code will not compile. Always declare the parameter as ?agent and unwrap with if (RealAgent := MaybeAgent?).
2. if conditions use [] for failable expressions, = for equality
In Verse, if (X = 5) tests equality (not assignment). Failable calls — like map lookups or option unwraps — use [] syntax inside the condition: if (Val := MyMap[Key]). Mixing these up is the #1 beginner compile error.
3. GetTriggerCountRemaining() returns 0 for UNLIMITED triggers
A return value of 0 does not mean "no triggers left" — it means the max count is set to unlimited (also 0). Check GetMaxTriggerCount() first: if it returns 0, the device has no cap and GetTriggerCountRemaining() is meaningless.
4. SetMaxTriggerCount clamps to [0, 20]
Passing a value outside this range is silently clamped. If you need more than 20 activations, set the max to 0 (unlimited) and track your own counter with a var.
5. message params require a localizes helper — no raw strings
If you ever pass text to a function that expects message, you cannot pass a raw string literal. Declare a helper: MyMsg<localizes>(S:string):message = "{S}" and call MyMsg("hello").
6. Every device reference must be @editable
A bare trigger_device{} default is an unplaced stub. Always expose real placed devices through @editable fields and assign them in the UEFN Details panel — otherwise your calls do nothing.