Docs / Learn / Connect to a match

Connect to a match

Get two clients into the same match, so the next step has somewhere to move your fighter.

In step 6 you gave your arena a mode and confirmed a match can be created. A match is an arena round: a bounded, ephemeral fight. A client does not own it: the client sends the intent to join, the server binds the match to that session, and from then on the server routes that client's input and broadcasts state to it.

There are two ways a client ends up in a match.

  • Direct join. The client already holds a match_id (from browsing joinable matches, an invite, or a shared code) and asks to join it.
  • Matchmaker-formed. The client enqueues for a mode, the server pairs it with others, creates the match, and places every paired client into it. No explicit join follows: the server pushes a "matched" event carrying the shared match_id, and state starts flowing.

Both reach your game's join callback (step 6). This page is client-side; the game logic does not change.

The one rule: register the state handler before you join

The server can start pushing match.state (and, on the matchmaker path, the matched event) the instant you are in the match. If you register your receive handler after joining, you race the server and drop the first frames. Register first, join second, every SDK.

Cloud vs self-hosted: identical here

Every call below is the same whether you deploy to Asobi Cloud or self-host. The only difference is the base server URL you configured when you built the client back in step 2: a console.asobi.dev environment URL for cloud, your own host:8084 for self-hosted. Joining is WebSocket-only by design; there is no REST join. The wire reference is WebSocket protocol -> Matches; the per-language mapping is Realtime API. For the matchmaker itself see Matchmaking.

Assume you have already connected and authenticated (steps 3-4). mode is the arena mode from step 6.

Defold. rt = client.realtime. Callbacks register with rt:on(event, fn), using the SDK's mapped event names. Register before joining. Install/auth: asobi-defold README.

Godot. Realtime is the Asobi.realtime autoload; you receive on Godot signals. Wire the signals before connecting or enqueuing. Install/auth: asobi-godot README.

Unity. Subscribe to the C# events with +=; handlers receive the raw JSON envelope string and you parse it yourself. Subscribe before you await the join. The matched member is OnMatchmakerMatched (it carries the wire match.matched). Install/auth: asobi-unity README.

Unreal. Bind the dynamic multicast delegates on UAsobiWebSocket; each handler must be a UFUNCTION and receives a raw JSON string. Bind before joining. Handler signature: void OnState(const FString& StateJson). Direct joins surface on OnMatchJoined; a matchmade placement surfaces on OnMatchMatched. Bind whichever path you use. Install/auth: asobi-unreal README.

Dart/Flame. Realtime exposes broadcast streams; listen with .stream.listen(...). Payloads are typed. Attach the listeners before you join. Install/auth: asobi-dart README.

JavaScript. The transport is asobi.websocket({token}); there is no client.realtime. Events use raw wire names (dots). send is an awaited RPC; sendFire is fire-and-forget. Register the handler before joining. Install/auth: asobi-js README.

LOVE. client.realtime, colon syntax, mapped event names. Register with :on(event, fn) before joining. LOVE has a manual pump: call client.realtime:update() every frame from love.update(dt) or no callbacks fire. Install/auth: asobi-love2d README.

rt:on("match_state", function(state) end)
rt:on("match_matched", function(payload) end)

rt:join_match(match_id)          -- direct join

rt:add_to_matchmaker(mode)       -- matchmaker path; match_matched fires with the match_id
Asobi.realtime.match_state.connect(_on_state)
Asobi.realtime.match_matched.connect(_on_matched)

Asobi.realtime.join_match(match_id)          # direct join

Asobi.realtime.add_to_matchmaker(mode)       # matchmaker path

func _on_state(payload: Dictionary) -> void: pass
func _on_matched(payload: Dictionary) -> void: pass
client.Realtime.OnMatchState += rawJson => { /* parse */ };
client.Realtime.OnMatchmakerMatched += rawJson => { /* parse */ };

await client.Realtime.JoinMatchAsync(matchId);            // direct join

await client.Realtime.AddToMatchmakerAsync(mode);         // matchmaker path
WebSocket->OnMatchState.AddDynamic(this, &UMyClass::OnState);
WebSocket->OnMatchMatched.AddDynamic(this, &UMyClass::OnMatched);

WebSocket->JoinMatch(MatchId);                            // direct join

// matchmaker path: enqueue via UAsobiMatchmaker, then OnMatchMatched fires
Matchmaker->Add(Mode, Party, OnResponse);
client.realtime.onMatchState.stream.listen((MatchState state) {});
client.realtime.onMatchmakerMatched.stream.listen((MatchmakerMatch m) {});

await client.realtime.joinMatch(matchId);                 // direct join

await client.realtime.addToMatchmaker(mode: mode);        // matchmaker path
ws.on("match.state", (payload) => {});
ws.on("match.matched", (payload) => {});

const reply = await ws.send("match.join", { match_id });   // direct join, awaited

await ws.send("matchmaker.add", { mode });                 // matchmaker path
client.realtime:on("match_state", function(state) end)
client.realtime:on("match_matched", function(payload) end)

client.realtime:join_match(match_id)          -- direct join

client.realtime:add_to_matchmaker(mode)       -- matchmaker path

function love.update(dt)
  client.realtime:update()
end

Checkpoint

Run two clients against the same server. In each, register the matched handler, then enqueue both for the arena mode:

  • client A: add_to_matchmaker(mode)
  • client B: add_to_matchmaker(mode)

The server pairs them, creates one match, and pushes the matched event to both. Log the match_id from each. You have connected to a match when both clients print the same match_id and each starts receiving match_state. If only one logs, or the ids differ, you registered the handler after enqueuing: move it before.

Next: Step 8 - Run a match

Send an input from one client, the server moves your fighter, and the new match.state renders on both.