Reference Verse compiles

Firefly Cove at Dusk: PlayerAddedEvent and the join lifecycle

Fortnite islands are drop-in worlds: players arrive whenever they please, not just at match start. This lesson teaches the full join lifecycle — subscribe to `GetPlayspace().PlayerAddedEvent()` for future arrivals, sweep `GetPlayspace().GetPlayers()` in `OnBegin` for everyone already aboard, and make registration idempotent so overlapping signals never double-book a racer. You will build the Coastal Race lobby registrar: every joiner lands in the Racers roster with a boat dock assigned, reusing the South Shores spawn pad as a third registration signal.

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

Overview

When your UEFN island is running, players can join at any moment — mid-game, at the start, or even after a round has begun. Your Verse devices don't automatically know when this happens. PlayerAddedEvent solves that: it is a listenable(player) event on the fort_playspace that fires once per joining player, delivering the player object directly to your callback.

When to reach for it:

  • Initialising per-player data (scores, inventories, persistent maps) the moment someone joins.
  • Greeting players with a HUD message or cinematic when they arrive at your dock.
  • Balancing teams dynamically as players trickle in.
  • Gating game-start logic until a minimum player count is reached.

Because the event is on the playspace (not on any placed device), you access it via GetPlayspace().PlayerAddedEvent() and subscribe a handler method.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: You're building a pirate cove island. The moment a sailor (player) rows in, a customizable light on the dock flashes to life, and the game logs how many crew members have arrived. We use PlayerAddedEvent to detect the join, GetPlayspace() to reach the playspace, and a customizable_light_device to give visible feedback on the dock.

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

# A log channel so we can see crew-arrival messages in the Output Log.
cove_log := class(log_channel){}

cove_welcome_manager := class(creative_device):

    # Wire this to a Customizable Light device placed on your dock in the editor.
    @editable DockLight : customizable_light_device = customizable_light_device{}

    # We track how many sailors have arrived this session.
    var CrewCount : int = 0

    # Entry point — subscribe to the playspace join event.
    OnBegin<override>()<suspends> : void =
        Logger := log{Channel := cove_log}
        Logger.Print("Cove is open — waiting for sailors...")

        # Grab the playspace and subscribe our handler.
        Playspace := GetPlayspace()
        Playspace.PlayerAddedEvent().Subscribe(OnSailorArrived)

        # Keep the device alive so the subscription stays active.
        loop:
            Sleep(60.0)

    # Called automatically every time a player joins.
    # The payload is a plain `player` (not ?agent), so no unwrap needed.
    OnSailorArrived(NewSailor : player) : void =
        set CrewCount = CrewCount + 1

        # Flash the dock light so everyone on the island sees the arrival.
        DockLight.Enable()

        Logger := log{Channel := cove_log}
        Logger.Print("A new sailor arrived! Crew aboard: {CrewCount}")```

**Line-by-line explanation:**

| Lines | What's happening |
|---|---|
| `@editable DockLight` | Declares the dock light so you can wire it in the UEFN Details panel. |
| `var CrewCount : int = 0` | Mutable counter — incremented each join. |
| `GetPlayspace()` | Returns the `fort_playspace` for the running session. |
| `.PlayerAddedEvent().Subscribe(OnSailorArrived)` | Registers `OnSailorArrived` as the callback; it fires once per joining player. |
| `loop: Sleep(60.0)` | Keeps `OnBegin` suspended so the subscription is never dropped. |
| `OnSailorArrived(NewSailor : player)` | Handler signature — payload is `player`, not `?agent`, so no option unwrap. |
| `set CrewCount += 1` | Mutates the crew counter (requires `set` keyword for `var` fields). |
| `DockLight.Enable()` | Turns the dock light on — visible feedback that someone joined. |

## Common patterns

### Pattern 1 — Initialise per-player data in a weak_map

The most common real-world use: seed a `weak_map` with default data the moment a player joins, so later lookups never fail.

```verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }

# A simple per-player score record.
player_score_data := struct:
    Score : int = 0
    Kills : int = 0

cove_score_tracker := class(creative_device):

    # Persistable weak_map: player -> their score data.
    var PlayerScores : weak_map(player, player_score_data) = map{}

    OnBegin<override>()<suspends> : void =
        GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerJoined)
        loop:
            Sleep(60.0)

    OnPlayerJoined(JoiningPlayer : player) : void =
        # Only seed if this player isn't already tracked.
        if (not PlayerScores[JoiningPlayer]):
            if (set PlayerScores[JoiningPlayer] = player_score_data{}) {}

Pattern 2 — Count players and enable a device when the crew is full

Wait until enough sailors have joined before enabling the island's treasure-hunt trigger.

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

cove_crew_gate := class(creative_device):

    # Wire to a Trigger device that starts the treasure hunt.
    @editable TreasureHuntTrigger : trigger_device = trigger_device{}

    # Minimum sailors before the hunt begins.
    @editable MinCrew : int = 4

    var ArrivedCount : int = 0

    OnBegin<override>()<suspends> : void =
        # Disable the trigger until the crew is full.
        TreasureHuntTrigger.Disable()
        GetPlayspace().PlayerAddedEvent().Subscribe(OnCrewMemberArrived)
        loop:
            Sleep(60.0)

    OnCrewMemberArrived(Sailor : player) : void =
        set ArrivedCount += 1
        if (ArrivedCount >= MinCrew):
            # Full crew — open the treasure hunt!
            TreasureHuntTrigger.Enable()

Pattern 3 — Dim the dock light when the first player arrives, then re-enable it

Demonstrates calling both Disable and Enable on a customizable_light_device in response to join events, showing the full light-control surface.

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

cove_light_greeter := class(creative_device):

    @editable HarbourLight : customizable_light_device = customizable_light_device{}

    var FirstPlayerArrived : logic = false

    OnBegin<override>()<suspends> : void =
        # Start with the light dimmed — the cove is empty.
        HarbourLight.DimLight()
        GetPlayspace().PlayerAddedEvent().Subscribe(OnArrival)
        loop:
            Sleep(60.0)

    OnArrival(Sailor : player) : void =
        if (not FirstPlayerArrived):
            set FirstPlayerArrived = true
            # First sailor docks — light up the harbour!
            HarbourLight.Enable()

Gotchas

1. The handler receives player, not ?agent — no unwrap needed. PlayerAddedEvent() is typed listenable(player), so your callback signature is OnArrival(Sailor : player) : void. Don't try to unwrap it as ?agent — that will fail to compile. If you need an agent for a method that requires one, player is already a subtype of agent in Verse, so you can pass it directly.

2. Subscribe in OnBegin, not at class scope. You cannot call GetPlayspace() at class-definition time. Always subscribe inside OnBegin (or a method called from it). Attempting GetPlayspace() outside a running context will cause a runtime error.

3. Keep OnBegin alive with a loop. If OnBegin returns, the subscription may be garbage-collected and stop firing. The idiomatic fix is loop: Sleep(60.0) at the end of OnBegin — it costs nothing and keeps the coroutine (and all its subscriptions) alive for the session.

4. Late-joining players are NOT retroactively detected. If your island starts and three players are already present, PlayerAddedEvent will NOT fire for them — it only fires for players who join after your subscription is set up. For players present at startup, iterate GetPlayspace().GetPlayers() in OnBegin before (or alongside) subscribing.

5. var fields require the set keyword to mutate. Writing CrewCount += 1 will not compile. You must write set CrewCount += 1. This trips up many beginners coming from other languages.

6. No using lines in your snippet? Add the right ones. PlayerAddedEvent lives in /Fortnite.com/Playspaces. Without using { /Fortnite.com/Playspaces } at the top of your file, the compiler reports Unknown identifier: PlayerAddedEvent. Always include this import alongside the standard /Fortnite.com/Devices and /Verse.org/Simulation.

Guides & scripts that use fort_playspace

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

Build your own lesson with fort_playspace

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 →