Overview
The timer_device tracks elapsed or remaining time and signals your Verse code when something meaningful happens: the clock hits zero (SuccessEvent or FailureEvent), or the timer enters its final-stretch Urgency Mode (StartUrgencyModeEvent). You can run it as a countdown (time runs out → failure) or a stopwatch (player finishes before time → success), and you control every moment of its lifecycle from Verse: start, pause, resume, reset, or force-complete.
Reach for timer_device when you need:
- A round countdown that locks doors when it expires
- A per-player speedrun lap board
- A bomb-defusal sequence that pauses while a cutscene plays
- Urgency-mode music or VFX that trigger in the final seconds
API Reference
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. |
Walkthrough
Scenario: An escape room. A button_device starts the clock. If the player presses a second button (the "solve" button) before time runs out, Complete fires SuccessEvent and a barrier drops. If the clock hits zero first, FailureEvent fires and the room resets. When urgency mode kicks in, we pause briefly to flash a warning, then resume.
escape_room_manager := class(creative_device):
# Wire these up in the UEFN editor
@editable
RoomTimer : timer_device = timer_device{}
@editable
StartButton : button_device = button_device{}
@editable
SolveButton : button_device = button_device{}
@editable
ExitBarrier : barrier_device = barrier_device{}
# Called once when the experience begins
OnBegin<override>()<suspends> : void =
# Subscribe to timer lifecycle events
RoomTimer.SuccessEvent.Subscribe(OnRoomSolved)
RoomTimer.FailureEvent.Subscribe(OnRoomFailed)
RoomTimer.StartUrgencyModeEvent.Subscribe(OnUrgency)
# Subscribe to button interactions
StartButton.InteractedWithEvent.Subscribe(OnStartPressed)
SolveButton.InteractedWithEvent.Subscribe(OnSolvePressed)
# Make sure the exit is locked at the start
ExitBarrier.Enable()
# Player pressed the START button — kick off the clock
OnStartPressed(Agent : agent) : void =
RoomTimer.Start(Agent) # per-agent start (respects IsStatePerAgent)
# Player pressed the SOLVE button — force a success
OnSolvePressed(Agent : agent) : void =
RoomTimer.Complete(Agent) # triggers SuccessEvent for this agent
# Timer fired SuccessEvent — player beat the clock
OnRoomSolved(Agent : ?agent) : void =
if (A := Agent?):
ExitBarrier.Disable() # drop the barrier for everyone
Print("Escape room solved!")
else:
ExitBarrier.Disable() # triggered globally
# Timer fired FailureEvent — clock hit zero
OnRoomFailed(Agent : ?agent) : void =
RoomTimer.ResetForAll() # reset clock for all players
Print("Time's up! Resetting...")
# Timer entered urgency mode — pause briefly, then resume
OnUrgency(Agent : ?agent) : void =
spawn:
UrgencyPause()
UrgencyPause()<suspends> : void =
RoomTimer.PauseForAll() # freeze the clock
Sleep(1.5) # hold for dramatic effect
RoomTimer.ResumeForAll() # let it tick again
Line-by-line highlights:
| Line | What it does |
|---|---|
RoomTimer.SuccessEvent.Subscribe(OnRoomSolved) |
Registers a handler that fires when Complete() is called or the stopwatch finishes in time |
RoomTimer.FailureEvent.Subscribe(OnRoomFailed) |
Fires when a countdown reaches zero without being completed |
RoomTimer.StartUrgencyModeEvent.Subscribe(OnUrgency) |
Fires when the timer crosses into its configured urgency threshold |
RoomTimer.Start(Agent) |
Starts the clock for one specific player (per-agent mode) |
RoomTimer.Complete(Agent) |
Immediately marks the timer successful for that player |
RoomTimer.ResetForAll() |
Stops and resets the clock for every player in the session |
RoomTimer.PauseForAll() / RoomTimer.ResumeForAll() |
Freeze and unfreeze the clock globally |
if (A := Agent?): |
Safe unwrap of the ?agent the event sends — always do this before using the agent |
Common patterns
Pattern 1 — Dynamic duration: set the clock length at runtime
Sometimes you want difficulty scaling — easy mode gets 120 s, hard mode gets 45 s. Use SetActiveDuration while the timer is running, or set it before Start.
difficulty_timer_manager := class(creative_device):
@editable
RaceTimer : timer_device = timer_device{}
@editable
HardModeButton : button_device = button_device{}
# How long the race lasts in easy mode (seconds)
EasyDuration : float = 120.0
HardDuration : float = 45.0
OnBegin<override>()<suspends> : void =
HardModeButton.InteractedWithEvent.Subscribe(OnHardModeChosen)
RaceTimer.SuccessEvent.Subscribe(OnRaceSuccess)
RaceTimer.FailureEvent.Subscribe(OnRaceFailure)
# Default: start with easy duration for all players
RaceTimer.StartForAll()
OnHardModeChosen(Agent : agent) : void =
# Shrink the remaining time for this specific agent
RaceTimer.SetActiveDuration(HardDuration, Agent)
OnRaceSuccess(Agent : ?agent) : void =
# Report how long the max duration was
MaxSecs : float = RaceTimer.GetMaxDuration()
Print("Race complete! Max allowed: {MaxSecs}s")
OnRaceFailure(Agent : ?agent) : void =
RaceTimer.ResetForAll()
Print("Race failed — resetting for all players.")
Key calls: StartForAll(), SetActiveDuration(float, agent), GetMaxDuration().
Pattern 2 — Persistence: save and load a player's best lap
For a speedrun leaderboard you want to persist each player's timer state across sessions. Save and Load handle this.
lap_persistence_manager := class(creative_device):
@editable
LapTimer : timer_device = timer_device{}
@editable
LapStartTrigger : trigger_device = trigger_device{}
@editable
LapEndTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
LapStartTrigger.TriggeredEvent.Subscribe(OnLapStart)
LapEndTrigger.TriggeredEvent.Subscribe(OnLapEnd)
LapTimer.SuccessEvent.Subscribe(OnLapSuccess)
OnLapStart(Agent : ?agent) : void =
if (A := Agent?):
# Load any previously saved state for this player
LapTimer.Load(A)
LapTimer.Start(A)
OnLapEnd(Agent : ?agent) : void =
if (A := Agent?):
# Force success so SuccessEvent fires
LapTimer.Complete(A)
OnLapSuccess(Agent : ?agent) : void =
if (A := Agent?):
# Persist the completed lap data for this player
LapTimer.Save(A)
ActiveSecs : float = LapTimer.GetActiveDuration(A)
Print("Lap saved! Active duration was: {ActiveSecs}s")
Key calls: Load(agent), Start(agent), Complete(agent), Save(agent), GetActiveDuration(agent).
Pattern 3 — Per-agent vs global mode check, plus urgency wiring
Before branching on per-player vs. global logic, check IsStatePerAgent. This pattern also shows Enable / Disable gating.
adaptive_timer_manager := class(creative_device):
@editable
ChallengeTimer : timer_device = timer_device{}
@editable
ActivateTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
# Disable the timer until a trigger activates it
ChallengeTimer.Disable()
ActivateTrigger.TriggeredEvent.Subscribe(OnActivate)
ChallengeTimer.StartUrgencyModeEvent.Subscribe(OnUrgencyStarted)
OnActivate(Agent : ?agent) : void =
ChallengeTimer.Enable() # allow the device to receive signals
# Branch on whether state is tracked per-player or globally
if (ChallengeTimer.IsStatePerAgent[]):
if (A := Agent?):
ChallengeTimer.Start(A) # per-player start
else:
ChallengeTimer.StartForAll() # global start
OnUrgencyStarted(Agent : ?agent) : void =
# Urgency mode began — could trigger VFX, music, etc.
Print("Urgency mode active!")
# Demonstrate Pause then Resume for all
spawn:
UrgencySequence()
UrgencySequence()<suspends> : void =
ChallengeTimer.PauseForAll()
Sleep(0.5)
ChallengeTimer.ResumeForAll()
Key calls: Disable(), Enable(), IsStatePerAgent[], Start(agent), StartForAll(), PauseForAll(), ResumeForAll().
Gotchas
1. ?agent events always need unwrapping
SuccessEvent, FailureEvent, and StartUrgencyModeEvent all send ?agent (an optional agent). If you try to use the value directly without unwrapping, the compiler rejects it. Always guard:
OnSuccess(Agent : ?agent) : void =
if (A := Agent?):
# A is now a concrete `agent` — safe to use
2. IsStatePerAgent uses the [] failure-context syntax
IsStatePerAgent is a <decides> function — it succeeds or fails rather than returning a bool. Call it inside an if with square brackets:
if (ChallengeTimer.IsStatePerAgent[]):
# per-agent path
Not if (ChallengeTimer.IsStatePerAgent() = true) — that won't compile.
3. SetActiveDuration only works while the timer is active
Calling SetActiveDuration on a timer that hasn't been started yet has no effect. Start the timer first, then adjust the remaining time.
4. PauseForAll / ResumeForAll inside a spawn
If you call PauseForAll() followed by Sleep() followed by ResumeForAll() in a handler, you must spawn a coroutine — event handlers are not <suspends> contexts. Forgetting the spawn causes a compile error.
5. message parameters need localized wrappers
If you ever call button_device.SetInteractionText alongside your timer logic, remember that message is not a plain string. Declare a localizer helper:
MyText<localizes>(S : string) : message = "{S}"
Then pass MyText("Press to start timer"). There is no StringToMessage function.
6. Enable / Disable gate ALL signals
A disabled timer_device will not fire SuccessEvent, FailureEvent, or StartUrgencyModeEvent — even if the underlying clock would have expired. Always call Enable() before Start() if you previously disabled the device.
7. ResetForAll stops the clock
ResetForAll() both resets the time AND stops the timer. If you want to restart immediately after a reset, call Start() or StartForAll() right after.