Overview
The score manager device is the device you reach for whenever your game needs to give players points under script control. Instead of a static "step on this plate, get 10 points" setup, Verse lets you:
- Decide when points are granted (
Activate). - Decide how many points the next payout is worth (
SetScoreAward,Increment,Decrement,SetToAgentScore). - Read a player's running total (
GetCurrentScore,GetScoreAward). - React to payouts and to the device hitting its trigger cap (
ScoreOutputEvent,MaxTriggersEvent).
Reach for it when you want a coin pickup, a kill-streak combo bonus, an objective completion reward, or a leaderboard-style scoring loop — anything where points are awarded conditionally and you want full control of the amount.
Scoring alone is only half of most game modes, though. The natural partner is the timer device: it turns open-ended point collection into a round — score all you want, but only while the clock is running. From Verse you can Start, Pause, Resume, and reset a timer_device, and react to it finishing via SuccessEvent / FailureEvent. The final section of this article wires both devices into one cooperating system: a timed score round.
A key detail: if the device's Activating Team option is set to a specific team, you should call the agent overloads (e.g. Activate(Player)), because the agent's team decides whether they're allowed to affect the device. The no-arg overloads grant to the configured default.
API Reference
score_manager_device
Used to manipulate scores using in-experience triggers. If Activating Team is set to a specific team, then you should use the
agentoverloads of each function. Theagent's team will be used to determine if thatagentis allowed to affect the state of the device.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
score_manager_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
MaxTriggersEvent |
MaxTriggersEvent<public>:listenable(agent) |
Signaled when the this device reaches its maximum number of triggers as defined by Times Can Trigger. Sends the agent who last triggered the device. |
ScoreOutputEvent |
ScoreOutputEvent<public>:listenable(agent) |
Signaled when the this device awards points to an agent. Sends the agent who received the points. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Enable |
Enable<public>(Agent:agent):void |
Enables this device. |
Enable |
Enable<public>():void |
Enables this device. |
Disable |
Disable<public>(Agent:agent):void |
Disables this device. |
Disable |
Disable<public>():void |
Disables this device. |
Activate |
Activate<public>(Agent:agent):void |
Grant points to Agent. |
Activate |
Activate<public>():void |
Grants points. |
Increment |
Increment<public>(Agent:agent):void |
Increments the score quantity to be awarded by the next activation by 1. |
Increment |
Increment<public>():void |
Increments the score quantity to be awarded by the next activation by 1. |
Decrement |
Decrement<public>(Agent:agent):void |
Decrements the score quantity to be awarded by the next activation by 1. |
Decrement |
Decrement<public>():void |
Decrements the score quantity to be awarded by the next activation by 1. |
SetScoreAward |
SetScoreAward<public>(Value:int):void |
Sets the score to be awarded by the next activation to Value. |
GetScoreAward |
GetScoreAward<public>()<transacts>:int |
Returns the score to be awarded by the next activation. |
SetToAgentScore |
SetToAgentScore<public>(Agent:agent):void |
Sets the score to be awarded by the next activation to Agent's current score. |
GetCurrentScore |
GetCurrentScore<public>(Agent:agent)<transacts>:int |
Returns the current score for Agent. |
Walkthrough
Let's build a coin pickup combo system. A trigger_device represents a coin a player runs through. Each coin in a row makes the next coin worth 1 more point (a combo), and we read back the player's new total to show how scoring grew. We use Increment, GetScoreAward, Activate, GetCurrentScore, and we subscribe to ScoreOutputEvent.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Coin combo scorer: each coin grabbed makes the next coin worth more.
coin_combo_device := class(creative_device):
# The placed coin trigger the player runs through.
@editable
CoinTrigger : trigger_device = trigger_device{}
# The placed score manager that hands out the points.
@editable
Scorer : score_manager_device = score_manager_device{}
OnBegin<override>()<suspends>:void =
# React whenever a coin is touched.
CoinTrigger.TriggeredEvent.Subscribe(OnCoinGrabbed)
# React whenever the score manager actually pays out.
Scorer.ScoreOutputEvent.Subscribe(OnPointsAwarded)
# Start the next payout worth a known amount.
Scorer.SetScoreAward(10)
# trigger_device hands us a ?agent — unwrap it before use.
OnCoinGrabbed(MaybeAgent : ?agent) : void =
if (Player := MaybeAgent?):
# Bump the combo: next activation is worth +1.
Scorer.Increment(Player)
# Inspect what the next payout will be.
NextAward := Scorer.GetScoreAward()
Print("Combo! Next coin worth {NextAward}")
# Grant the points to this player right now.
Scorer.Activate(Player)
# ScoreOutputEvent fires after points land — read the new total.
OnPointsAwarded(Player : agent) : void =
Total := Scorer.GetCurrentScore(Player)
Print("Points awarded! Player total is now {Total}")
Line by line:
@editable CoinTrigger/@editable Scorer— these FIELDS are how Verse references the placed devices. You drop a trigger and a score manager in the level, then assign them in the device's Details panel. Without an@editablefield you cannot call the device's methods.OnBegin<override>()<suspends>:void— runs when the game starts. All subscriptions and setup happen here.CoinTrigger.TriggeredEvent.Subscribe(OnCoinGrabbed)— wires the player stepping on the coin to our handler method.Scorer.ScoreOutputEvent.Subscribe(OnPointsAwarded)— wires the device's payout signal to a second handler.Scorer.SetScoreAward(10)— sets the first payout to 10 points.OnCoinGrabbed(MaybeAgent : ?agent)—TriggeredEventis alistenable(?agent), so the handler receives an optional agent.if (Player := MaybeAgent?):unwraps it.Scorer.Increment(Player)— adds 1 to the pending award, building the combo. We use theagentoverload because the touching player's team gates the device.Scorer.GetScoreAward()— reads what the next activation will pay (note the<transacts>effect; it's a pure read).Scorer.Activate(Player)— actually grants the current pending award to this player.OnPointsAwarded(Player : agent)—ScoreOutputEventislistenable(agent)(NOT optional), so this hands us a plainagent. We readGetCurrentScore(Player)to print the running total.
Common patterns
Fixed objective reward with SetScoreAward + Activate
A capture-point that grants a flat 250 points to whoever completes it.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
objective_reward_device := class(creative_device):
@editable
CaptureButton : button_device = button_device{}
@editable
Scorer : score_manager_device = score_manager_device{}
OnBegin<override>()<suspends>:void =
CaptureButton.InteractedWithEvent.Subscribe(OnCaptured)
OnCaptured(Player : agent) : void =
# Lock the payout to exactly 250 then grant it.
Scorer.SetScoreAward(250)
Scorer.Activate(Player)
Copying one player's score onto a payout with SetToAgentScore
A "bounty" device that, when a hunter eliminates a target, awards the hunter points equal to the target's current score.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
bounty_device := class(creative_device):
@editable
ClaimButton : button_device = button_device{}
@editable
Scorer : score_manager_device = score_manager_device{}
OnBegin<override>()<suspends>:void =
ClaimButton.InteractedWithEvent.Subscribe(OnClaim)
OnClaim(Hunter : agent) : void =
# Set the next award equal to the hunter's own score, then double it.
Scorer.SetToAgentScore(Hunter)
Base := Scorer.GetScoreAward()
Print("Bounty base is {Base}")
Scorer.Activate(Hunter)
Reacting to the trigger cap with MaxTriggersEvent + Disable
When a limited-supply reward chest has paid out its configured number of times, shut it down.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
limited_chest_device := class(creative_device):
@editable
Scorer : score_manager_device = score_manager_device{}
OnBegin<override>()<suspends>:void =
Scorer.MaxTriggersEvent.Subscribe(OnExhausted)
OnExhausted(Agent : agent) : void =
Print("Chest exhausted by its last user.")
# No more points from this device.
Scorer.Disable()
Wiring in the timer: a timed score round
Scoring becomes a game the moment there's a clock on it. The timer device gives you that clock, and its Verse surface is a clean mirror of the score manager's: methods to drive it, events to react to.
The members we use (resolved from the live Epic digest, timer_device in Fortnite.digest.verse):
| Member | Signature | What it does |
|---|---|---|
Start / StartForAll |
Start<public>(Agent:agent):void / StartForAll<public>():void |
Starts the timer (for one agent, or for everyone). |
Pause / PauseForAll |
Pause<public>(Agent:agent):void / PauseForAll<public>():void |
Freezes the clock. |
Resume / ResumeForAll |
Resume<public>(Agent:agent):void / ResumeForAll<public>():void |
Lets a paused clock tick again. |
Reset / ResetForAll |
Reset<public>():void / ResetForAll<public>():void |
Puts the timer back to its base time and stops it. |
Complete / CompleteForAll |
Complete<public>(Agent:agent):void / CompleteForAll<public>():void |
Force-finishes the timer (fires SuccessEvent). |
SuccessEvent |
SuccessEvent<public>:listenable(?agent) |
Timer completed or ended with success. Sends the activating agent, if any. |
FailureEvent |
FailureEvent<public>:listenable(?agent) |
Timer completed or ended with failure (a countdown hitting zero). Sends the activating agent, if any. |
StartUrgencyModeEvent |
StartUrgencyModeEvent<public>:listenable(?agent) |
The timer entered its configured Urgency Mode window. |
Now the cooperating system. One device script owns both: a start button kicks off the round clock, coins pay points while it runs, a pause button freezes and unfreezes the clock, beating the clock pays a bonus, and time running out resets everything for the next round.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# One cooperating system: score_manager pays the points,
# timer_device decides when points can flow.
timed_score_round_device := class(creative_device):
@editable
StartButton : button_device = button_device{}
@editable
PauseButton : button_device = button_device{}
@editable
CoinTrigger : trigger_device = trigger_device{}
@editable
Scorer : score_manager_device = score_manager_device{}
@editable
RoundTimer : timer_device = timer_device{}
# Tracks whether the clock is currently frozen.
var Paused : logic = false
OnBegin<override>()<suspends>:void =
StartButton.InteractedWithEvent.Subscribe(OnRoundStart)
PauseButton.InteractedWithEvent.Subscribe(OnPauseToggled)
CoinTrigger.TriggeredEvent.Subscribe(OnCoinGrabbed)
# The timer's finish lines: beat the clock, or run out of time.
RoundTimer.SuccessEvent.Subscribe(OnClockBeaten)
RoundTimer.FailureEvent.Subscribe(OnTimeUp)
# Each coin is worth 25 during the round.
Scorer.SetScoreAward(25)
OnRoundStart(Player : agent) : void =
# Reset puts the timer back to base time AND stops it,
# so a fresh Start always runs the full round length.
RoundTimer.ResetForAll()
RoundTimer.StartForAll()
set Paused = false
Print("Round started — collect coins before time runs out!")
OnPauseToggled(Player : agent) : void =
if (Paused?):
RoundTimer.ResumeForAll()
set Paused = false
Print("Clock resumed.")
else:
RoundTimer.PauseForAll()
set Paused = true
Print("Clock paused.")
OnCoinGrabbed(MaybeAgent : ?agent) : void =
if (Player := MaybeAgent?):
# Points only matter while the round clock runs —
# the same Activate you already know.
Scorer.Activate(Player)
# SuccessEvent is listenable(?agent): the finishing agent is optional.
OnClockBeaten(MaybeAgent : ?agent) : void =
if (Player := MaybeAgent?):
# Beating the clock pays a flat 100-point bonus.
Scorer.SetScoreAward(100)
Scorer.Activate(Player)
Total := Scorer.GetCurrentScore(Player)
Print("Beat the clock! Total is now {Total}")
# Next round's coins are worth 25 again.
Scorer.SetScoreAward(25)
OnTimeUp(MaybeAgent : ?agent) : void =
Print("Time's up! Round over.")
# Stop and rewind the clock so the start button
# can launch a clean next round.
RoundTimer.ResetForAll()
How the two devices cooperate, line by line:
Scorer.SetScoreAward(25)inOnBegin— the score half is configured once; everyActivate(Player)during the round pays 25.OnRoundStart—ResetForAll()stops the timer and rewinds it to its base time; theStartForAll()right after launches a full-length round. CallingStartwithout the reset would resume from wherever the clock last stopped.OnPauseToggled— one button, two behaviors:PauseForAll()freezes the countdown,ResumeForAll()un-freezes it. Thevar Paused : logicfield remembers which side we're on;if (Paused?):is how you branch on alogicvalue.OnClockBeaten(MaybeAgent : ?agent)— the timer'sSuccessEventislistenable(?agent)(the timer can finish without a specific activating agent), so this handler unwraps exactly like a trigger handler does. On success we retarget the payout (SetScoreAward(100)), grant it, and read back the total withGetCurrentScore.OnTimeUp—FailureEventfires when a countdown hits zero. WeResetForAll()so the system is ready for the next press of the start button. Score is untouched: players keep what they earned.- Division of labor: the score manager never knows about time, the timer never knows about points. The Verse device is the conductor — it listens to both and decides how they cooperate. That's the pattern to reuse for any multi-device system.
Gotchas
- Two different event shapes.
TriggeredEventon a trigger islistenable(?agent)(optional — must unwrap withMaybeAgent?), but the score manager's ownScoreOutputEventandMaxTriggersEventarelistenable(agent)(plain agent — no unwrap). Don't writeAgent?on those or it won't compile. Activateis what actually grants.SetScoreAward,Increment,Decrement, andSetToAgentScoreonly configure the pending amount. Nothing reaches a player until you callActivate(orActivate(Agent)).- Agent vs no-arg overloads. If your device's Activating Team option is a specific team, use the
agentoverloads so the agent's team is checked. The no-argActivate()grants per the device's default configuration and ignores per-agent team gating. GetScoreAward/GetCurrentScoreare<transacts>reads. They're pure and return anintdirectly — no failure to handle — but you can't call them where side-effects aren't allowed in a speculative context that gets rolled back. In normal handler code they're fine.- Editable fields are mandatory. Calling
Scorer.Activate()only works becauseScoreris an@editablefield bound to a placed device. A barescore_manager_device{}.Activate()does nothing and won't reference your real device. - No int↔float auto-convert. Score values are
int. If you ever multiply by a float multiplier, you must convert explicitly — don't pass a float whereSetScoreAward(Value:int)expects an int. - Timer finish events are optionals too.
SuccessEvent,FailureEvent, andStartUrgencyModeEventontimer_devicearelistenable(?agent)— the timer may finish without a specific activating agent — so their handlers takeMaybeAgent : ?agentand unwrap, exactly like a trigger'sTriggeredEvent(and unlike the score manager's plain-agentevents). Resetstops the clock; it does not restart it. Resetting atimer_devicerewinds it to base time and stops it. To relaunch a round you need the pair: reset, thenStart/StartForAll. And a plainResumeafterPausecontinues from where the clock froze — it never rewinds.- Timer state can be per-agent or global.
Start(Agent)/Pause(Agent)drive one agent's clock; the*ForAllvariants drive everyone's. Mixing them against a device configured the other way is a classic "why didn't my timer move" bug — match your calls to the device's Timer Is Per Agent setting.