Overview
The player_reference_device solves a specific problem: you need to remember which player did something important and react whenever their stats change. Place one on your island, point it at a player with Register, and it becomes a live feed — signaling TrackedStatChangedEvent every time the tracked stat ticks up, AgentUpdatedEvent when the stored agent changes, and ActivatedEvent when you call Activate (which also ends the round). You can read the current stat with GetStatValue, check whether a particular agent is the one being tracked with IsReferenced, and retrieve the stored agent with GetAgent.
Reach for this device when you need:
- A leaderboard that re-sorts as scores change
- A most-wanted / bounty system that highlights the leading player
- A winner's podium that shows the champion's hologram at round end
- Any logic that must react to a specific player's stat in real time
API Reference
player_reference_device
Used to relay
agentstatistics to other devices andagents. Can transmit statistics such as elimination count, eliminated count, or scores when certain conditions are met. Can also project a hologram of theagentand display text that can be altered in various positions and curvatures.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
player_reference_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ActivatedEvent |
ActivatedEvent<public>:listenable(agent) |
Signaled when this device is activated. Sends the agent stored in the device. |
TrackedStatChangedEvent |
TrackedStatChangedEvent<public>:listenable(agent) |
Signaled when a stat tracked by this device is updated. Sends the agent stored in the device. |
AgentUpdatedEvent |
AgentUpdatedEvent<public>:listenable(agent) |
Signaled when the agent tracked by this device is updated. Sends the new agent stored in the device. |
AgentUpdateFailsEvent |
AgentUpdateFailsEvent<public>:listenable(agent) |
Signaled when the agent tracked by this fails to be updated. Sends the agent that attempted to be stored in this device. |
AgentReplacedEvent |
AgentReplacedEvent<public>:listenable(agent) |
Signaled when the agent tracked by this device is replaced. Sends the new agent stored in the device. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Enable |
Enable<public>():void |
Enables this device. |
Disable |
Disable<public>():void |
Disables this device. |
Activate |
Activate<public>():void |
Ends the round/game. |
Register |
Register<public>(Agent:agent):void |
Registers Agent as the agent being tracked by this device. |
Clear |
Clear<public>():void |
Clears the state of this device. |
IsReferenced |
IsReferenced<public>(Agent:agent)<transacts><decides>:void |
Is true when Agent is the player being referenced by the device. |
GetAgent |
GetAgent<public>():?agent |
Returns the agent currently referenced by the device. |
GetStatValue |
GetStatValue<public>():int |
Returns the stat value that this device is currently tracking |
Walkthrough
Scenario: Most-Wanted Bounty Board
A player steps on a pressure plate. Your Verse device registers them as the "most wanted" player on a player_reference_device. Every time that player's elimination count increases, a HUD message announces the new count. If a different player steps on the plate, the reference is replaced. When the tracked player's eliminations reach 5, the device activates (ending the round/game).
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
# Localizer helper — player_reference_device.Activate needs no message,
# but hud_message_device does.
bounty_device := class(creative_device):
# Wire up in the UEFN Details panel
@editable
BountyReference : player_reference_device = player_reference_device{}
@editable
RegistrationPlate : trigger_device = trigger_device{}
@editable
AnnouncementHUD : hud_message_device = hud_message_device{}
# How many eliminations trigger the round end
EliminationGoal : int = 5
OnBegin<override>()<suspends> : void =
# Subscribe to the pressure plate — when any player steps on it,
# register them as the most-wanted agent.
RegistrationPlate.TriggeredEvent.Subscribe(OnPlateTriggered)
# React every time the tracked stat (set to Eliminations in device
# settings) changes for the registered agent.
BountyReference.TrackedStatChangedEvent.Subscribe(OnStatChanged)
# React when the stored agent is successfully swapped out.
BountyReference.AgentUpdatedEvent.Subscribe(OnAgentUpdated)
# React when an agent update fails (e.g., device is disabled).
BountyReference.AgentUpdateFailsEvent.Subscribe(OnAgentUpdateFailed)
# Called when a player steps on the plate.
# TriggeredEvent sends ?agent, so we unwrap it.
OnPlateTriggered(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
# Check if this agent is already the one being tracked.
# IsReferenced is a <decides> function — use it inside an if.
if (BountyReference.IsReferenced[A]):
# Already the most-wanted; nothing to do.
AnnouncementHUD.SetText(AlreadyTrackedMsg())
else:
# Register the new agent as most-wanted.
BountyReference.Register(A)
# Called when the tracked stat changes.
# The event sends the stored agent (not ?agent — it's a plain agent).
OnStatChanged(TrackedAgent : agent) : void =
CurrentStat := BountyReference.GetStatValue()
# Announce the new elimination count via HUD.
AnnouncementHUD.SetText(EliminationCountMsg(CurrentStat))
# If the goal is reached, activate the device (ends the round).
if (CurrentStat >= EliminationGoal):
BountyReference.Activate()
# Called when the stored agent is successfully replaced.
OnAgentUpdated(NewAgent : agent) : void =
AnnouncementHUD.SetText(NewBountyMsg())
# Called when an update attempt fails.
OnAgentUpdateFailed(FailedAgent : agent) : void =
AnnouncementHUD.SetText(UpdateFailedMsg())
# --- Localized message helpers ---
AlreadyTrackedMsg<localizes>() : message = "Already the most-wanted!"
NewBountyMsg<localizes>() : message = "New most-wanted player registered!"
UpdateFailedMsg<localizes>() : message = "Could not update the most-wanted player."
EliminationCountMsg<localizes>(Count : int) : message = "Most-wanted eliminations: {Count}"
Line-by-line explanation
| Lines | What's happening |
|---|---|
@editable BountyReference |
Wires the placed player_reference_device into Verse via the Details panel. |
RegistrationPlate.TriggeredEvent.Subscribe(OnPlateTriggered) |
Listens for any player stepping on the trigger. |
BountyReference.TrackedStatChangedEvent.Subscribe(OnStatChanged) |
Fires whenever the tracked stat (configured in device settings) ticks up for the stored agent. |
BountyReference.AgentUpdatedEvent.Subscribe(OnAgentUpdated) |
Fires when Register succeeds and the stored agent changes. |
BountyReference.AgentUpdateFailsEvent.Subscribe(OnAgentUpdateFailed) |
Fires when a Register call is rejected (e.g., device disabled). |
if (BountyReference.IsReferenced[A]) |
IsReferenced is <decides>, so it must live inside an if — it succeeds only when A is the currently stored agent. |
BountyReference.Register(A) |
Stores A as the tracked agent; triggers AgentUpdatedEvent on success. |
BountyReference.GetStatValue() |
Returns the current integer value of the tracked stat. |
BountyReference.Activate() |
Ends the round/game — use this as your win condition trigger. |
<localizes> helpers |
hud_message_device.SetText requires a message type; raw strings won't compile. |
Common patterns
Pattern 1 — Enable/Disable and GetAgent for a conditional spotlight
Only allow registration during a specific phase. After the phase ends, disable the device and retrieve the stored agent to drive downstream logic (e.g., spawn a trophy).
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
spotlight_phase_device := class(creative_device):
@editable
Spotlight : player_reference_device = player_reference_device{}
@editable
PhaseTimer : timer_device = timer_device{}
@editable
RegistrationPlate : trigger_device = trigger_device{}
@editable
AnnouncementHUD : hud_message_device = hud_message_device{}
OnBegin<override>()<suspends> : void =
# Start with the device enabled so registrations are accepted.
Spotlight.Enable()
RegistrationPlate.TriggeredEvent.Subscribe(OnPlateTriggered)
PhaseTimer.SuccessEvent.Subscribe(OnPhaseEnd)
OnPlateTriggered(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
Spotlight.Register(A)
OnPhaseEnd(MaybeAgent : ?agent) : void =
# Lock the spotlight — no new registrations accepted.
Spotlight.Disable()
# Retrieve whoever is currently stored.
if (StoredAgent := Spotlight.GetAgent()?):
# StoredAgent is now a plain agent — use it for downstream logic.
AnnouncementHUD.SetText(SpotlightWinnerMsg())
else:
AnnouncementHUD.SetText(NoOneRegisteredMsg())
SpotlightWinnerMsg<localizes>() : message = "Phase over — spotlight winner locked in!"
NoOneRegisteredMsg<localizes>() : message = "Phase over — nobody stepped up!"
Pattern 2 — AgentReplacedEvent and Clear for a rotating bounty
In a rotating bounty system, the most-wanted player changes every 30 seconds. Use Clear to wipe the current reference and AgentReplacedEvent to react when a new agent takes over.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
rotating_bounty_device := class(creative_device):
@editable
BountyRef : player_reference_device = player_reference_device{}
@editable
RotationPlate : trigger_device = trigger_device{}
@editable
AnnouncementHUD : hud_message_device = hud_message_device{}
OnBegin<override>()<suspends> : void =
BountyRef.AgentReplacedEvent.Subscribe(OnBountyReplaced)
RotationPlate.TriggeredEvent.Subscribe(OnRotationTriggered)
OnRotationTriggered(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
# Wipe the old reference first, then register the new agent.
# Clear resets state; Register then fires AgentReplacedEvent
# because an agent was previously stored.
BountyRef.Clear()
BountyRef.Register(A)
# AgentReplacedEvent fires when the stored agent is swapped
# (i.e., a previous agent existed and was replaced).
OnBountyReplaced(NewAgent : agent) : void =
AnnouncementHUD.SetText(NewBountyMsg())
NewBountyMsg<localizes>() : message = "The bounty has a new target!"
Pattern 3 — ActivatedEvent for a winner's podium
When Activate is called (either from Verse or a sequencer), ActivatedEvent fires and sends the stored agent. Use this to trigger a podium animation or cinematic.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
podium_device := class(creative_device):
@editable
WinnerRef : player_reference_device = player_reference_device{}
@editable
WinButton : button_device = button_device{}
@editable
PodiumHUD : hud_message_device = hud_message_device{}
OnBegin<override>()<suspends> : void =
WinnerRef.ActivatedEvent.Subscribe(OnWinnerActivated)
WinButton.InteractedWithEvent.Subscribe(OnButtonPressed)
OnButtonPressed(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
# Register this player as the winner, then activate.
WinnerRef.Register(A)
WinnerRef.Activate() # Fires ActivatedEvent AND ends the round.
# ActivatedEvent sends the stored agent as a plain agent (not ?agent).
OnWinnerActivated(Winner : agent) : void =
# The hologram on the device will display the winner automatically
# if "Show Player Details" is enabled in device settings.
PodiumHUD.SetText(WinnerAnnouncementMsg())
WinnerAnnouncementMsg<localizes>() : message = "We have a winner! Check the podium!"
Gotchas
1. ActivatedEvent vs. Activate() — they're not the same thing
Activate() is a method you call that ends the round/game AND signals ActivatedEvent. ActivatedEvent is the event you subscribe to in order to react when activation happens (whether triggered by your Verse code or by a sequencer). Don't confuse calling Activate() with subscribing to ActivatedEvent — you need both if you want to trigger the end and react to it.
2. Event handler signatures — agent vs. ?agent
All five events on player_reference_device send a plain agent (not ?agent). This is different from many other devices (like trigger_device.TriggeredEvent) that send ?agent. Your handler signatures must match:
# CORRECT for player_reference_device events:
OnStatChanged(TrackedAgent : agent) : void = ...
# WRONG — will not compile:
OnStatChanged(MaybeAgent : ?agent) : void = ...
3. IsReferenced is <decides> — use it inside if
IsReferenced is a failable expression. You cannot call it in a void context or assign its result. Always use it as an if condition:
# CORRECT:
if (BountyReference.IsReferenced[A]):
# A is the tracked agent
# WRONG — compile error:
BountyReference.IsReferenced(A) # missing <decides> context
Note the bracket syntax IsReferenced[A] — this is the standard Verse pattern for calling <decides> functions inside if.
4. GetAgent returns ?agent — always unwrap
GetAgent() returns an optional. If no agent has been registered yet (or Clear was called), it returns false. Always unwrap:
if (StoredAgent := WinnerRef.GetAgent()?):
# safe to use StoredAgent here
5. Localized text for HUD messages
hud_message_device.SetText requires a message type, not a raw string. Declare a <localizes> helper for every string you want to display. There is no StringToMessage function in Verse.
6. AgentUpdatedEvent vs. AgentReplacedEvent
AgentUpdatedEventfires whenever the stored agent changes to a new value — including the first registration when no agent was stored before.AgentReplacedEventfires only when an agent that was already stored is replaced by a different one. UseAgentReplacedEventfor "swap" logic andAgentUpdatedEventfor any change including first registration.
7. The device must be placed and wired via @editable
You cannot construct a player_reference_device in Verse with player_reference_device{} and expect it to work as a real device. It must be placed in the UEFN level and referenced via an @editable field. The = player_reference_device{} in the field declaration is only a default placeholder so the class compiles — always assign the real placed device in the Details panel.