Overview
In many languages a ternary expression looks like condition ? valueA : valueB. Verse replaces that with a first-class if expression:
Result = if (Condition) then ValueA else ValueB
Because if is an expression in Verse — not just a statement — it produces a value you can assign, pass as an argument, or embed inside another expression. This makes it perfect for:
- Choosing which
trigger_deviceto fire based on game state. - Picking a delay, a count, or a colour value inline without a helper variable.
- Keeping event handlers short and readable.
The device you'll pair it with throughout this article is trigger_device — the Swiss-army relay of UEFN. A trigger can fire its TriggeredEvent, be enabled/disabled, have its max-fire count capped, and have independent reset and transmit delays. All of those numeric parameters are natural candidates for ternary-style selection.
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. |
vector3
3-dimensional vector with
floatcomponents.
Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse).
vector3<native><public> := struct<concrete><computes><persistable><uht_comparable>:
color
Represents colors as RGB triples in the ACES 2065-1 color space. Component values are linear (i.e.
*gamma* = 1.0).
Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse).
color<native><public> := struct<concrete><computes><persistable><uht_comparable>:
Walkthrough
Scenario — The Pirate Dock Gate
You're building a 2D cel-shaded pirate island. At the sun-drenched dock there are two gates: a harbour gate (lets friendly ships in) and a cannon gate (fires a warning shot at strangers). A pressure plate on the dock determines which gate activates. If the player who steps on the plate is the captain (tracked by a simple flag), the harbour gate trigger fires; otherwise the cannon gate fires. The reset delay is also chosen with a ternary: captains get a short 2-second cooldown, strangers get a longer 5-second one so the cannon can reload.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
# -------------------------------------------------------
# dock_gate_manager — place this Verse device on the island
# -------------------------------------------------------
dock_gate_manager := class(creative_device):
# Wire these in the UEFN details panel
@editable HarbourGateTrigger : trigger_device = trigger_device{}
@editable CannonGateTrigger : trigger_device = trigger_device{}
@editable DockPlateTrigger : trigger_device = trigger_device{}
# Simple flag — in a real island this would come from
# a persistent stat or a team check.
var IsCaptainOnDock : logic = false
# Localised message helper (required for message params)
GateMsg<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Give the dock plate an unlimited trigger count (0 = no limit)
DockPlateTrigger.SetMaxTriggerCount(0)
# Short transmit delay so the gate animation has time to start
DockPlateTrigger.SetTransmitDelay(0.3)
# Subscribe to the dock plate
DockPlateTrigger.TriggeredEvent.Subscribe(OnDockPlateTriggered)
# Keep the task alive
loop:
Sleep(10.0)
# Called every time a player steps on the dock pressure plate
OnDockPlateTriggered(MaybeAgent : ?agent) : void =
# Unwrap the optional agent
if (Agent := MaybeAgent?):
# ---- TERNARY-STYLE CHOICE ----
# Pick which gate trigger to fire based on captain status
var ActiveTrigger : trigger_device = CannonGateTrigger
if (IsCaptainOnDock?):
set ActiveTrigger = HarbourGateTrigger
# Pick a reset delay: captains get 2 s, strangers get 5 s
var ChosenDelay : float = 5.0
if (IsCaptainOnDock?):
set ChosenDelay = 2.0
# Apply the chosen delay to whichever trigger we selected
ActiveTrigger.SetResetDelay(ChosenDelay)
# Fire the chosen gate trigger, passing the agent through
ActiveTrigger.Trigger(Agent)```
### Line-by-line explanation
| Lines | What's happening |
|---|---|
| `@editable` fields | Lets you drag real placed devices from the UEFN outliner into these slots. Without `@editable` the class can't see any placed device. |
| `SetMaxTriggerCount(0)` | `0` means unlimited — the dock plate can be stepped on as many times as needed. |
| `SetTransmitDelay(0.3)` | The plate waits 0.3 s before telling downstream devices it fired, giving gate animations a head-start. |
| `DockPlateTrigger.TriggeredEvent.Subscribe(OnDockPlateTriggered)` | Registers our handler. The event sends `?agent` (an optional agent). |
| `if (MaybeAgent?)` | Unwraps the `?agent`. If no agent triggered it (e.g. a code call), we skip the body safely. |
| `ActiveTrigger := if (IsCaptainOnDock) then HarbourGateTrigger else CannonGateTrigger` | **The ternary pattern.** One expression, one assignment, no extra `var`. |
| `ChosenDelay := if (IsCaptainOnDock) then 2.0 else 5.0` | Same pattern for a `float` value — Verse infers the type. |
| `ActiveTrigger.SetResetDelay(ChosenDelay)` | Applies the chosen delay to the selected trigger before firing. |
| `ActiveTrigger.Trigger(Agent)` | Fires the gate trigger and passes the agent so downstream devices know *who* caused it. |
---
## Common patterns
### Pattern 1 — Choosing a max-trigger count at runtime
A buoy bell on the lagoon shore rings a different number of times depending on whether it's high tide (game flag) or low tide. Use a ternary to pick the cap, then read it back with `GetMaxTriggerCount`.
```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
buoy_bell_manager := class(creative_device):
@editable BuoyBellTrigger : trigger_device = trigger_device{}
# True when the tide-event device has signalled high tide
var IsHighTide : logic = false
OnBegin<override>()<suspends> : void =
# High tide: bell rings up to 5 times; low tide: only 2 times
MaxRings := if (IsHighTide) then 5 else 2
BuoyBellTrigger.SetMaxTriggerCount(MaxRings)
# Log the effective cap back out via the getter
EffectiveCap := BuoyBellTrigger.GetMaxTriggerCount()
# Ring the bell immediately (no agent — triggered by code)
BuoyBellTrigger.Trigger()
# Check how many rings remain after the first one
Remaining := BuoyBellTrigger.GetTriggerCountRemaining()
loop:
Sleep(30.0)
Key calls: SetMaxTriggerCount, GetMaxTriggerCount, GetTriggerCountRemaining, Trigger() (no-agent overload).
Pattern 2 — Enable / Disable a clifftop signal fire
A clifftop signal-fire trigger should only be active during the day phase. A ternary picks the action to call — Enable or Disable — based on a IsDayPhase flag. Because both methods are void, we call the result of the ternary directly by storing a lambda.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
signal_fire_manager := class(creative_device):
@editable SignalFireTrigger : trigger_device = trigger_device{}
@editable DayNightTrigger : trigger_device = trigger_device{}
var IsDayPhase : logic = true
OnBegin<override>()<suspends> : void =
DayNightTrigger.TriggeredEvent.Subscribe(OnPhaseChanged)
# Start in the correct state
ApplyPhase()
loop:
Sleep(60.0)
OnPhaseChanged(MaybeAgent : ?agent) : void =
# Toggle the phase flag
set IsDayPhase = if (IsDayPhase) then false else true
ApplyPhase()
ApplyPhase() : void =
# Ternary picks the branch; each branch calls the right method
if (IsDayPhase):
SignalFireTrigger.Enable()
else:
SignalFireTrigger.Disable()
Key calls: Enable, Disable, TriggeredEvent.Subscribe.
Pattern 3 — Transmit delay chosen by player count
A cove cannon fires a salute. The transmit delay (how long before it tells linked firework devices to go off) is longer when many players are watching so the crowd can see the flash first. We use a ternary on a player-count check to set it.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
cove_cannon_manager := class(creative_device):
@editable CoveCannon : trigger_device = trigger_device{}
@editable SaluteTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
SaluteTrigger.TriggeredEvent.Subscribe(OnSaluteTriggered)
loop:
Sleep(60.0)
OnSaluteTriggered(MaybeAgent : ?agent) : void =
Players := GetPlayspace().GetPlayers()
PlayerCount := Players.Length
# Big crowd (>= 4 players) gets a longer dramatic delay
ChosenTransmitDelay := if (PlayerCount >= 4) then 1.5 else 0.5
CoveCannon.SetTransmitDelay(ChosenTransmitDelay)
# Confirm the delay we just set
ActualDelay := CoveCannon.GetTransmitDelay()
# Fire the cannon (no specific agent needed)
CoveCannon.Trigger()
Key calls: SetTransmitDelay, GetTransmitDelay, Trigger() (no-agent overload).
Gotchas
1. Both branches of if-then-else must return the same type
Verse infers the type of an if expression from both branches. If one branch is trigger_device and the other is int, the compiler rejects it. Keep both branches the same concrete type.
# WRONG — mismatched types
Bad := if (Flag) then HarbourGateTrigger else 42
# RIGHT — both are trigger_device
Good := if (Flag) then HarbourGateTrigger else CannonGateTrigger
2. No implicit int ↔ float conversion
SetMaxTriggerCount takes int; SetResetDelay takes float. Don't mix them:
# WRONG — 2 is an int, SetResetDelay needs float
DockPlateTrigger.SetResetDelay(if (Flag) then 2 else 5)
# RIGHT — use float literals
DockPlateTrigger.SetResetDelay(if (Flag) then 2.0 else 5.0)
3. Always unwrap ?agent before use
TriggeredEvent sends ?agent (an optional agent). Calling Trigger(Agent) requires a plain agent. Always unwrap:
OnTriggered(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?): # unwrap here
SomeTrigger.Trigger(A) # now A is agent, not ?agent
If you skip the unwrap the compiler will reject the call because ?agent ≠ agent.
4. SetMaxTriggerCount is clamped to [0, 20]
Values above 20 are silently clamped to 20. 0 is the special "unlimited" sentinel. Plan your trigger budgets accordingly — if you need more than 20 fires, set the count to 0.
5. GetTriggerCountRemaining returns 0 when the limit is unlimited
This is a documentation quirk: 0 means both "unlimited" (from GetMaxTriggerCount) and "zero remaining" (from GetTriggerCountRemaining). Check GetMaxTriggerCount() first to disambiguate:
Max := MyTrigger.GetMaxTriggerCount()
Remaining := MyTrigger.GetTriggerCountRemaining()
IsUnlimited := if (Max = 0) then true else false
6. @editable is mandatory for placed devices
A bare trigger_device{} in your class is a default-constructed stub — it is not the device you placed in the level. Always mark fields @editable and wire them in the UEFN details panel, or your calls silently do nothing.
7. message params need <localizes>, not raw strings
If you ever pass text to a UI widget alongside your trigger logic, remember: message is not string. Declare a localizing helper:
MyLabel<localizes>(S : string) : message = "{S}"
There is no StringToMessage function in Verse.