Reference Verse compiles

sync & await: Waiting for Everything at Once

Sometimes your game needs to wait for *several* things to finish before moving on — all three treasure chests opened, every timer expired, every gate triggered. Verse's `sync` expression runs multiple async branches in parallel and only continues when **every single one** completes. Pair it with `await` on device events and you get a powerful, readable way to orchestrate multi-step island moments without a tangle of flags and counters.

Updated Examples verified on the live UEFN compiler

Overview

In Verse, sync is a structured-concurrency expression that launches two or more async sub-expressions simultaneously and suspends the caller until all of them finish. Think of it as the parallel cousin of race — where race stops the moment one branch wins, sync waits for every branch to cross the finish line.

This matters on a pirate lagoon island where you want three separate dock gates to be triggered before the treasure ship sails. Without sync you'd need shared counters, mutable state, and careful bookkeeping. With sync the intent is crystal-clear:

sync:
    await GateA.TriggeredEvent   # wait for gate A
    await GateB.TriggeredEvent   # wait for gate B (runs in parallel)
    await GateC.TriggeredEvent   # wait for gate C (runs in parallel)
// all three done — fire the cannons!

When to reach for sync:

  • Multiple players must each complete a task before the round ends.
  • A sequence of devices (timers, triggers) must all succeed before a cinematic plays.
  • You want a countdown timer and a trigger condition to both be satisfied simultaneously.
  • Any "all of the above" gate in your game logic.

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.

timer_device

Provides a way to keep track of the time something has taken, either for scoreboard purposes, or to trigger actions. It can be configured in several ways, either acting as a countdown to an event that is triggered at the end, or as a stopwatch for an action that needs to be completed before a set time runs out.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).

timer_device<public> := class<concrete><final>(creative_device_base):

Events (subscribe a handler to react):

Event Signature Description
SuccessEvent SuccessEvent<public>:listenable(?agent) Signaled when the timer completes or ends with success. Sends the agent that activated the timer, if any.
FailureEvent FailureEvent<public>:listenable(?agent) Signaled when the timer completes or ends with failure. Sends the agent that activated the timer, if any.
StartUrgencyModeEvent StartUrgencyModeEvent<public>:listenable(?agent) Signaled when the timer enters Urgency Mode. Sends the agent that activated the timer, if any.

Methods (call these to make the device act):

Method Signature Description
Enable Enable<public>(Agent:agent):void Enables this device for Agent.
Enable Enable<public>():void Enables this device.
Disable Disable<public>(Agent:agent):void Disables this device for Agent. While disabled this device will not receive signals.
Disable Disable<public>():void Disables this device. While disabled this device will not receive signals.
ResetForAll ResetForAll<public>(Agent:agent):void Resets the timer back to its base time and stops it for all agents.
ResetForAll ResetForAll<public>():void Resets the timer back to its base time and stops it for all agents.
Start Start<public>(Agent:agent):void Starts the timer for Agent.
Start Start<public>():void Starts the timer.
Pause Pause<public>(Agent:agent):void Pauses the timer for Agent.
Pause Pause<public>():void Pauses the timer.
Resume Resume<public>(Agent:agent):void Resumes the timer for Agent.
Resume Resume<public>():void Resumes the timer.
Complete Complete<public>(Agent:agent):void Completes the timer for Agent.
Complete Complete<public>():void Completes the timer.
StartForAll StartForAll<public>(Agent:agent):void Starts the timer for all agents.
StartForAll StartForAll<public>():void Starts the timer for all agents.
PauseForAll PauseForAll<public>(Agent:agent):void Pauses the timer for all agents.
PauseForAll PauseForAll<public>():void Pauses the timer for all agents.
ResumeForAll ResumeForAll<public>(Agent:agent):void Resumes the timer for all agents.
ResumeForAll ResumeForAll<public>():void Resumes the timer for all agents.
CompleteForAll CompleteForAll<public>(Agent:agent):void Completes the timer for all agents.
CompleteForAll CompleteForAll<public>():void Completes the timer for all agents.
Save Save<public>(Agent:agent):void Saves this device's data for Agent.
Load Load<public>(Agent:agent):void Loads this device's saved data for Agent.
ClearPersistenceData ClearPersistenceData<public>(Agent:agent):void Clears this device's saved data for Agent.
ClearPersistenceDataForAll ClearPersistenceDataForAll<public>(Agent:agent):void Clears this device's saved data for all agents.
ClearPersistenceDataForAll ClearPersistenceDataForAll<public>():void Clears this device's saved data for all agents.
SetActiveDuration SetActiveDuration<public>(Time:float, Agent:agent):void Sets the remaining time (in seconds) on the timer, if active, on Agent.
SetActiveDuration SetActiveDuration<public>(Time:float):void Sets the remaining time (in seconds) on the timer, if active. Use this function if the timer is set to use the same time for all agent's.
GetActiveDuration GetActiveDuration<public>(Agent:agent)<transacts>:float Returns the remaining time (in seconds) on the timer for Agent.
GetActiveDuration GetActiveDuration<public>()<transacts>:float Returns the remaining time (in seconds) on the timer if it is set to be global.
SetLapTime SetLapTime<public>(Agent:agent):void Sets the lap time indicator for Agent.
SetLapTimeForAll SetLapTimeForAll<public>(Agent:agent):void Sets the lap time indicator for all agents.
SetLapTimeForAll SetLapTimeForAll<public>():void Sets the lap time indicator for all agents.
SetMaxDuration SetMaxDuration<public>(Time:float):void Sets the maximum duration of the timer (in seconds).
GetMaxDuration GetMaxDuration<public>()<transacts>:float Returns the maximum duration of the timer (in seconds).
IsStatePerAgent IsStatePerAgent<public>()<transacts><decides>:void Succeeds if this device is tracking timer state for each individual agent independently. Fails if state is being tracked globally for all agent's.

player

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse). Inherited members are merged from agent.

player<native><public> := class<unique><persistent><module_scoped_var_weak_map_key><epic_internal>(agent):

Walkthrough

Scenario — The Lagoon Treasure Vault

Three glowing dock pressure plates guard a sunken pirate vault on a 2D cel-shaded lagoon island. When all three plates are stepped on (in any order), a countdown timer starts. If the timer completes successfully and a final confirmation trigger fires before time runs out, the vault door blasts open via a cinematic sequence. Miss the window and the vault resets.

Place in UEFN:

  • Three trigger_devices: DockPlateA, DockPlateB, DockPlateC
  • One timer_device: VaultTimer (set to countdown, e.g. 30 seconds)
  • One trigger_device: ConfirmTrigger (the final lever pull)
  • One cinematic_sequence_device: VaultOpenCinematic
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# lagoon_vault_manager — place this Verse device on your pirate lagoon island.
# Wire up the three dock plates, the vault timer, the confirm trigger,
# and the vault cinematic in the Details panel.
lagoon_vault_manager := class(creative_device):

    # The three glowing dock pressure plates around the lagoon
    @editable DockPlateA : trigger_device = trigger_device{}
    @editable DockPlateB : trigger_device = trigger_device{}
    @editable DockPlateC : trigger_device = trigger_device{}

    # Countdown timer — configure it in UEFN to count down (e.g. 30 s)
    @editable VaultTimer : timer_device = timer_device{}

    # The final confirmation lever near the vault door
    @editable ConfirmTrigger : trigger_device = trigger_device{}

    # Cinematic that plays the vault door blasting open
    @editable VaultOpenCinematic : cinematic_sequence_device = cinematic_sequence_device{}

    OnBegin<override>()<suspends>:void =
        # Disable the confirm trigger and timer until all plates are stepped on
        ConfirmTrigger.Disable()
        VaultTimer.Disable()

        loop:
            # ── PHASE 1 ─────────────────────────────────────────────────────
            # Wait for ALL THREE dock plates to be triggered (any order, in parallel).
            # sync suspends here until every branch completes.
            sync:
                block:
                    PlateEventA := DockPlateA.TriggeredEvent.Await()
                block:
                    PlateEventB := DockPlateB.TriggeredEvent.Await()
                block:
                    PlateEventC := DockPlateC.TriggeredEvent.Await()

            # All three plates are now active — arm the vault window
            ConfirmTrigger.Enable()
            VaultTimer.Enable()
            VaultTimer.Start()   # kick off the countdown

            # ── PHASE 2 ─────────────────────────────────────────────────────
            # Race: did the player pull the confirm lever before time ran out?
            # We use race here (not sync) because only ONE outcome can happen.
            race:
                block:
                    # SUCCESS PATH — confirm lever pulled in time
                    ConfirmTrigger.TriggeredEvent.Await()
                    VaultTimer.Complete()           # stop the timer with success
                    VaultOpenCinematic.Play()       # blast the vault open!
                    # Disable everything while cinematic plays
                    ConfirmTrigger.Disable()
                    VaultTimer.Disable()
                    Sleep(8.0)                      # let the cinematic finish
                block:
                    # FAILURE PATH — timer ran out
                    VaultTimer.FailureEvent.Await()
                    ConfirmTrigger.Disable()
                    VaultTimer.Disable()

            # Reset all plates for the next attempt and loop back
            DockPlateA.Enable()
            DockPlateB.Enable()
            DockPlateC.Enable()
            VaultTimer.ResetForAll()
            Sleep(2.0)   # brief pause before accepting new input

Line-by-line breakdown:

Lines What's happening
ConfirmTrigger.Disable() / VaultTimer.Disable() Prevent premature activation before all plates are stepped on.
sync: block: … block: … block: … The heart of this article. Three block branches run simultaneously. Each Await()s its own TriggeredEvent. sync suspends the outer coroutine until all three have fired — in whatever order players step on them.
VaultTimer.Start() Kicks off the countdown the moment all plates confirm.
race: block: … block: … Two competing outcomes — success (lever pulled) vs failure (timer expires). Only one wins.
VaultTimer.Complete() Manually marks the timer successful, stopping it cleanly.
VaultTimer.FailureEvent.Await() Listens for the timer's built-in failure signal (time ran out).
VaultTimer.ResetForAll() Resets the timer state so the loop can run again.
Sleep(2.0) Small cooldown before the next attempt cycle begins.

Common patterns

Pattern 1 — Sync two timers before awarding a score

Two separate lagoon challenge timers must both complete successfully before the score manager awards bonus points. sync waits for both SuccessEvents.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# dual_timer_bonus — awards bonus points only when BOTH lagoon timers succeed.
dual_timer_bonus := class(creative_device):

    @editable TimerLeft  : timer_device        = timer_device{}
    @editable TimerRight : timer_device        = timer_device{}
    @editable BonusScore : score_manager_device = score_manager_device{}

    # Tracks the agent from the left timer so we can award them
    var LastAgent : ?agent = false

    OnBegin<override>()<suspends>:void =
        TimerLeft.Start()
        TimerRight.Start()

        # Wait for BOTH timers to signal success — in parallel
        sync:
            block:
                MaybeAgent := TimerLeft.SuccessEvent.Await()
                if (A := MaybeAgent?):
                    set LastAgent = MaybeAgent
            block:
                TimerRight.SuccessEvent.Await()

        # Both timers succeeded — award the bonus
        if (A := LastAgent?, Scorer := A):
            BonusScore.Activate(Scorer)

Pattern 2 — Disable a trigger after it fires, then re-enable after a delay

A single dock-gate trigger on the pirate cove disables itself after firing (so it can't be spammed), then re-enables after a reset delay. Uses SetMaxTriggerCount, GetResetDelay, and Enable/Disable directly.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# cove_gate_cooldown — a dock trigger that enforces its own cooldown via Verse.
cove_gate_cooldown := class(creative_device):

    @editable CoveGate : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        # Allow exactly 1 trigger per cycle; 0 = unlimited, so we use 1
        CoveGate.SetMaxTriggerCount(1)
        CoveGate.SetResetDelay(0.0)   # we'll handle the delay ourselves

        loop:
            # Wait for the gate to fire
            CoveGate.TriggeredEvent.Await()

            # Immediately lock it out
            CoveGate.Disable()

            # Read the transmit delay to honour whatever the designer set
            TransmitDelay := CoveGate.GetTransmitDelay()
            CooldownTime  := TransmitDelay + 5.0   # add 5 s on top
            Sleep(CooldownTime)

            # Re-arm the gate for the next player
            CoveGate.Enable()
            CoveGate.SetMaxTriggerCount(1)

Pattern 3 — Sync a trigger event and a timer urgency signal

On the clifftop above the lagoon, a pressure plate must be triggered and the urgency-mode alarm must sound before the cannon fires. Both conditions must be true (sync), not just one (race).

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# clifftop_cannon_arm — fires cannon only after plate triggered AND urgency mode starts.
clifftop_cannon_arm := class(creative_device):

    @editable ClifftopPlate  : trigger_device   = trigger_device{}
    @editable AlarmTimer     : timer_device      = timer_device{}
    @editable CannonTrigger  : trigger_device    = trigger_device{}

    OnBegin<override>()<suspends>:void =
        AlarmTimer.Start()

        loop:
            # Both conditions must be satisfied before the cannon fires:
            # 1. A player steps on the clifftop plate
            # 2. The alarm timer enters urgency mode (e.g. last 10 seconds)
            sync:
                block:
                    ClifftopPlate.TriggeredEvent.Await()
                block:
                    AlarmTimer.StartUrgencyModeEvent.Await()

            # Both done — fire the cannon!
            CannonTrigger.Trigger()

            # Reset for next round
            AlarmTimer.ResetForAll()
            AlarmTimer.Start()
            Sleep(3.0)

Gotchas

1. sync vs race — know which one you need

sync waits for all branches. race stops at the first to finish and cancels the rest. Mixing them up is the #1 bug: if you use race where you meant sync, your vault opens the moment any plate is stepped on instead of all three.

2. TriggeredEvent is listenable(?agent) — unwrap before using the agent

The event sends ?agent (an optional). If you need the agent who triggered it, you must unwrap:

MaybeAgent := DockPlateA.TriggeredEvent.Await()
if (A := MaybeAgent?):
    # use A here

Forgetting the ? unwrap causes a type error at compile time.

3. Each sync branch needs its own block: (or a function call)

In Verse, sync takes a block of expressions separated by newlines at the same indent level. Wrap each branch in block: to keep them unambiguous, especially when each branch is more than one line.

4. timer_device events are also listenable(?agent) — same unwrap rule

SuccessEvent, FailureEvent, and StartUrgencyModeEvent all send ?agent. If you only care that the event fired (not who triggered it), you can Await() and discard the result — but if you need the agent, unwrap it.

5. SetMaxTriggerCount clamps to [0, 20]

Passing a value outside that range is silently clamped. 0 means unlimited — not zero triggers. Use 1 if you want exactly one trigger per cycle.

6. Devices must be @editable fields — not local variables

You cannot write trigger_device{}.TriggeredEvent.Await() in a function body. Every device you interact with must be declared as an @editable field on your creative_device class and wired up in the UEFN Details panel.

7. Sleep(0.0) yields one tick — useful inside loops

If you have a tight loop that checks conditions without any natural Await(), add Sleep(0.0) to yield control back to the engine each frame and prevent hangs.

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 →