Reference Devices compiles

player_counter_device: Track Players, Trigger Events, Gate Progress

The `player_counter_device` is your go-to tool whenever a game moment depends on *how many* players are present — a vault that opens only when four players stand on the pad, a boss arena that scales difficulty by headcount, or a party lobby that refuses to start until everyone is ready. It tracks players entering and leaving a zone, fires events when the count hits (or misses) a target, and exposes a rich Verse API so you can query, adjust, and react to the count entirely in code.

Updated Examples verified on the live UEFN compiler
Watch the Knotplayer_counter_device in ~90 seconds.

Overview

The player_counter_device solves one of the most common multiplayer design problems: gating progress on player presence. Place the device in your level, set a target count, and it continuously evaluates whether the number of players inside its zone matches that target. When the count succeeds or fails, it signals events your Verse code can subscribe to. You can also query the count at any time, adjust the target dynamically, register specific players manually, and show or hide an in-world info panel so players always know how many more teammates they need.

Reach for it when you need to:

  • Lock a door, barrier, or cinematic until N players are present.
  • Scale enemy waves or loot drops based on current player count.
  • Build a lobby "ready check" that waits for a full squad.
  • Track which specific agents are inside a zone at any moment.

API Reference

player_counter_device

Used to track and react to how many players are in a particular area, and optionally display that information in game.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).

player_counter_device<public> := class<concrete><final>(creative_device_base):

Events (subscribe a handler to react):

Event Signature Description
CountSucceedsEvent CountSucceedsEvent<public>:listenable(tuple()) Signaled when the player count matches Target Player Count. The frequency of evaluation against Target Player Count can be controlled through the device settings.
CountFailsEvent CountFailsEvent<public>:listenable(tuple()) Signaled when the player count does not match Target Player Count. The frequency of evaluation against Target Player Count can be controlled through the device settings.
CountedEvent CountedEvent<public>:listenable(agent) Signaled when a valid player enters the zone and is counted. The frequency of evaluation against the Target Player Count can be controlled through the device settings. Sends the agent that is now being counted.
RemovedEvent RemovedEvent<public>:listenable(agent) Signaled when a player is no longer counted by this device, such as when they leave the zone, leave the game, or are assigned to a different team or class. Sends the agent that is no longer being counted.

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.
IncrementTargetCount IncrementTargetCount<public>():void Increments Target Player Count by 1. Immediately triggers a new comparison.
DecrementTargetCount DecrementTargetCount<public>():void Decrements Target Player Count by 1. Immediately triggers a new comparison.
CompareToTarget CompareToTarget<public>():void Triggers an evaluation of the current count vs Target Player Count, signaling CountSucceedsEvent or CountFailsEvent based on the evaluation result.
TransmitForAllCounted TransmitForAllCounted<public>():void Triggers CountedEvent for all agents currently being counted.
Register Register<public>(Agent:agent):void Adds the player to the registered player list. Track Registered Players determines how registered players are counted.
Unregister Unregister<public>(Agent:agent):void Removes the player from the registered player list. Track Registered Players determines how registered players are counted.
UnregisterAll UnregisterAll<public>():void Clears all players from the list of registered players. Track Registered Players determines how registered players are counted.
ShowInfoPanel ShowInfoPanel<public>():void Show this device in the world as an info panel showing Current + Required player counts.
HideInfoPanel HideInfoPanel<public>():void Hide this device info panel from the world.
IsShowingInfoPanel IsShowingInfoPanel<public>()<transacts><decides>:void Returns whether this device is represented in the world as an info panel showing Current + Required player counts.
SetTargetCount SetTargetCount<public>(Count:int):void Sets the number of players required for this counter to succeed. Immediately triggers a new comparison.
GetTargetCount GetTargetCount<public>()<transacts>:int Returns the number of players required for this counter to succeed.
GetCount GetCount<public>()<transacts>:int Returns the number of players currently counted by this device
IsPassingTest IsPassingTest<public>()<transacts><decides>:void Is true if the device is currently succeeding in its comparison.
IsCounted IsCounted<public>(Agent:agent)<transacts><decides>:void Is true if Agent is currently counted by this device.
GetCountedAgents GetCountedAgents<public>()<reads>:[]agent Returns an array of agents that are currently counted by the device.

Walkthrough

Scenario — The Vault Door: Four players must stand on a pressure pad simultaneously to open a vault door (represented by a barrier_device). An info panel shows the current vs. required count. When the count succeeds the barrier disables; when players leave and the count drops, the barrier re-enables.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

vault_manager := class(creative_device):

    # ── Editable device references ──────────────────────────────────────
    @editable
    PressurePadCounter : player_counter_device = player_counter_device{}

    @editable
    VaultDoor : barrier_device = barrier_device{}

    # ── Lifecycle ───────────────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =
        # Require exactly 4 players on the pad to open the vault.
        PressurePadCounter.SetTargetCount(4)

        # Show the in-world info panel so players can see "2 / 4" etc.
        PressurePadCounter.ShowInfoPanel()

        # Subscribe to the four key events.
        PressurePadCounter.CountSucceedsEvent.Subscribe(OnCountSucceeds)
        PressurePadCounter.CountFailsEvent.Subscribe(OnCountFails)
        PressurePadCounter.CountedEvent.Subscribe(OnPlayerCounted)
        PressurePadCounter.RemovedEvent.Subscribe(OnPlayerRemoved)

        # Force an immediate evaluation in case players are already inside.
        PressurePadCounter.CompareToTarget()

    # ── Event handlers ──────────────────────────────────────────────────

    # CountSucceedsEvent fires with a tuple() — no payload to unwrap.
    OnCountSucceeds(Unused : tuple()) : void =
        # Enough players are on the pad — open the vault!
        VaultDoor.Disable()
        Print("Vault unlocked!")

    # CountFailsEvent fires when the count no longer matches the target.
    OnCountFails(Unused : tuple()) : void =
        # Someone left — re-lock the vault.
        VaultDoor.Enable()
        Print("Vault locked — not enough players.")

    # CountedEvent sends the agent who just entered the zone.
    OnPlayerCounted(Agent : agent) : void =
        Current := PressurePadCounter.GetCount()
        Target  := PressurePadCounter.GetTargetCount()
        Print("Player entered pad. Count: {Current} / {Target}")

    # RemovedEvent sends the agent who left or disconnected.
    OnPlayerRemoved(Agent : agent) : void =
        Current := PressurePadCounter.GetCount()
        Print("Player left pad. Count now: {Current}")

Line-by-line explanation:

Lines What's happening
@editable fields Bind the placed player_counter_device and barrier_device in the UEFN editor. Without @editable the identifiers are unknown at compile time.
SetTargetCount(4) Overrides the device's editor setting at runtime — useful when target count is data-driven.
ShowInfoPanel() Makes the device render a floating HUD element in-world showing current vs. required count.
CountSucceedsEvent.Subscribe(OnCountSucceeds) Registers the handler. The event carries tuple() — an empty payload — so the handler signature is (Unused : tuple()) : void.
CountFailsEvent.Subscribe(OnCountFails) Mirror of the above; fires whenever the count stops matching.
CountedEvent.Subscribe(OnPlayerCounted) This event carries an agent directly (not ?agent), so no option-unwrap is needed.
CompareToTarget() Forces an immediate evaluation. Without this, the device only evaluates on its configured frequency tick.
GetCount() / GetTargetCount() Query methods marked <transacts> — safe to call anywhere, including inside event handlers.
VaultDoor.Disable() / .Enable() The actual game effect: open or close the barrier.

Common patterns

Pattern 1 — Dynamic difficulty: raise the target mid-game and query which agents are inside

After the first wave clears, require more players to be on the capture point before the next wave spawns. Also iterate over every currently-counted agent to apply a buff.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

wave_scaler := class(creative_device):

    @editable
    CaptureCounter : player_counter_device = player_counter_device{}

    @editable
    WaveSpawner : spawner_device = spawner_device{}

    OnBegin<override>()<suspends> : void =
        # Start with a target of 2 for wave 1.
        CaptureCounter.SetTargetCount(2)
        CaptureCounter.CountSucceedsEvent.Subscribe(OnCapture)

    OnCapture(Unused : tuple()) : void =
        # Raise the bar for the next wave.
        CaptureCounter.IncrementTargetCount()
        CaptureCounter.IncrementTargetCount()  # +2 each wave

        NewTarget := CaptureCounter.GetTargetCount()
        Print("Next wave requires {NewTarget} players on point.")

        # Inspect every agent currently on the capture point.
        CountedAgents := CaptureCounter.GetCountedAgents()
        for (A : CountedAgents):
            Print("Agent on point — granting wave bonus.")
            # (wire up a grant_device or item_granter_device here)

        WaveSpawner.SpawnAtRandomLocation()

Key APIs used: SetTargetCount, IncrementTargetCount, GetTargetCount, GetCountedAgents, CountSucceedsEvent.


Pattern 2 — VIP registration: only count specific players

In a VIP escort mode, only the VIP player and their two bodyguards should count toward the extraction trigger. Register them manually and use IsCounted to gate a cinematic.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }

vip_extraction := class(creative_device):

    @editable
    ExtractionCounter : player_counter_device = player_counter_device{}

    @editable
    CinematicSequence : cinematic_sequence_device = cinematic_sequence_device{}

    @editable
    VipSpawner : player_spawner_device = player_spawner_device{}

    OnBegin<override>()<suspends> : void =
        # Only 1 player (the VIP) needs to reach extraction.
        ExtractionCounter.SetTargetCount(1)

        # Clear any stale registrations from a previous round.
        ExtractionCounter.UnregisterAll()

        ExtractionCounter.CountSucceedsEvent.Subscribe(OnExtractionSuccess)
        ExtractionCounter.CountFailsEvent.Subscribe(OnExtractionFailed)

        # Simulate registering the VIP when they spawn.
        VipSpawner.SpawnedEvent.Subscribe(OnVipSpawned)

    OnVipSpawned(Agent : agent) : void =
        # Register this specific agent so only they count toward extraction.
        ExtractionCounter.Register(Agent)
        Print("VIP registered with extraction counter.")

    OnExtractionSuccess(Unused : tuple()) : void =
        # Check the VIP is genuinely in the zone before playing the cinematic.
        CountedAgents := ExtractionCounter.GetCountedAgents()
        for (A : CountedAgents):
            if (ExtractionCounter.IsCounted[A]):
                CinematicSequence.Play()
                Print("VIP extracted! Playing outro cinematic.")

    OnExtractionFailed(Unused : tuple()) : void =
        Print("VIP left the extraction zone — hold position!")

Key APIs used: SetTargetCount, UnregisterAll, Register, CountSucceedsEvent, CountFailsEvent, GetCountedAgents, IsCounted.


Pattern 3 — Info panel toggle and passing-test query

A lobby manager shows the info panel while waiting for players, hides it once the game starts, and periodically checks IsPassingTest to decide whether to begin the match.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

lobby_manager := class(creative_device):

    @editable
    LobbyCounter : player_counter_device = player_counter_device{}

    @editable
    GameStartSequence : cinematic_sequence_device = cinematic_sequence_device{}

    OnBegin<override>()<suspends> : void =
        LobbyCounter.SetTargetCount(4)
        LobbyCounter.ShowInfoPanel()

        # Poll every 3 seconds until enough players are ready.
        loop:
            Sleep(3.0)
            # Force a fresh comparison so events fire if state changed.
            LobbyCounter.CompareToTarget()

            if (LobbyCounter.IsPassingTest[]):
                # Enough players — hide the panel and start the game.
                LobbyCounter.HideInfoPanel()
                LobbyCounter.Disable()   # Stop counting; game is live.
                GameStartSequence.Play()
                break
            else:
                Current := LobbyCounter.GetCount()
                Target  := LobbyCounter.GetTargetCount()
                Print("Waiting: {Current} / {Target} players in lobby.")

Key APIs used: SetTargetCount, ShowInfoPanel, CompareToTarget, IsPassingTest, GetCount, GetTargetCount, HideInfoPanel, Disable.

Gotchas

1. CountSucceedsEvent and CountFailsEvent carry tuple(), not agent

These two events have the signature listenable(tuple()). Your handler must accept (Unused : tuple()). A common mistake is writing (Agent : agent) — that won't compile. Only CountedEvent and RemovedEvent carry an agent.

2. CountedEvent sends agent directly — no option unwrap needed

Unlike some device events that send ?agent, CountedEvent and RemovedEvent send a plain agent. Do not write if (A := Agent?) — just use Agent directly in the handler body.

3. IsPassingTest[] and IsCounted[] are failable — call inside if

Both methods are marked <decides>, meaning they can fail. Always call them inside an if expression:

if (LobbyCounter.IsPassingTest[]):
    # safe to proceed

Calling them bare (outside if) is a compile error.

4. IsShowingInfoPanel[] is also <decides>

Same rule applies: wrap in if (Counter.IsShowingInfoPanel[]): before branching on it.

5. CompareToTarget() is not automatic

The device evaluates on a configurable tick frequency set in the editor. If you need an immediate evaluation — for example, right after calling SetTargetCount or IncrementTargetCount — call CompareToTarget() explicitly. Both IncrementTargetCount and DecrementTargetCount do trigger an immediate comparison, but SetTargetCount also does — check your editor settings to avoid double-firing.

6. Register / Unregister behaviour depends on the editor setting

The Track Registered Players option in the device's editor settings controls whether registered players are unioned, intersected, or differenced with the zone-tracked players. Calling Register(Agent) in Verse without configuring this setting first may produce surprising count results.

7. GetCountedAgents() is <reads> — avoid in <transacts> contexts

GetCountedAgents is marked <reads>, which means it can observe mutable state. It cannot be called from a <transacts>-only context. Call it from a regular (suspending or non-transacting) function.

8. Disable() stops counting — don't forget to Enable() again

Calling Disable() freezes the device: no new players are counted and no events fire. If your game has rounds, call Enable() at the start of each round and UnregisterAll() to reset the registered list.

Device Settings & Options

The player_counter_device User Options panel in the UEFN editor — every setting you can tune.

Player Counter Device settings and options panel in the UEFN editor — Compare Player Count, Target Player Count, Info Panel Visible, Zone Visible During Game, Count Guard as Player
Player Counter Device — User Options in the UEFN editor: Compare Player Count, Target Player Count, Info Panel Visible, Zone Visible During Game, Count Guard as Player
⚙️ Settings on this device (5)

Setting names read from the panel above — may be partial; the screenshots are the source of truth.

Compare Player Count
Target Player Count
Info Panel Visible
Zone Visible During Game
Count Guard as Player

Guides & scripts that use player_counter_device

Step-by-step tutorials that put this object to work.

Build your own lesson with player_counter_device

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →