⌘K
↑↓ Navigate ↵ Open Esc Close
Tier 5: Matchmaking

Getting players into the same live match

Matchmaking and in-match broadcast, both push-based: no polling, no hand-rolled matchmaker.

Tier 5LobbyRTTGlobal App22 / 29

Lobbies: matchmaking without hand-rolling a matchmaker

Multiplayer / Relay Service for what happens after a lobby hands you a match.

brainCloud's Lobby service handles matchmaking in one call. findOrCreateLobby takes a lobby type, a matchmaking algorithm, and the player's data, and brainCloud either slots the player into a compatible in-progress search or starts one. No polling is required: every step (search started, a player joined, the room is assigned, the room is ready) arrives as a push event over the realtime connection the client already has open. The set of lobby types a game offers lives in portal config, not client code, so shipping a new game mode is a server-side edit plus a UI that reads it, no client release required.

How it works

  1. Read lobby types from the server instead of hardcoding them. AllLobbyTypes is a portal-managed global property: a JSON object where each entry looks like {"lobby": "CursorPartyV2", ...}. Every reference client reads it once at login via the Global App service and builds its lobby-type picker from the response, never a fixed list.
  2. Matchmake with findOrCreateLobby. Pass it a matching algorithm (the reference apps use {"strategy":"ranged-absolute","alignment":"center","ranges":[1000]}, which starts players near the middle of a rating band and widens it as the search runs), a rating, and an extra JSON blob carrying anything other members should see about the player: color choice, region ping, or anything else that matters for the game.
  3. Use the region-aware variant for latency-sensitive games. Call getRegionsForLobbies, ping each candidate region, then pass the ping map into findOrCreateLobbyWithPingData (or set it via extra.pings). That gives both the matchmaker and the lobby UI something to reason about beyond rating: actual latency.
  4. Everything after that arrives on its own; nothing needs to be fetched. Register a lobby event callback once. MATCHMAKING_IN_PROGRESS, MEMBER_JOIN, MEMBER_LEFT, STARTING, ROOM_ASSIGNED, ROOM_READY, DISBANDED all arrive on it with the full lobby state attached. No reference app re-fetches with getLobbyData after the initial join.

Code

C++
// Dynamic lobby-type list: never hardcoded
pBCWrapper->getBCClient()->getGlobalAppService()->readProperties(
    new BCCallback([](const Json::Value &result) { applyLobbyTypes(result); }, ...));
// AllLobbyTypes.value is a JSON-encoded string; each value has a "lobby" field

// Matchmake
pBCWrapper->getLobbyService()->findOrCreateLobby(
    lobbyType, 0, 1,
    "{\"strategy\":\"ranged-absolute\",\"alignment\":\"center\",\"ranges\":[1000]}",
    "{}", {}, "{}", false,
    buildExtraJson(),  // {"colorIndex":N,"rank":N,"pings":{...}}
    settings.teamCode,
    new BCCallback([](const Json::Value &) {},
                    [](const std::string &msg) { errorAndReturnToMenu(msg); }));
JavaScript
// Dynamic lobby-type list
this.bc.globalApp.readProperties(res => {
  const parsed = JSON.parse(res.data.AllLobbyTypes.value)
  this.setState({ appLobbies: Object.values(parsed) })
})

// Matchmake
const algo = { strategy: 'ranged-absolute', alignment: 'center', ranges: [1000] }
this.bc.rttService.registerRTTLobbyCallback(this.onLobbyEvent.bind(this))
this.bc.lobby.findOrCreateLobby(
  lobbyType, 0, 1, algo, {}, null, {}, false, extraJson, '',
  result => { /* search started */ })
Godot (C#)
_brainCloudWrapper.LobbyService.GetRegionsForLobbies(new[] { lobbyType },
    (regionsResponse, cbObj) => {
        _brainCloudWrapper.LobbyService.PingRegions((pingResponse, _) => {
            foreach (var kv in _brainCloudWrapper.LobbyService.PingData) pingData[kv.Key] = (int)kv.Value;
            extraJSON["pings"] = pingData;
            _brainCloudWrapper.LobbyService.FindOrCreateLobbyWithPingData(
                lobbyType, rating, maxSteps, algo, filterJSON, isReady, extraJSON,
                teamCode, settings, otherUserCxIDs, OnSuccess, OnFailed);
        }, (status, reasonCode, jsonError, _) => DoFindLobby(false));
    }, (status, reasonCode, jsonError, cbObj) => DoFindLobby(false));
Godot (GDScript)
var algo := {"strategy": "ranged-absolute", "alignment": "center", "ranges": [1000]}
var extra := {"colorIndex": AppState.my_color_index, "presentSinceStart": true}
if use_ping:
    await AppState.bc.lobby_service.get_regions_for_lobbies([lobby_type])
    var ping_data: Dictionary = await AppState.bc.lobby_service.ping_regions()
    extra["pings"] = ping_data
    AppState.bc.lobby_service.set_ping_data(ping_data)
    return await AppState.bc.lobby_service.find_or_create_lobby_with_ping_data(
        lobby_type, 0, 1, algo, {}, {}, false, extra, team_code, [])

Gotchas worth knowing up front

  • extra is the only per-member broadcast channel at join time. Anything another lobby member needs to know about a player before the match starts (color pick, team, region latency) rides extra, since there's no separate lobby-profile endpoint. It round-trips through every subsequent lobby event.