Reference Verse compiles

Ternary-Style Choice in Verse: Branching Logic on the Sunny Docks

Verse has no `?:` ternary operator — instead it uses `if (cond) then A else B` expressions that return a value, giving you the same compact branching in one line. Pair that with `trigger_device` events and you get snappy, readable game logic: one expression picks which dock gate fires, which cannon primes, or how many times a buoy can ring before the tide rolls in. This article teaches you the pattern through a sun-drenched 2D cel-shaded pirate dock, then shows every corner of the `trigger_devic

Updated Examples verified on the live UEFN compiler

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_device to 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 float components.

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.

Guides & scripts that use trigger_device

Step-by-step tutorials that put this object to work.

Build your own lesson with trigger_device

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →