Unity SDK.
A small, hand-written Unity client for the matchmaking API, with no generated boilerplate. Every call is a coroutine over UnityWebRequest that reports an ApiResult<T> through a callback. Drop the client into a scene, point a config asset at your server, and call the queue / match / player / SSE surface.
This is the SDK-specific setup. The endpoint-level reference (every route, request shape and response) lives in the API reference.
Requirements
- Unity 2021.3 LTS or newer (2022 / Unity 6 are fine).
- The Newtonsoft Json package (
com.unity.nuget.newtonsoft-json), an SDK dependency you install separately.
Install
Grab MatchKit.Matchmaking.unitypackage (the classes and editor tools, no netcode) from the dashboard, then import it one of two ways:
- .unitypackage: double-click it, or Assets ▸ Import Package ▸ Custom Package…, to drop the SDK under
Assets/MatchKit.Matchmaking. - UPM package: Package Manager ▸ + ▸ Add package from disk… and pick its
package.json.
Install the Newtonsoft Json package first: Package Manager ▸ + ▸ Add package by name… ▸ com.unity.nuget.newtonsoft-json. The SDK won't compile without it.
Setup
- Open Tools ▸ MatchKit Matchmaking ▸ Setup.
- Click Create Config Asset…, then enter your Base URL and API Key and Test Connection.
- Add a Matchmaking Client component to a GameObject and drag the config asset onto it.
The config is a ScriptableObject, so you can also create it via Assets ▸ Create ▸ MatchKit ▸ Matchmaking Config and edit it in its custom inspector.
Usage
Inspect IsSuccess, then read Data on success or Error / NetworkError on failure. Lifetime stats are maintained server-side, so read them with GetPlayer(id) or listen for the match-ended SSE event.
using MatchKit.Matchmaking;
using UnityEngine;
public class MatchmakingDemo : MonoBehaviour
{
// Add a MatchmakingClient component and point it at a MatchmakingConfig asset.
[SerializeField] private MatchmakingClient matchmaking;
private SseConnection _sse;
void Start()
{
// externalId is your game's own stable user id (create + update key off it).
var request = new PlayerRequest {
externalId = "user-42",
username = "ace",
region = "eu",
matchmakingMode = MatchmakingMode.MMR,
ratingMode = RatingMode.TS,
};
matchmaking.CreatePlayer(request, created => {
if (!created.IsSuccess) { Debug.LogError(created); return; }
long playerId = created.Data.id;
// Server pushes arrive over SSE: "queue-update" as a match forms,
// "match-ended" carrying the player's fresh stats afterwards.
_sse = matchmaking.Subscribe(playerId);
_sse.OnEvent += (name, json) => Debug.Log($"{name}: {json}");
// Join: region, modes and rating are resolved server-side from the
// stored player; the client never sends a score.
matchmaking.JoinQueue(playerId, gameMode: "ranked", teamSize: 5, r => Debug.Log(r));
});
}
void OnDestroy() => _sse?.Close();
}Handing off to Mirror
MatchKit ends at a matchId and hands the roster back; your netcode takes it from there (the full picture is in Where MatchKit fits). A demo package ships that handoff for Mirror: import the SDK plus the Mirror package and add MATCHKIT_MIRROR under Project Settings ▸ Player ▸ Scripting Define Symbols.
The bridge is host-authoritative. On the match-found event every client runs the same election over the roster, and the lowest playerId hosts, so exactly one calls StartHost() and the rest StartClient(), with no extra round-trip.
using System.Linq;
using MatchKit.Matchmaking;
using Mirror;
using Newtonsoft.Json.Linq;
using UnityEngine;
// MatchKit tells you *who* is in the match; Mirror moves the bytes between
// them. This is the entire handoff: on match-found, agree on a host and either
// StartHost() or StartClient(). Guard it behind the MATCHKIT_MIRROR define.
public class MirrorMatchHandoff : MonoBehaviour
{
[SerializeField] private MatchmakingClient matchmaking;
[SerializeField] private long localPlayerId;
private SseConnection _sse;
void Start()
{
// The same SSE stream the base client uses: "match-found" fires once a
// full roster forms, carrying { matchId, teams[][] }.
_sse = matchmaking.Subscribe(localPlayerId);
_sse.OnEvent += OnServerEvent;
}
void OnServerEvent(string name, string json)
{
if (name != "match-found") return;
// "teams" is every roster in stable order — two for a classic match,
// ten for a ten-team FFA. Flatten it and you never mind the shape.
long[] roster = JObject.Parse(json)["teams"]
.SelectMany(team => team)
.Select(entry => entry["playerId"].Value<long>())
.ToArray();
// Every client sees the identical roster, so a pure function of it
// elects the same host with no round-trip: lowest playerId hosts.
long hostId = roster.Min();
if (localPlayerId == hostId)
{
NetworkManager.singleton.StartHost();
}
else
{
// Mirror only needs the host's address string. How you learn it is
// your game's call: a relay, a Steam lobby, your own signalling.
NetworkManager.singleton.networkAddress = ResolveHostAddress(hostId);
NetworkManager.singleton.StartClient();
}
}
void OnDestroy() => _sse?.Close();
}MatchKit never carries the host's network address; it isn't in the match payload. The elected host is agreed here; how a client reaches it (a relay, a Steam lobby, your own signalling) is the one piece your game still owns.
Other netcode
MatchKit is netcode-agnostic, so the REST + SSE API works with any of these today. What's not written yet is the ready-made handoff package, so these are coming soon:
- Photon PUN 2Coming soonmatchId as the room name; Photon Cloud relays, so there's no host election.
- Fish-NetworkingComing soonSame host-authoritative shape as Mirror (v4 API), plus Steamworks via FishySteamworks.
The pre-alpha is invite-only, and keys are handed out one at a time. Tell me what you're building and I'll send you one.
Request an invite