Overview
sync is a Verse concurrency expression that runs two or more <suspends> branches simultaneously and only continues past the sync block once every branch has completed. It is the parallel counterpart to race (which stops as soon as the first branch finishes).
The game problem it solves: Many island moments need several things to happen at the same time. A player steps onto a dock pressure plate → you want to:
- Show a HUD countdown message that auto-hides after a few seconds, AND
- Run a trigger cooldown loop that limits how often the plate can fire
Without sync you'd have to chain those sequentially, making the second thing wait for the first. With sync both branches run in parallel and the code after the sync block only executes once both are truly done.
When to reach for it:
- Two independent timers that must both expire before the next phase starts
- Showing a HUD message while simultaneously waiting for a trigger reset
- Animating a cinematic AND waiting for a player input at the same time
- Any "all of the above must finish" parallel gate
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. |
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):
hud_message_device
Used to show custom HUD messages to one or more
agents.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
hud_message_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ShowMessageEvent |
ShowMessageEvent<public>:listenable(agent) |
Called when a Message has been Shown on-screen. Returns an Agent if it was Shown on a specified Agent's screen. |
HideMessageEvent |
HideMessageEvent<public>:listenable(agent) |
Called when a Message has been Hidden on-screen. Returns an Agent if it was Hidden from a specified Agent's screen. |
ClearAllMessagesEvent |
ClearAllMessagesEvent<public>:listenable(agent) |
Called when all queued Messages from all players that are affected by this HUD Message Device have been cleared. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Show |
Show<public>(Agent:agent):void |
Shows the currently set HUD Message on Agents screen. Will replace any previously active message. Use this when the device is setup to target specific agents. |
Show |
Show<public>():void |
Shows the currently set Message HUD message on screen. Will replace any previously active message. |
Hide |
Hide<public>():void |
Hides the HUD message. |
Hide |
Hide<public>(Agent:agent):void |
Hides the currently set HUD Message on Agents screen. Use this when the device is setup to target specific agents. |
Show |
Show<public>(Agent:agent, Message:message, ?DisplayTime:float |
Displays a Custom message to a specific Agent that you define.Setting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device. |
Show |
Show<public>(Message:message, ?DisplayTime:float |
Displays a Custom message that you define for all PlayersSetting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device. |
SetDisplayTime |
SetDisplayTime<public>(Time:float):void |
Sets the time (in seconds) the HUD message will be displayed. 0.0 will display the HUD message persistently. |
GetDisplayTime |
GetDisplayTime<public>()<transacts>:float |
Returns the time (in seconds) for which the HUD message will be displayed. 0.0 means the message is displayed persistently. |
SetText |
SetText<public>(Text:message):void |
Sets the Message to be displayed when the HUD message is activated. Text is clamped to 150 characters. |
ClearAllMessages |
ClearAllMessages<public>():void |
Clears all queued Messages from all players that are affected by this HUD Message Device. |
Walkthrough
Scenario: The Lagoon Dock Alarm
A 2D cel-shaded pirate island. A pressure-plate trigger sits on the dock. When a player steps on it:
- A HUD message flashes "⚓ Dock Alarm! Pirates incoming!" for 4 seconds.
- The trigger is disabled for 6 seconds (cooldown) so it can't be spammed.
- Both things happen at the same time via
sync. Only after both finish does the trigger re-enable.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Attach this creative_device to a Verse Device actor in UEFN.
# Wire DockPlate -> a trigger_device placed on the dock.
# Wire AlarmHUD -> a hud_message_device set to show for 0 sec (we control duration in code).
lagoon_dock_alarm := class(creative_device):
# The pressure-plate trigger on the dock
@editable DockPlate : trigger_device = trigger_device{}
# HUD message device — set Display Time to 0 in its properties (we drive it from code)
@editable AlarmHUD : hud_message_device = hud_message_device{}
# Localised message helper
AnchorMessage<localizes>(S : string) : message = "{S}"
# Called once when the island starts
OnBegin<override>()<suspends> : void =
# Limit the plate to 1 trigger per cooldown window (max 1, resets via Enable below)
DockPlate.SetMaxTriggerCount(1)
# Subscribe — handler fires every time the plate is stepped on
DockPlate.TriggeredEvent.Subscribe(OnDockStepped)
# Handler: called by TriggeredEvent — receives ?agent
OnDockStepped(MaybeAgent : ?agent) : void =
# Disable the plate immediately so it can't double-fire
DockPlate.Disable()
# Kick off the parallel alarm logic in a new async task
spawn { RunAlarm(MaybeAgent) }
# Async helper: runs the HUD flash and the cooldown timer in parallel
RunAlarm(MaybeAgent : ?agent)<suspends> : void =
# Prepare the alarm text
AlarmHUD.SetText(AnchorMessage("⚓ Dock Alarm! Pirates incoming!"))
# sync: both branches run at the same time.
# We only proceed past this block when BOTH branches are done.
sync:
# Branch 1 — show the HUD message for 4 seconds, then hide it
block:
if (A := MaybeAgent?):
AlarmHUD.Show(A)
else:
AlarmHUD.Show()
Sleep(4.0)
AlarmHUD.Hide()
# Branch 2 — enforce a 6-second cooldown on the trigger
block:
Sleep(6.0)
# Both branches finished — re-arm the plate
DockPlate.SetMaxTriggerCount(1)
DockPlate.Enable()
Line-by-line explanation
| Lines | What's happening |
|---|---|
@editable DockPlate |
Declares the trigger_device field so UEFN can wire it to the placed device. |
@editable AlarmHUD |
Declares the hud_message_device field. |
AnchorMessage<localizes> |
message parameters must be localized values — this helper converts a string literal. |
DockPlate.SetMaxTriggerCount(1) |
Caps the plate at 1 fire per enable cycle. |
DockPlate.TriggeredEvent.Subscribe(OnDockStepped) |
Wires the event to our handler. |
OnDockStepped(MaybeAgent : ?agent) |
TriggeredEvent is listenable(?agent) so the param is ?agent. |
DockPlate.Disable() |
Prevents re-entry while the alarm is running. |
spawn { RunAlarm(MaybeAgent) } |
Launches the async alarm logic without blocking OnDockStepped. |
sync: |
The key expression. Everything inside runs in parallel. |
| Branch 1 | Shows the HUD (per-agent if we have one, global otherwise), waits 4 s, hides it. |
| Branch 2 | Waits 6 s (the longer cooldown). |
After sync |
Re-arms the plate — only reached once BOTH branches are done (i.e., after 6 s). |
Common patterns
Pattern 1 — Querying trigger state before a sync cooldown
Check how many triggers remain and log it to a HUD, then run a dual-branch sync that resets the trigger and updates the display.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Shows remaining trigger count in HUD, then runs a sync cooldown + display clear.
lagoon_trigger_inspector := class(creative_device):
@editable InspectTrigger : trigger_device = trigger_device{}
@editable StatusHUD : hud_message_device = hud_message_device{}
CountMsg<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Give the trigger a max of 5 uses with a 2-second reset delay
InspectTrigger.SetMaxTriggerCount(5)
InspectTrigger.SetResetDelay(2.0)
InspectTrigger.TriggeredEvent.Subscribe(OnInspect)
OnInspect(MaybeAgent : ?agent) : void =
spawn { ShowCountAndCooldown(MaybeAgent) }
ShowCountAndCooldown(MaybeAgent : ?agent)<suspends> : void =
# Read current state
Remaining := InspectTrigger.GetTriggerCountRemaining()
MaxCount := InspectTrigger.GetMaxTriggerCount()
ResetSec := InspectTrigger.GetResetDelay()
# Build a status string and push it to the HUD
StatusHUD.SetText(CountMsg("Triggers left — check your HUD!"))
if (A := MaybeAgent?):
StatusHUD.Show(A)
else:
StatusHUD.Show()
# sync: hide the HUD after 3 s AND wait for the reset delay simultaneously
sync:
block:
Sleep(3.0)
AlarmHide(MaybeAgent)
block:
Sleep(ResetSec)
AlarmHide(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
StatusHUD.Hide(A)
else:
StatusHUD.Hide()
Pattern 2 — Transmit-delay inspection + parallel HUD flash
Read GetTransmitDelay and GetDisplayTime, then use sync so the HUD message and a transmit-delay sleep run together. This pattern is useful when you need the HUD to stay up exactly as long as the transmit delay.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Reads the trigger's transmit delay and the HUD's display time,
# then syncs a HUD flash with the transmit window.
lagoon_transmit_sync := class(creative_device):
@editable SignalTrigger : trigger_device = trigger_device{}
@editable FlashHUD : hud_message_device = hud_message_device{}
TransmitMsg<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Configure a 3-second transmit delay on the trigger
SignalTrigger.SetTransmitDelay(3.0)
SignalTrigger.TriggeredEvent.Subscribe(OnSignal)
OnSignal(MaybeAgent : ?agent) : void =
spawn { SyncFlashWithTransmit(MaybeAgent) }
SyncFlashWithTransmit(MaybeAgent : ?agent)<suspends> : void =
TransmitSec := SignalTrigger.GetTransmitDelay()
HudSec := FlashHUD.GetDisplayTime()
# Show a "Signal transmitting..." message
FlashHUD.SetText(TransmitMsg("📡 Signal transmitting to the cove..."))
# sync: HUD flash branch vs transmit-delay branch — both run in parallel
sync:
# Branch A: show HUD, wait for its display time, then hide
block:
if (A := MaybeAgent?):
FlashHUD.Show(A)
else:
FlashHUD.Show()
Sleep(HudSec)
if (A := MaybeAgent?):
FlashHUD.Hide(A)
else:
FlashHUD.Hide()
# Branch B: wait for the full transmit delay
block:
Sleep(TransmitSec)
# Only here once BOTH the HUD has hidden AND the transmit delay has elapsed
# Safe to fire the trigger programmatically now
SignalTrigger.Trigger()
Pattern 3 — Disable / Enable gate with a ClearAllMessages cleanup
A one-shot trigger on the clifftop: fire once, clear all HUD messages and wait for a cooldown in parallel, then re-enable.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# One-shot clifftop trigger: fires once, clears HUD and waits cooldown in sync, then re-enables.
clifftop_oneshot := class(creative_device):
@editable ClifftopTrigger : trigger_device = trigger_device{}
@editable AlertHUD : hud_message_device = hud_message_device{}
AlertMsg<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
ClifftopTrigger.SetMaxTriggerCount(1)
ClifftopTrigger.TriggeredEvent.Subscribe(OnClifftopFired)
OnClifftopFired(MaybeAgent : ?agent) : void =
ClifftopTrigger.Disable()
spawn { CooldownAndClear(MaybeAgent) }
CooldownAndClear(MaybeAgent : ?agent)<suspends> : void =
AlertHUD.SetText(AlertMsg("🏴☠️ Clifftop beacon lit! Retreat to the cove!"))
if (A := MaybeAgent?):
AlertHUD.Show(A)
else:
AlertHUD.Show()
# sync: clear all messages after 5 s AND enforce an 8 s cooldown
sync:
block:
Sleep(5.0)
AlertHUD.ClearAllMessages()
block:
Sleep(8.0)
# Re-arm for next round
ClifftopTrigger.SetMaxTriggerCount(1)
ClifftopTrigger.Enable()
Gotchas
1. sync requires every branch to be <suspends>
sync only makes sense with async branches. Each sub-expression inside sync must be (or call) a <suspends> expression. If you accidentally put a plain synchronous call as the only thing in a branch, it completes instantly — which is fine, but it means that branch doesn't actually contribute any wait time. Wrap non-suspending setup in block: alongside a Sleep if you need the branch to hold.
2. ?agent unwrap — always check before calling agent-specific methods
TriggeredEvent is listenable(?agent), so your handler receives MaybeAgent : ?agent. You must unwrap it with if (A := MaybeAgent?): before passing it to AlarmHUD.Show(A) or AlarmHUD.Hide(A). Passing a ?agent directly where an agent is expected is a compile error.
3. message parameters are NOT raw strings
hud_message_device.SetText and the Show(Agent, Message, ...) overload take a message, not a string. You cannot pass a string literal directly. Declare a localized helper:
MyText<localizes>(S : string) : message = "{S}"
Then call SetText(MyText("Hello lagoon!")). There is no StringToMessage function.
4. SetMaxTriggerCount clamps to [0, 20]
Passing a value outside [0, 20] is silently clamped. 0 means unlimited triggers — not zero triggers. If you want a one-shot trigger, pass 1.
5. spawn vs direct <suspends> call in event handlers
Event handler methods (subscribed via .Subscribe(...)) are not themselves <suspends>, so you cannot Sleep or call sync directly inside them. Always spawn { MyAsyncHelper(...) } to launch async work from a synchronous handler.
6. sync waits for the longest branch
Unlike race (which exits on the first branch to finish), sync waits for all branches. If one branch sleeps for 10 seconds and another for 2 seconds, the code after sync runs after 10 seconds. Design your branch durations intentionally — the longest branch is your bottleneck.
7. GetTriggerCountRemaining returns 0 when unlimited
If GetMaxTriggerCount() returns 0 (unlimited), then GetTriggerCountRemaining() also returns 0 — not some large number. Check GetMaxTriggerCount() first if you need to distinguish "unlimited" from "zero remaining".