Matchmaking pairs players who queue with the same mode, region, and match size, then drops them all into a freshly created room. One awaitable call takes you from "Find match" to "in a room with opponents".
Find a match
var result := await SpawnWeaver.find_match("duel")
if result.ok:
start_game(result.room) # a normal SWRoom — roster, code, host, state
else:
match result.error.code:
"match-timeout": say("Nobody around right now — try again.")
"cancelled": pass # the player backed out
_: say(result.error.message)
find_match(mode, options) queues a ticket and resolves when the match is found —
which can take seconds, or as long as the server-side timeout. The await handles the
whole wait; there is no polling.
Modes, regions, and sizes
Matching is exact: players are bucketed by project + mode + region + size, and a
room is created the moment a bucket fills. First-come, first-served.
await SpawnWeaver.find_match("duel") # size 2, global
await SpawnWeaver.find_match("ffa", {"size": 4}) # 4-player bucket
await SpawnWeaver.find_match("ranked", {"region": "eu", "size": 2}) # region-scoped
| Option | Default | Notes |
|---|---|---|
mode |
"default" |
Any string without \|. Your own vocabulary: "duel", "coop", … |
region |
"global" |
Any string without \|. Only players in the same region match. |
size |
2 |
2–64 players. The room is created when exactly this many are queued. |
Players with different modes, regions, or sizes never match each other — a player
searching {"size": 2} and one searching {"size": 3} sit in separate buckets. Keep
your option matrix small, or players will wait.
Cancelling
SpawnWeaver.cancel_matchmaking()
Cancelling makes any awaited find_match() resolve immediately with error code
cancelled, and removes the server-side ticket. Disconnecting also removes the ticket.
Calling find_match() again while searching simply replaces the previous ticket
(the earlier await resolves with the newer outcome pathway — don't run two searches
in parallel; there is one ticket per connection).
Timeouts
If no bucket fills within the server's matchmaking timeout (default 30 seconds,
deployment-configurable), the ticket expires and find_match() resolves with
match-timeout. The error is marked retryable — offer a "Search again" button, or
retry automatically with a message:
func search_forever() -> void:
while true:
var result := await SpawnWeaver.find_match("duel")
if result.ok:
start_game(result.room)
return
if result.error.code != "match-timeout":
return # cancelled, disconnected, …
say("Still searching…")
The SDK also applies its own client-side guard (120s) so an await can never hang
forever, even if the server reply is lost — that surfaces as a retryable timeout.
A matched room is a normal room
The result of find_match() is a regular SWRoom:
- It has a host — the first matched player — with all the usual host powers
(room state,
update_room()), and normal host migration. - It has a join code, so a disconnected player can rejoin within the grace window.
room_joinedfires,PlayerSpawnerspawns avatars,SpawnSyncstarts syncing — everything from the Rooms guide applies unchanged.
You cannot search while in a room — find_match() fails with already-in-room.
leave_room() first (e.g. for a "requeue" button after a match).
UI pattern: searching spinner with cancel
func _on_find_match_pressed() -> void:
_spinner.visible = true
_cancel_button.visible = true
var result := await SpawnWeaver.find_match("duel")
_spinner.visible = false
_cancel_button.visible = false
if result.ok:
get_tree().change_scene_to_file("res://levels/arena.tscn")
elif result.error.code == "match-timeout":
_status.text = "No opponents found — try again?"
elif result.error.code != "cancelled":
_status.text = "Matchmaking failed: %s" % result.error.message
func _on_cancel_pressed() -> void:
SpawnWeaver.cancel_matchmaking() # the await above resolves with "cancelled"
Because the outcome comes back through the same await, the spinner logic lives in one
function — no signal bookkeeping.
Testing matchmaking locally
Run two instances (Debug → Run Multiple Instances → 2) and press "Find match" in
both. With the default size of 2, they match each other within a second. For larger
sizes, run that many instances — every instance is its own player identity.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
| Searching while in a room | already-in-room |
await SpawnWeaver.leave_room() before find_match() |
Using \| in mode or region |
invalid-payload |
The \| character is reserved; pick another separator |
size of 1 or above 64 |
invalid-payload |
Sizes are 2–64 |
| Too many mode/region/size combinations | Players wait forever in separate buckets | Start with one mode and "global"; add options when you have the player base |
| No timeout handling | Players stare at an endless spinner | Handle match-timeout — it is retryable by design |
| Forgetting the matched room has a host | Nobody writes room state; game never starts | The first matched player is host — check SpawnWeaver.is_host and act |
Next
- Rooms — everything the matched room can do
- State sync — start the game with host-written room state