Reference Devices compiles

stat_creator_device: Custom Stats, Scores & RPG Levels

Need a custom currency, an RPG-style XP-and-level system, or a win condition built on your own scoreboard stat? The stat_creator_device lets you define a statistic for a player, team, or the whole match — then read it, write it, and react to it from Verse. This article shows you the real API doing real game things.

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

Overview

The stat_creator_device generates a single named scoreboard statistic that can be scoped to an individual Player, a Team, or the whole Match. That stat can drive Game End / Round End conditions, be shown on the HUD, or power an RPG-style progression: when its Value reaches Max Value, the device can bump a Level up by 1 and reset Value to 0.

Reach for this device when you want a number you control — coins collected, kills toward a custom objective, crafting XP, faction reputation — instead of one of Fortnite's built-in stats. From Verse you can:

  • Read the current value/level with GetValue, GetLevel, GetValueForMatch, GetLevelForMatch.
  • Write it with SetValue, SetLevel, SetValueForMatch, SetLevelForMatch.
  • React to changes by subscribing to ValueChangedEvent, LevelChangedEvent, and MaximumReachedEvent.

A critical detail: the Scope you choose in the device's Details panel decides which overload succeeds. The GetValue(Agent)/SetValue(Agent, ...) calls only succeed when Scope is Player; the (Team) overloads only succeed when Scope is Team; the ...ForMatch calls only succeed when Scope is Match. All of these are <decides>, so you call them inside if (...).

API Reference

stat_creator_device

A device that generates a scoreboard stat that can be used by the game to determine Game End and Round End conditions. The stat can apply to individual players, teams, or everyone in the match. It can also generate a Level that will increment by 1 each time Value reaches Max Value, resetting Value to 0 when Level increments.

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

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

Events (subscribe a handler to react):

Event Signature Description
ValueChangedEvent ValueChangedEvent<public>:listenable(tuple(?agent, int)) Signaled when the stat changes Value. Sends a tuple of the instigating agent (or false if none) and the new Value. Value increases to a maximum of Max Value. If the stat has not yet reached Max Level then Value will reset to 0 and L
LevelChangedEvent LevelChangedEvent<public>:listenable(tuple(?agent, int)) Signaled when the stat changes Level. Sends a tuple of the instigating agent (or false if none) and the new Level.
MaximumReachedEvent MaximumReachedEvent<public>:listenable(tuple(?agent, int)) Signaled when an agent reaches the maximum stat value. This occurs when the stat reaches Max Value if the device has no levels, or Max Level if that option is set. Sends the agent that caused the stat event that changed the level an

Methods (call these to make the device act):

Method Signature Description
GetValue GetValue<public>(Agent:agent)<transacts><decides>:int Returns stat Value for Agent. Succeeds if Scope is set to Player and Agent passes the requirement checks on the device.
GetValue GetValue<public>(Team:team)<transacts><decides>:int Returns stat Value for Team. Succeeds if Scope is set to Team and Team passes the requirement checks on the device.
GetValueForMatch GetValueForMatch<public>()<transacts><decides>:int Returns stat Value for the match. Succeeds if Scope is set to Match.
SetValue SetValue<public>(Agent:agent, Value:int)<transacts><decides>:void Updates stat Value for Agent. Succeeds if Scope is set to Agent and Agent passes the requirement checks on the device. If the stat does not have levels, Value is clamped between 0 and Max Value. If the stat does have levels, the
SetValue SetValue<public>(Team:team, Value:int)<transacts><decides>:void Updates stat Value for Team. Succeeds if Scope is set to Team and Team passes the requirement checks on the device. If the stat does not have levels, Value is clamped between 0 and Max Value. If the stat does have levels, the Va
SetValueForMatch SetValueForMatch<public>(Value:int)<transacts><decides>:void Updates stat Value for the match. Succeeds if Scope is set to Match. If the stat does not have levels, Value is clamped between 0 and Max Value. If the stat does have levels, the Value is not clamped and setting a value below 0 or a
GetLevel GetLevel<public>(Agent:agent)<transacts><decides>:int Returns stat Level for Agent. Succeeds if Number of Levels is set to greater than 0, Scope is set to Player and Agent passes the requirement checks on the device.
GetLevel GetLevel<public>(Team:team)<transacts><decides>:int Returns stat Level for Team. Succeeds if Number of Levels is set to greater than 0, Scope is set to Team and the Team passes the requirement checks on the device.
GetLevelForMatch GetLevelForMatch<public>()<transacts><decides>:int Returns stat Level for the match. Succeeds if Number of Levels is set to greater than 0 and Scope is set to Match.
SetLevel SetLevel<public>(Agent:agent, Level:int)<transacts><decides>:void Updates stat Level for Agent. Succeeds if Number of Levels is set to greater than 0 and Scope is set to Agent. Altering stat Level will set Agent's stat Value to 0.
SetLevel SetLevel<public>(Team:team, Level:int)<transacts><decides>:void Updates stat Level for Team. Succeeds if Number of Levels is set to greater than 0 and Scope is set to Team. Altering stat Level will set Team's stat Value to 0.
SetLevelForMatch SetLevelForMatch<public>(Level:int)<transacts><decides>:void Updates stat Level for the match. Succeeds if Number of Levels is set to greater than 0 and Scope is set to Match . Altering stat Level will set the Match's stat Value to 0.
GetName GetName<public>():string Returns the name for the stat that is generated by this Stat Creator.

Walkthrough

Let's build a coin XP system. The scope of our stat is Player with levels enabled (Number of Levels > 0, Max Value = 100). When a player steps on a coin pad (a button_device), we add 25 to their custom "Coins" stat. Each time the value reaches 100, the device automatically increments their Level and resets Value to 0 — and we light up a billboard with their current level.

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

coin_xp_game := class(creative_device):

    # The stat_creator_device — Scope = Player, Levels enabled, Max Value = 100
    @editable
    CoinStat : stat_creator_device = stat_creator_device{}

    # Button the player interacts with to earn coins
    @editable
    CoinButton : button_device = button_device{}

    # Billboard to show feedback
    @editable
    StatusBillboard : billboard_device = billboard_device{}

    # Localized message helper — message params need localized text, not raw strings
    StatusText<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends>:void =
        # React when a player earns/loses coins
        CoinStat.ValueChangedEvent.Subscribe(OnValueChanged)
        # React when a player levels up
        CoinStat.LevelChangedEvent.Subscribe(OnLevelChanged)
        # React when a player maxes out the stat
        CoinStat.MaximumReachedEvent.Subscribe(OnMaxReached)
        # Stepping the button awards coins
        CoinButton.InteractedWithEvent.Subscribe(OnButtonPressed)

    # Award 25 coins to whoever pressed the button
    OnButtonPressed(Agent : agent) : void =
        if:
            # Read the player's current value (Scope must be Player)
            Current := CoinStat.GetValue[Agent]
            # Write the new value; clamped 0..MaxValue, overflow rolls the level
            CoinStat.SetValue[Agent, Current + 25]
        then:
            # success

    # ValueChangedEvent hands us (?agent, int) — the instigator and the new Value
    OnValueChanged(Payload : tuple(?agent, int)) : void =
        NewValue := Payload(1)
        if (A := Payload(0)?):
            StatusBillboard.SetText(StatusText("{CoinStat.GetName()}: {NewValue}"))

    # LevelChangedEvent fires when Value rolled over Max Value and Level went up
    OnLevelChanged(Payload : tuple(?agent, int)) : void =
        NewLevel := Payload(1)
        if (A := Payload(0)?):
            StatusBillboard.SetText(StatusText("Level Up! Now level {NewLevel}"))

    # MaximumReachedEvent fires when the player hits the maximum level/value
    OnMaxReached(Payload : tuple(?agent, int)) : void =
        if (A := Payload(0)?):
            StatusBillboard.SetText(StatusText("MAX REACHED!"))

Line by line:

  • CoinStat, CoinButton, StatusBillboard are @editable fields, so you bind the real placed devices in the Details panel. You can only call a placed device through such a field.
  • In OnBegin we subscribe all three events plus the button. Handlers are ordinary methods at class scope.
  • The event payloads are tuple(?agent, int). Payload(0) is the optional instigating agent — unwrap it with if (A := Payload(0)?):. Payload(1) is the new Value (or Level).
  • GetValue[Agent] and SetValue[Agent, ...] are <decides>, so they live inside if:/then:. We read the current value, add 25, and write it back. Because levels are enabled, when the value crosses 100 the device rolls Value to 0 and raises Level — which fires LevelChangedEvent for free.
  • GetName() returns the configured stat name as a string, handy for labels.

Common patterns

Team objective: set and read a Team-scoped value

When Scope is Team, use the (team) overloads. Here a capture trigger adds 10 to the capturing player's team score.

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

team_capture_game := class(creative_device):

    @editable
    TeamScoreStat : stat_creator_device = stat_creator_device{}

    @editable
    CaptureTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        CaptureTrigger.TriggeredEvent.Subscribe(OnCaptured)

    OnCaptured(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            Teams := GetPlayspace().GetTeamCollection()
            if:
                Team := Teams.GetTeam[A]
                Current := TeamScoreStat.GetValue[Team]
                TeamScoreStat.SetValue[Team, Current + 10]
            then:
                # team score updated

Match-wide countdown using ...ForMatch

When Scope is Match, use GetValueForMatch/SetValueForMatch. This snippet seeds a shared match resource pool at start.

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

match_resource_game := class(creative_device):

    @editable
    ResourceStat : stat_creator_device = stat_creator_device{}

    OnBegin<override>()<suspends>:void =
        ResourceStat.MaximumReachedEvent.Subscribe(OnPoolFull)
        if:
            ResourceStat.SetValueForMatch[50]
            Seeded := ResourceStat.GetValueForMatch[]
        then:
            # match pool seeded to 50

    OnPoolFull(Payload : tuple(?agent, int)) : void =
        # the shared match pool hit its maximum

RPG level control with SetLevel / GetLevel

With levels enabled and Scope = Player, you can jump a player directly to a level. Setting Level resets that player's Value to 0.

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

level_grant_game := class(creative_device):

    @editable
    XPStat : stat_creator_device = stat_creator_device{}

    @editable
    PromoteButton : button_device = button_device{}

    OnBegin<override>()<suspends>:void =
        XPStat.LevelChangedEvent.Subscribe(OnLevelChanged)
        PromoteButton.InteractedWithEvent.Subscribe(OnPromote)

    OnPromote(Agent : agent) : void =
        if:
            CurrentLevel := XPStat.GetLevel[Agent]
            # promote one level (this resets their Value to 0)
            XPStat.SetLevel[Agent, CurrentLevel + 1]
        then:
            # promoted

    OnLevelChanged(Payload : tuple(?agent, int)) : void =
        NewLevel := Payload(1)
        if (A := Payload(0)?):
            # player A is now NewLevel

Gotchas

  • Scope decides which overload succeeds. GetValue[Agent] fails if Scope is Team or Match; GetValueForMatch[] fails unless Scope is Match. These are <decides> calls — wrap them in if and pick the overload that matches your device's configured Scope. Mixing them silently fails the if.
  • All Get/Set methods are <decides>. You must call them inside an if/then (or another failure context), not as bare statements. They also require the agent/team to pass the device's requirement checks.
  • Event payloads are tuples with an optional agent. ValueChangedEvent, LevelChangedEvent, and MaximumReachedEvent all hand you tuple(?agent, int). Use Payload(0) for the optional instigator (unwrap with if (X := Payload(0)?):) and Payload(1) for the new value/level. The agent is false when no instigator caused the change.
  • Setting Level zeroes Value. SetLevel/SetLevelForMatch reset the corresponding Value to 0 — expected for RPG progression but surprising if you wanted to keep accumulated value.
  • Value clamping depends on levels. Without levels, SetValue clamps between 0 and Max Value. With levels enabled, the value is not clamped the same way — overflow rolls into the next Level instead, which is what fires LevelChangedEvent.
  • message params need localized text. Anything taking a message (like a billboard's SetText) needs a <localizes> helper — StatusText<localizes>(S:string):message = "{S}" — not a raw string. There is no StringToMessage.
  • GetName() returns a string, not a message. Use it inside string interpolation, then wrap the whole thing in your <localizes> helper if you need a message.

Device Settings & Options

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

Stat Creator Device settings and options panel in the UEFN editor
Stat Creator Device — User Options in the UEFN editor

Guides & scripts that use stat_creator_device

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

Build your own lesson with stat_creator_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 →