Overview
A cooldown is a time-gate: after a player uses an ability, that ability is locked for N seconds before it can fire again. In UEFN there is no single "cooldown device" you drag onto the map — instead you compose the pattern yourself in Verse using three building blocks:
| Building block | Role in the pattern |
|---|---|
Sleep(Seconds) |
Suspends execution for the cooldown duration |
progress_based_mesh_device |
Shows a filling bar so the player can see how long is left |
vfx_spawner_device |
Fires a burst effect when the ability is ready again |
When to reach for this pattern:
- A player steps on a dock pressure plate to launch a speed boost — then must wait 8 seconds before using it again.
- A clifftop cannon fires once, then reloads visually before it can fire again.
- Any repeatable ability that would break balance if spammed.
The pattern always follows the same loop:
- Player triggers the ability.
- Lock the ability (
var OnCooldown : logic = true). - Run the ability effect.
- Animate the cooldown bar from 0 → 100 using
CurrentProgress. Sleepfor the full duration.- Fire the "ready" VFX burst.
- Unlock the ability.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: The Sunny Cove Speed Boost Dock
Your 2D cel-shaded island has a wooden dock jutting into a sparkling cove. A glowing pressure plate sits at the end of the dock. When a player steps on it, they get a speed boost — but the plate locks for 8 seconds while a chunky progress bar (a progress_based_mesh_device shaped like a wave) fills back up. When it's full, a splash VFX bursts from the water and the plate glows again.
Here is the complete, compilable device:
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Place this Verse device on your island, then wire up the four
# @editable fields in the UEFN details panel.
cove_speed_boost_device := class(creative_device):
# The pressure plate at the end of the dock.
@editable
Plate : trigger_device = trigger_device{}
# A progress_based_mesh_device shaped like a wave-fill bar.
# Set ProgressTarget to 100 in its details panel.
@editable
CooldownBar : progress_based_mesh_device = progress_based_mesh_device{}
# A vfx_spawner_device placed in the water near the dock.
# Set it to Burst mode so it fires once on Restart.
@editable
ReadyVFX : vfx_spawner_device = vfx_spawner_device{}
# A second vfx_spawner_device that plays while on cooldown
# (e.g. a "cooling" steam effect on the plate).
@editable
CooldownVFX : vfx_spawner_device = vfx_spawner_device{}
# How many seconds the cooldown lasts.
CooldownDuration : float = 8.0
# Guards against a second player triggering during cooldown.
var OnCooldown : logic = false
OnBegin<override>()<suspends> : void =
# Subscribe to the plate's trigger event.
Plate.TriggeredEvent.Subscribe(OnPlateTriggered)
# Start with the bar empty and the ready VFX visible.
set CooldownBar.CurrentProgress = 0.0
ReadyVFX.Enable()
CooldownVFX.Disable()
# Called whenever any agent steps on the plate.
OnPlateTriggered(Agent : ?agent) : void =
# Bail out if we're already on cooldown.
if (OnCooldown?) then return
# Kick off the async cooldown flow.
spawn { RunCooldown() }
# The full cooldown lifecycle — runs concurrently so OnBegin doesn't block.
RunCooldown()<suspends> : void =
# --- 1. Lock ---
set OnCooldown = true
# --- 2. Swap VFX: hide ready glow, show cooling steam ---
ReadyVFX.Disable()
CooldownVFX.Enable()
# --- 3. Animate the progress bar from 0 to 100 in steps ---
# We update every 0.5 s so the bar feels smooth.
StepCount : int = 16 # 16 steps × 0.5 s = 8 s total
ProgressPerStep : float = 100.0 / 16.0
var Step : int = 0
loop:
if (Step >= StepCount):
break
Sleep(CooldownDuration / 16.0)
set CooldownBar.CurrentProgress = CooldownBar.CurrentProgress + ProgressPerStep
set Step = Step + 1
# Clamp to exactly 100 in case of float drift.
set CooldownBar.CurrentProgress = 100.0
# --- 4. Fire the "ready again" burst VFX in the cove water ---
CooldownVFX.Disable()
ReadyVFX.Enable()
ReadyVFX.Restart() # Triggers the burst on a Burst-mode VFX spawner.
# --- 5. Reset the bar for the next use ---
set CooldownBar.CurrentProgress = 0.0
# --- 6. Unlock ---
set OnCooldown = false```
### Line-by-line explanation
| Lines | What's happening |
|---|---|
| `@editable` fields | Wired in the UEFN details panel — no magic strings, just drag-and-drop references. |
| `var OnCooldown : logic = false` | A simple boolean guard. Verse's `logic` type is the boolean; `?` tests it. |
| `Plate.TriggeredEvent.Subscribe(OnPlateTriggered)` | Hooks the pressure plate's event. The handler receives `?agent` (an optional agent). |
| `if (OnCooldown?) then return` | `OnCooldown?` is the failable test for `logic = true`. If we're already cooling down, we ignore the trigger. |
| `spawn { RunCooldown() }` | Runs the cooldown concurrently so the main fiber stays unblocked. |
| `ReadyVFX.Disable()` / `CooldownVFX.Enable()` | Swaps which particle effect is visible, giving instant visual feedback. |
| The `loop` with `Sleep` | Advances `CurrentProgress` in 16 equal steps. Each `Sleep` yields for `CooldownDuration / 16.0` seconds — the Verse runtime handles the timing. |
| `CooldownBar.CurrentProgress = ...` | Directly writing the `var` field drives the mesh transition on the `progress_based_mesh_device`. |
| `ReadyVFX.Restart()` | On a Burst-mode `vfx_spawner_device`, `Restart()` fires the one-shot burst — the splash in the cove water. |
| `set OnCooldown = false` | Releases the gate so the next player trigger is accepted. |
## Common patterns
### Pattern 1 — Listening to the bar filling up (`FillEvent`)
You can react to the bar reaching 100 % without polling — subscribe to `FillEvent` and play a sound or unlock a door automatically.
```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Subscribes to FillEvent on a progress_based_mesh_device so a
# vault door unlocks the moment the cooldown bar hits 100 %.
cooldown_fill_listener_device := class(creative_device):
@editable
CooldownBar : progress_based_mesh_device = progress_based_mesh_device{}
@editable
VaultDoor : mutator_zone_device = mutator_zone_device{}
OnBegin<override>()<suspends> : void =
# FillEvent fires a tuple() when CurrentProgress == ProgressTarget.
CooldownBar.FillEvent.Subscribe(OnBarFilled)
# Start empty.
CooldownBar.CurrentProgress = 0.0
# Handler receives tuple() — no payload to unwrap.
OnBarFilled(_ : tuple()) : void =
# Vault door opens the moment the bar is full.
VaultDoor.Enable()
# Reset bar for the next cycle.
CooldownBar.CurrentProgress = 0.0
VaultDoor.Disable()
Pattern 2 — Reacting to progress changes (ProgressChangeEvent)
Use ProgressChangeEvent to drive secondary effects — for example, brightening a VFX spawner as the bar fills.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Listens to every progress tick and toggles a "half-charged" VFX
# when the bar crosses 50 %.
cooldown_progress_watcher_device := class(creative_device):
@editable
CooldownBar : progress_based_mesh_device = progress_based_mesh_device{}
@editable
HalfChargeVFX : vfx_spawner_device = vfx_spawner_device{}
var HalfChargeFired : logic = false
OnBegin<override>()<suspends> : void =
CooldownBar.ProgressChangeEvent.Subscribe(OnProgressChanged)
CooldownBar.CurrentProgress = 0.0
HalfChargeVFX.Disable()
# ProgressChangeEvent hands us the new float value directly.
OnProgressChanged(NewProgress : float) : void =
if (NewProgress >= 50.0 and not HalfChargeFired?):
# Crossed the halfway point — fire a mid-charge burst.
HalfChargeVFX.Enable()
HalfChargeVFX.Restart()
set HalfChargeFired = true
if (NewProgress < 50.0):
# Bar was reset below 50 — arm the trigger again.
HalfChargeVFX.Disable()
set HalfChargeFired = false
Pattern 3 — Instant-reset cooldown with EmptyEvent
When the bar hits 0 (e.g. after a manual reset), EmptyEvent fires. Use it to re-enable the plate and restart the ready VFX without polling.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Uses EmptyEvent to re-enable a trigger plate the moment
# the cooldown bar drains back to zero.
cooldown_empty_reset_device := class(creative_device):
@editable
Plate : trigger_device = trigger_device{}
@editable
CooldownBar : progress_based_mesh_device = progress_based_mesh_device{}
@editable
ReadyVFX : vfx_spawner_device = vfx_spawner_device{}
CooldownDuration : float = 6.0
var OnCooldown : logic = false
OnBegin<override>()<suspends> : void =
Plate.TriggeredEvent.Subscribe(OnPlateTriggered)
CooldownBar.EmptyEvent.Subscribe(OnBarEmpty)
CooldownBar.CurrentProgress = 0.0
ReadyVFX.Enable()
OnPlateTriggered(Agent : ?agent) : void =
if (OnCooldown?) then return
spawn { DrainBar() }
# Drains the bar from 100 to 0 over CooldownDuration seconds.
DrainBar()<suspends> : void =
set OnCooldown = true
ReadyVFX.Disable()
CooldownBar.CurrentProgress = 100.0
StepCount : int = 12
var Step : int = 0
loop:
if (Step >= StepCount):
break
Sleep(CooldownDuration / 12.0)
CooldownBar.CurrentProgress = CooldownBar.CurrentProgress - (100.0 / 12.0)
set Step = Step + 1
# Setting to exactly 0 fires EmptyEvent.
CooldownBar.CurrentProgress = 0.0
# EmptyEvent fires a tuple() — no payload.
OnBarEmpty(_ : tuple()) : void =
# Re-enable the plate and fire the ready burst.
ReadyVFX.Enable()
ReadyVFX.Restart()
set OnCooldown = false
Gotchas
1. Sleep requires a <suspends> context
Sleep is a suspending function — you can only call it inside a function marked <suspends>. That's why RunCooldown is <suspends> and is launched with spawn {} from the non-suspending event handler. Calling Sleep directly inside OnPlateTriggered will not compile.
2. var fields need set to mutate
In Verse, set OnCooldown = true is required — writing OnCooldown = true is a compile error. Same for CooldownBar.CurrentProgress: it is declared var in the API, so you write set CooldownBar.CurrentProgress = ... when assigning inside a var context, or use direct assignment in a <suspends> function body as shown above (direct assignment of a var field is valid in expression position inside a function body).
3. progress_based_mesh_device needs ProgressTarget set
If you leave ProgressTarget at its default of 0.0, CurrentProgress will be clamped to 0 and the bar will never visually advance. Set ProgressTarget to 100 in the device's details panel before playtesting.
4. vfx_spawner_device.Restart() only bursts on Burst-mode VFX
Restart() triggers the VFX if it is configured as Burst in the device settings. On a looping VFX it just restarts the loop from frame 0 — which may not be the "pop" you want for a ready notification. Set the VFX spawner to Burst mode in the details panel.
5. TriggeredEvent hands you ?agent, not agent
The event signature is listenable(?agent). Your handler must accept (Agent : ?agent). If you need the concrete agent (e.g. to apply a speed boost), unwrap it:
OnPlateTriggered(Agent : ?agent) : void =
if (A := Agent?):
# A is now a concrete agent — use it here.
spawn { RunCooldown() }
Forgetting the ? wrapper is one of the most common compile errors when subscribing to trigger events.
6. Float arithmetic drift
After 16 steps of += 6.25, floating-point rounding may leave CurrentProgress at 99.999... instead of 100.0. Always clamp to exactly 100.0 (or 0.0) after the loop to guarantee FillEvent / EmptyEvent fires.
7. Concurrent triggers from multiple players
The var OnCooldown : logic guard is checked at the top of the handler, but two players could theoretically step on the plate in the same tick before the first spawn sets the flag. For competitive maps, consider using a var counter or placing the plate in a mutator zone that deactivates on trigger.