Rooms are where players meet. Every room has a short join code, a roster, and a host. This guide covers the full room lifecycle and the events that keep your UI in sync.
The zero-code option: LobbyBrowser
Before writing any lobby code, know that you may not have to: drop a LobbyBrowser node (Create Node dialog → LobbyBrowser) into a scene and you get a complete lobby — player name, Create room, Quick match, join by code, and a live list of public rooms. Connect one signal and it's done:
$LobbyBrowser.entered_room.connect(func(room):
get_tree().change_scene_to_file("res://arena.tscn"))
It's built from standard Controls, so your project's Theme restyles it, and the
Inspector exports cover the common tweaks (quick-match size, default room size,
hide-on-join, which widgets to show). When your game outgrows it, everything it does
is public SDK API — the rest of this page.
Create a room
await SpawnWeaver.start()
var result := await SpawnWeaver.create_room()
if result.ok:
show_code(result.room.code) # e.g. "4V8772" — share with friends
All options are optional:
var result := await SpawnWeaver.create_room({
"name": "Arena", # display name
"visibility": "public", # "private" (default) or "public"
"max_players": 4, # omitted = unlimited
"metadata": {"mode": "ffa"}, # your own data, visible to joiners and in listings
})
The creator becomes the host. Creating fails with already-in-room if you are
already in a room — leave it first.
Join and leave
var result := await SpawnWeaver.join_room("4V8772")
if not result.ok:
match result.error.code:
"room-not-found": say("No such room — check the code.")
"room-full": say("That room is full.")
_: say(result.error.message)
await SpawnWeaver.leave_room()
join_room() accepts a share code (works for private and public rooms) or a public
room's id from list_rooms(). Note: max_players counts briefly disconnected
members too — a seat inside the grace window is still a seat.
Private vs. public, and listing
- Private (default): joinable only by someone who has the code.
- Public: also appears in listings and can be joined by id — this is your lobby browser.
var result := await SpawnWeaver.list_rooms(20)
if result.ok:
for summary in result.rooms: # Array of SWRoomSummary
print("%s %d/%s players" % [
summary.name if not summary.name.is_empty() else summary.code,
summary.player_count,
"∞" if summary.max_players == 0 else str(summary.max_players)])
if summary.has_space():
add_join_button(summary.id) # join_room(summary.id) works too
Listings return the project's public rooms, most recently active first.
The roster: SpawnWeaver.room
While you are in a room, SpawnWeaver.room is an SWRoom kept up to date
automatically — you never mutate it yourself:
var room := SpawnWeaver.room
room.code # "4V8772"
room.players # Array[SWPlayer] — the full roster, including you
room.host() # the host's SWPlayer (or null)
room.other_players() # everyone except you
room.get_player(id) # lookup by player id
room.metadata # your developer metadata
SpawnWeaver.is_host # true when you are the host
React to roster changes with signals (they never echo yourself):
SpawnWeaver.player_joined.connect(func(p): add_row(p))
SpawnWeaver.player_left.connect(func(p, reason): remove_row(p.id))
SpawnWeaver.room_joined.connect(func(room): rebuild_ui(room)) # you entered a room
SpawnWeaver.room_left.connect(func(reason): show_menu()) # you left ("left"/"disconnected"/"expired")
room_joined fires on create, join, matchmaking, and automatic resume after a
reconnect — treat it as "rebuild everything from room", not "the join button was
clicked". Rebuild idempotently from room.players.
Host and host migration
Every room always has a host — room.host_id — including matchmade rooms (the first
matched player). Only the host may call update_room() and set_room_state().
Migration is automatic: when the host leaves or disconnects, the earliest-joined connected player is promoted immediately (the game never waits out the grace window to write room state). If the ex-host returns, they come back as a regular member.
SpawnWeaver.host_changed.connect(func(new_host):
if SpawnWeaver.is_host:
take_over_game_logic()) # you were promoted
Updating a room (host only)
var result := await SpawnWeaver.update_room({"max_players": 6, "name": "Arena II"})
Only the fields you pass change — except metadata, which replaces the whole map.
Non-hosts get not-host. All members receive room_updated(room).
Room metadata
metadata is a free-form dictionary for anything joiners should see before or after
joining: game mode, map name, difficulty. It appears in SWRoomSummary entries too, so
your lobby browser can filter on it. It is not gameplay state — use
room state for values that change during play.
The disconnect grace window
When a member's connection drops, they are not removed immediately:
- Their roster entry stays, with
connected = false. You receiveplayer_disconnected(p)— grey out their row, pause their turn. - If they return within the grace window (default 60s) — by token reconnect or by
rejoining with the code — their seat and their entities are restored. You receive
player_reconnected(p). - If the window expires, you receive
player_left(p, "disconnected")and their entities are removed (entity_removed(id, "owner-left")).
SpawnWeaver.player_disconnected.connect(func(p): mark_row_ghosted(p.id))
SpawnWeaver.player_reconnected.connect(func(p): mark_row_active(p.id))
Your own side of this is symmetric: if your connection blips, the SDK reconnects and
the server puts you straight back into the room — room_joined fires again with the
fresh roster and state. Rooms with no connected members expire after a short TTL
(default 60s); members still in grace then receive room_left("expired").
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Calling create_room()/join_room() before start() |
SWResult fails with not-connected |
await SpawnWeaver.start() first (once, at boot) |
| Creating/joining while already in a room | already-in-room |
One room per connection — await SpawnWeaver.leave_room() first |
Treating room_joined as "user clicked join" |
Duplicate UI/avatars after a reconnect resume | Rebuild idempotently from SpawnWeaver.room each time it fires |
Removing players on player_disconnected |
Players vanish during a 2-second Wi-Fi blip | Grey them out; only remove on player_left |
| Assuming the host never changes | Host-only calls start failing with not-host after migration |
Watch host_changed and check SpawnWeaver.is_host before host-only calls |
Passing partial metadata to update_room() |
The rest of the metadata is erased | metadata replaces the whole map — send the complete dictionary |
Next
- Matchmaking — fill rooms automatically
- State sync — room state and entities
- Events — transient messages between members