Reference Devices compiles

race_manager_device: Lap Races That Run Themselves

The race_manager_device is the brain of any checkpoint race in UEFN — it knows when the race begins, when each lap is done, and when a racer crosses the finish line for good. In Verse you can start and stop the race on cue and reward players the moment they complete a lap or finish, turning a basic time trial into a full game loop.

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

Overview

The race_manager_device works hand-in-hand with race_checkpoint_devices to build advanced racing modes. The checkpoints define the track; the race manager tracks each agent's progress around it — counting laps, detecting the first lap, and signaling when somebody crosses the finish line.

Reach for it whenever you want a lap race (speedway, go-kart circuit, parkour lap challenge) and you need code to react to race events: start a countdown timer when the race begins, grant a boost item when a player finishes their first lap, award points the instant someone completes the final lap, or shut the whole race down when the round ends.

The device gives you four events — RaceBeganEvent, FirstLapCompletedEvent, LapCompletedEvent, RaceCompletedEvent — and four methods — Enable, Disable, Begin, End. Each event hands you the agent it concerns, so you always know who started, lapped, or finished.

API Reference

race_manager_device

Used with the race_checkpoint_device to create more advanced racing modes.

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

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

Events (subscribe a handler to react):

Event Signature Description
RaceBeganEvent RaceBeganEvent<public>:listenable(agent) Signaled when the race begins. Sends the agent that started the race.
RaceCompletedEvent RaceCompletedEvent<public>:listenable(agent) Signaled when an agent finishes the race. Sends the agent that finished the race.
FirstLapCompletedEvent FirstLapCompletedEvent<public>:listenable(agent) Signaled when an agent completes their first lap. Sends the agent that finished the lap.
LapCompletedEvent LapCompletedEvent<public>:listenable(agent) Signaled when an agent completes a lap. Sends the agent that finished the lap.

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.
Begin Begin<public>():void Begins the race.
End End<public>():void Ends the race.

Walkthrough

Let's build a complete go-kart circuit loop. When the round starts we enable the manager and Begin the race. A HUD message announces who started it. The first time a racer completes a lap, we grant them a speed-boost item. Every lap we keep a running count, and when a racer finishes the whole race we award points and, once the first finisher comes in, End the race so the next round can start.

race_loop_manager := class(creative_device):

    # The race brain — drag the placed Race Manager here in the Details panel.
    @editable
    RaceManager:race_manager_device = race_manager_device{}

    # Hands out a boost item the first time a racer laps.
    @editable
    BoostGranter:item_granter_device = item_granter_device{}

    # Awards score to finishers.
    @editable
    ScoreAwarder:score_manager_device = score_manager_device{}

    # Shows race announcements.
    @editable
    Announcer:hud_message_device = hud_message_device{}

    # Localized-text helper: message params need a localized value, not a raw string.
    Msg<localizes>(S:string):message = "{S}"

    OnBegin<override>()<suspends>:void =
        # Wire up handlers BEFORE we start the race so nothing is missed.
        RaceManager.RaceBeganEvent.Subscribe(OnRaceBegan)
        RaceManager.FirstLapCompletedEvent.Subscribe(OnFirstLap)
        RaceManager.LapCompletedEvent.Subscribe(OnLapCompleted)
        RaceManager.RaceCompletedEvent.Subscribe(OnRaceCompleted)

        # Make sure the device is on, then kick off the race.
        RaceManager.Enable()
        RaceManager.Begin()

    OnRaceBegan(Starter:agent):void =
        # Announce to everyone that the race is live.
        Announcer.Show()

    OnFirstLap(Racer:agent):void =
        # Reward the racer's first completed lap with a boost item.
        BoostGranter.GrantItem(Racer)

    OnLapCompleted(Racer:agent):void =
        # Fires on EVERY lap (including the first). Give a small score nudge per lap.
        ScoreAwarder.Activate(Racer)

    OnRaceCompleted(Finisher:agent):void =
        # A racer crossed the finish line for good — award the big payoff.
        ScoreAwarder.Activate(Finisher)
        # End the race so the round can wrap up after the first finisher.
        RaceManager.End()

Line by line:

  • The @editable fields let you drag your placed devices in from the Outliner. RaceManager is the star; the others (granter, score manager, HUD) are the rewards and feedback.
  • Msg<localizes> is the standard pattern for producing a message — any device method that wants displayed text needs a localized value, never a raw string.
  • In OnBegin we subscribe every handler first, then Enable() and Begin(). Subscribing before Begin() guarantees we catch the RaceBeganEvent.
  • Each handler receives a plain agent (these events are listenable(agent), not listenable(?agent)), so no if (A := Agent?) unwrap is needed here.
  • OnFirstLap only ever fires once per racer — perfect for a one-time reward. OnLapCompleted fires for every lap, so it's where per-lap score lives.
  • OnRaceCompleted fires when someone finishes the full race; we award points and End() the race to close it out.

Common patterns

Pattern 1 — Gate the race behind a start button. Don't Begin() automatically; wait for a button so a host can launch the race when everyone's ready.

race_start_gate := class(creative_device):

    @editable
    RaceManager:race_manager_device = race_manager_device{}

    @editable
    StartButton:button_device = button_device{}

    OnBegin<override>()<suspends>:void =
        # Keep the race disabled until the host presses the button.
        RaceManager.Disable()
        StartButton.InteractedWithEvent.Subscribe(OnStartPressed)

    OnStartPressed(Presser:agent):void =
        RaceManager.Enable()
        RaceManager.Begin()

Pattern 2 — Stop the race on a timer expiring. Use End() to cut a race short when a time limit runs out, then disable the manager.

race_time_limit := class(creative_device):

    @editable
    RaceManager:race_manager_device = race_manager_device{}

    @editable
    LimitTimer:timer_device = timer_device{}

    OnBegin<override>()<suspends>:void =
        RaceManager.Enable()
        RaceManager.Begin()
        # When the round timer succeeds, stop the race.
        LimitTimer.SuccessEvent.Subscribe(OnTimeUp)

    OnTimeUp(Agent:?agent):void =
        # SuccessEvent is listenable(?agent) — unwrap is optional since we don't need the agent.
        RaceManager.End()
        RaceManager.Disable()

Pattern 3 — Track laps per racer with LapCompletedEvent. Build a live tally to drive a leaderboard or detect the final lap yourself.

lap_tracker := class(creative_device):

    @editable
    RaceManager:race_manager_device = race_manager_device{}

    # Running lap count keyed by agent.
    var LapCounts:[agent]int = map{}

    OnBegin<override>()<suspends>:void =
        RaceManager.LapCompletedEvent.Subscribe(OnLap)
        RaceManager.Enable()
        RaceManager.Begin()

    OnLap(Racer:agent):void =
        # Read the current count (default 0) and bump it.
        Current := LapCounts[Racer] or 0
        if (set LapCounts[Racer] = Current + 1) {}

Gotchas

  • Subscribe before you Begin(). If you call Begin() before wiring up RaceBeganEvent, the begin signal fires into the void. In OnBegin, do all .Subscribe(...) calls first, then Enable() then Begin().
  • Enable() vs Begin() are different. Enable/Disable turn the device on/off; Begin/End start and stop an actual race. A disabled device won't run a race even if you call Begin() — enable it first.
  • Race events give a plain agent, the timer's SuccessEvent gives ?agent. The four race events are listenable(agent) so the handler param is agent directly. But cross-device events like timer_device.SuccessEvent are listenable(?agent) — those handlers take ?agent and you unwrap with if (A := Agent?): when you need the agent.
  • message params need localized text. Any HUD/announcement method that takes a message won't accept a raw string. Declare a <localizes> helper like Msg<localizes>(S:string):message = "{S}" and pass Msg("Go!"). There is no StringToMessage.
  • FirstLapCompletedEvent also triggers LapCompletedEvent. Completing lap one signals BOTH events. If you reward in both handlers you'll double-grant on the first lap — put one-time rewards only in OnFirstLap.
  • You must declare the device as an @editable field. Calling race_manager_device{}.Begin() on a literal does nothing useful — you need the placed device assigned through the Details panel so events actually flow.

Device Settings & Options

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

Race Manager Device settings and options panel in the UEFN editor — Number of Laps, Start Race on Game Start
Race Manager Device — User Options (1 of 2) in the UEFN editor: Number of Laps, Start Race on Game Start
Race Manager Device settings and options panel in the UEFN editor — Number of Laps, Start Race on Game Start
Race Manager Device — User Options (2 of 2) in the UEFN editor: Number of Laps, Start Race on Game Start
⚙️ Settings on this device (2)

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

Number of Laps
Start Race on Game Start

Guides & scripts that use race_manager_device

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

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