
Graceful Goodbyes: Detecting Player Leave
Overview
Every multiplayer island eventually meets the same uninvited guest: the mid-round dropout. A player rage-quits, loses connection, or gets called to dinner — and suddenly your round logic is waiting on someone who is never coming back. In Shell-Hunt terms: the golden shell walks out the door in a gone player's pocket, the scoreboard still counts their shells, and the "last collector standing" check never fires. Coral the flamingo has seen it all from her perch on South Shores, and her advice is simple: say a graceful goodbye, tidy up, and keep the round moving.
The real Verse API for this is fort_playspace.PlayerRemovedEvent() — a listenable(player) that fires whenever a human player leaves your island (the concept some creators call a "players left event"). Its sibling ParticipantRemovedEvent() covers any participant agent, including AI-controlled characters — handy when your round mixes players and NPCs. You will subscribe to the player event, and when it fires you will run a short, ordered cleanup routine: return their held loot to the round, drop their score entry, and check whether the round should end early.
What you will build
The mid-round dropout handler for the Shell-Hunt capstone — the piece that guarantees Shell-Hunt never soft-locks waiting on a player who already left. One creative_device that:
- Tracks every player's shell count in a
[player]intmap (seeded atOnBegin, extended when players join late). - Tracks who currently holds the golden shell (an
?playeroption, set from anitem_spawner_device'sItemPickedUpEvent). - Subscribes to
GetPlayspace().PlayerRemovedEvent()and, on a leave, runs cleanup in order: golden shell back into play → score entry removed → end-of-round check. - Ends the round via an
end_game_deviceif only one hunter remains, instead of waiting forever.
Walkthrough
Step 1 — Place the devices
In UEFN, place on your island:
- An Item Spawner configured to spawn your golden shell item (this device both spawns the shell and tells us who picked it up).
- A HUD Message device with its message set to something like
"The golden shell washes back ashore!". - An End Game device that declares the activating player's team the winner.
Step 2 — The complete dropout handler
Create a new Verse file, paste this in, build, and wire the three @editable slots in the Details panel:
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Shell-Hunt dropout handler: keeps the round fair and un-stuck
# when a player leaves mid-round.
shell_hunt_dropout_device := class(creative_device):
# Item Spawner configured with the golden shell item.
@editable
GoldenShellSpawner : item_spawner_device = item_spawner_device{}
# HUD Message announcing the shell is back in play.
@editable
DropoutMessage : hud_message_device = hud_message_device{}
# End Game device that crowns the last hunter standing.
@editable
WinnerScreen : end_game_device = end_game_device{}
# Per-player shell tally. A leaver's entry must be removed so
# averages/leaderboards don't count a ghost.
var ShellCounts : [player]int = map{}
# Who is holding the golden shell right now (nobody at round start).
var GoldenHolder : ?player = false
OnBegin<override>()<suspends>:void =
# Seed a zero tally for everyone already on the island.
for (P : GetPlayspace().GetPlayers()):
if (set ShellCounts[P] = 0) {}
# Playspace events use () before .Subscribe (unlike device events).
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerJoined)
GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerLeft)
# Track who picked up the golden shell.
GoldenShellSpawner.ItemPickedUpEvent.Subscribe(OnGoldenShellPickedUp)
# Late joiners get a fresh tally so the map always mirrors the lobby.
OnPlayerJoined(NewPlayer : player):void =
if (set ShellCounts[NewPlayer] = 0) {}
Print("A new hunter wades ashore.")
# Remember the current golden-shell holder.
OnGoldenShellPickedUp(Agent : agent):void =
# Bots are agents but not players — narrow before storing.
if (P := player[Agent]):
set GoldenHolder = option{P}
# THE graceful goodbye. Order matters here:
# 1) loot back into play, 2) score cleanup, 3) round-end check.
OnPlayerLeft(Leaver : player):void =
# Step 1: if the leaver held the golden shell, respawn it
# so the objective is never stranded in a gone inventory.
if (Holder := GoldenHolder?, Holder = Leaver):
set GoldenHolder = false
GoldenShellSpawner.SpawnItem()
DropoutMessage.Show()
Print("Golden shell returned to the beach.")
# Step 2: drop the leaver's tally from the score map.
RemoveFromCounts(Leaver)
# Step 3: never wait on a ghost — end the round if only
# one hunter remains.
CheckRemainingHunters()
# Verse maps have no delete — rebuild the map without the leaver.
RemoveFromCounts(Leaver : player):void =
var Rebuilt : [player]int = map{}
for (Key -> Value : ShellCounts, Key <> Leaver):
if (set Rebuilt[Key] = Value) {}
set ShellCounts = Rebuilt
# If one player is left, crown them instead of soft-locking.
CheckRemainingHunters():void =
Remaining := GetPlayspace().GetPlayers()
Print("Hunters remaining: {Remaining.Length}")
if (Remaining.Length = 1, LastHunter := Remaining[0]):
WinnerScreen.Activate(LastHunter)
Step 3 — Read the cleanup order like Coral does
| Order | Step | Why this order |
|---|---|---|
| 1 | GoldenShellSpawner.SpawnItem() |
The objective item must come back first — everything else can wait, but a stranded objective soft-locks the round. |
| 2 | RemoveFromCounts(Leaver) |
Score math (leaderboards, averages, "most shells wins" ties) must stop counting the ghost before anything reads the map. |
| 3 | CheckRemainingHunters() |
Run the end-of-round check last, against the already-cleaned state, so the winner screen reflects reality. |
A few Verse details worth pausing on:
PlayerRemovedEvent()is a playspace event, so it takes()before.Subscribe— device events likeItemPickedUpEventdo not. Mixing these up is the number-one compile error on this lesson.- Verse maps have no remove-key call. The proven pattern is to rebuild: loop
Key -> Valuewith aKey <> Leaverfilter into a fresh map, thensetthe variable to the rebuilt map. GoldenHolder : ?playeris an option:falsemeans "nobody",option{P}means "P holds it". Unwrapping withGoldenHolder?insideifis failable, which is exactly what we want — the whole golden-shell branch simply skips when nobody held it.
Common patterns
Pattern 1 — Participant tracking (players and NPCs)
Shell-Hunt's later rounds add AI beachcombers. Human players are players, but AI-controlled characters are agent participants — so the round-end check should count participants, and the leave handling should also hear ParticipantRemovedEvent():
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
participant_watch_device := class(creative_device):
OnBegin<override>()<suspends>:void =
# Participants = human players + registered AI agents.
Everyone := GetPlayspace().GetParticipants()
Print("Round starts with {Everyone.Length} participants.")
# Fires for ANY participant leaving — human or AI.
GetPlayspace().ParticipantRemovedEvent().Subscribe(OnParticipantLeft)
OnParticipantLeft(Gone : agent):void =
# player[] narrows: succeeds only for humans.
if (P := player[Gone]):
Print("A human hunter left the shores.")
else:
Print("An AI beachcomber wandered off.")
Print("Participants remaining: {GetPlayspace().GetParticipants().Length}")
Pattern 2 — Await-style leave watching
If you prefer a linear coroutine over a subscription (useful inside a round loop that also races timers), Await() the same event:
Where this goes next
This is the Shell-Hunt capstone's dropout-resilience piece. When you assemble the full minigame at the end of South Shores, shell_hunt_dropout_device slots in beside the shell-spawning and scoring pieces from the earlier lessons: the capstone's round manager imports the same [player]int tally shape you built here, subscribes its own listeners in OnBegin right next to these, and calls the same rebuild-the-map cleanup whenever PlayerRemovedEvent() fires. Because you handled the golden-shell edge case here, the capstone's win condition can trust two things unconditionally: the objective item is always somewhere reachable, and every entry in the score map belongs to a hunter who is actually still on the beach. That is what a graceful goodbye buys you — a round that never waits on someone who already sailed home.
Get the complete code — free
You've read the full walkthrough. The complete, copy-paste-ready Verse solution is free for members — sign in to unlock it.
Free with your BrainDead.TV / BrainDeadGuild Discord account. The walkthrough above is always free.
Check your understanding
Test yourself with an interactive quiz and track your progress + earn XP — free for members.
Turn this into a guided course
Add How to detect a player leaving mid-round with fort_playspace.PlayerRemovedEvent and run ordered cleanup (return held loot, remove score entries, end-of-round check) so the round never soft-locks to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.
Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.