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.