Lobbies and Matchmaking
Lobby Basics
Lobbies are the matchmaking layer of the Photon Cloud. Every visible room is listed in exactly one lobby, and every matchmaking operation, room lists, random joins, filters, runs against the lobby you address. A lobby only organizes rooms; players in a lobby do not see or interact with each other.
The client automatically joins the default lobby when the connection is established, so simple games never need an explicit lobby call.
Call SetAutoJoinLobby(false) before connecting to opt out, for example when the game always targets a named lobby anyway.
Lobby Types
LobbyType::Default matches rooms by exact equality on their lobby-visible custom properties.
LobbyType::SqlLobby replaces equality matching with query-style filters over indexed properties and has its own page: SQL Lobby Matchmaking.
Joining and Leaving a Lobby
JoinLobby(name, type) enters a lobby and LeaveLobby() exits it; both return Task<Result<void>>.
Calling JoinLobby() without arguments targets the default lobby, and IsInLobby() reports the current membership.
Joining a lobby the client is already in fails instead of being a no-op. Because the client auto-joins the default lobby on connect, switching to a named lobby right after connecting means leaving the default lobby first.
Switch from the default lobby to a named one:
C++
Task<Result<void>> EnterRankedLobby(RealtimeClient& client)
{
Result<void> left = co_await client.LeaveLobby();
if (left.IsErr())
{
co_return left;
}
co_return co_await client.JoinLobby(PHOTON_STR("ranked"), LobbyType::Default);
}
C
/* Leave first, then join from the LeaveLobbyResult handler. */
realtime_leave_lobby(client);
if (e->type == RT_LeaveLobbyResult && e->mode == 0)
{
realtime_join_lobby(client, "ranked", 0); /* lobbyType 0 = Default */
}
/* lobbyType takes the numeric LobbyType, and only 0, 2 and 3 are valid, so
any other value falls back to Default.
realtime_set_auto_join_lobby(client, 0) before connecting opts out of
the automatic default-lobby join, which removes the need to leave it
first. */
Room Lists
While the client is in a lobby the server pushes listing updates through OnRoomListUpdated(const std::vector<RoomListing>&).
GetCachedRoomList() returns the most recent list without a round trip, which is usually all a room browser needs between updates.
| Field | Type | Description |
|---|---|---|
Name |
StringType |
Unique room name. |
PlayerCount |
int |
Players in the room. |
MaxPlayers |
uint8_t |
Player limit, 0 when unlimited. |
IsOpen |
bool |
Whether the room accepts joins. |
DirectMessaging |
DirectMode |
The room's direct messaging mode. |
CustomProperties |
RealtimeMap |
The room's lobby-visible custom properties. |
Random Matchmaking
JoinRandomRoom(MatchmakingOptions) asks the server to drop the player into a room matching the given options.
When no room matches, the Task resolves to ErrorCode::NoMatchFound instead of waiting for one to appear.
JoinRandomOrCreateRoom(createOptions, matchmakingOptions) closes the classic race between "no match found" and "create a room": when nothing matches, the server creates the room described by createOptions in the same operation.
This makes it the recommended default flow for drop-in multiplayer.
Match on a game mode and cap the room at eight players:
C++
Task<Result<MutableRoomView>> EnterDeathmatch(RealtimeClient& client)
{
MatchmakingOptions matchmaking;
matchmaking.Filter[PHOTON_STR("mode")] = PHOTON_STR("deathmatch");
matchmaking.MaxPlayers = 8;
CreateRoomOptions create;
create.MaxPlayers = 8;
create.CustomProperties[PHOTON_STR("mode")] = PHOTON_STR("deathmatch");
create.LobbyProperties = {PHOTON_STR("mode")};
co_return co_await client.JoinRandomOrCreateRoom(create, matchmaking);
}
C
/* Search: the filter travels as a SQL filter string, and the lobby to match in
is a parameter of the search itself. */
realtime_join_random_room(client,
8, /* maxPlayers */
0, /* matchmakingMode: FillRoom */
NULL, /* lobbyName: the default lobby */
0, /* lobbyType: Default */
NULL); /* sqlFilter: SQL lobbies only */
/* Fall back to creating a matching room when nothing was found. */
#define RT_ERROR_NO_MATCH_FOUND 24
if (e->type == RT_JoinRandomRoomResult && e->mode == RT_ERROR_NO_MATCH_FOUND)
{
rt_blob props = {0};
rt_blob keys = {0};
rt_begin(&props);
rt_put_str(&props, "mode", "deathmatch");
rt_end(&props);
realtime_create_room(client, NULL, 8, 1, 1, 0, 0, NULL, props.data, props.len);
rt_free(&props);
/* LobbyProperties are applied after the join, once in the room. */
rt_begin(&keys);
rt_add_str(&keys, "mode");
rt_end(&keys);
/* ... in the CreateRoomResult handler: */
realtime_room_set_lobby_properties(client, keys.data, keys.len);
rt_free(&keys);
}
/* Matchmaking Options Are Partly Available: realtime_join_random_room
covers MaxPlayers, Mode, LobbyName, Lobby and SqlFilter, but not the
equality Filter map or ExpectedUsers. Exact-equality matching on
lobby-visible properties is therefore only reachable from C++; from C,
express the same intent as a SqlFilter against a SQL lobby.
realtime_join_random_or_create_room takes no matchmaking parameters at
all - only the room it would create - so a filtered search plus fallback
has to be written as the two-step flow above. */
Matchmaking Options
| Field | Default | Description |
|---|---|---|
Filter |
empty | Custom properties a room must match exactly. |
MaxPlayers |
0 |
Only match rooms created with this player limit; 0 matches any. |
Mode |
MatchmakingMode::FillRoom |
The fill strategy. |
LobbyName |
empty | The lobby to match in; empty targets the default lobby. |
Lobby |
LobbyType::Default |
The type of the addressed lobby. |
SqlFilter |
empty | Query filter for the SQL lobby. |
ExpectedUsers |
empty | User ids to reserve slots for in the matched room. |
Filter compares exactly: a room matches only when every property in the filter equals the room's lobby-visible custom property of the same key.
Range or combination queries are not expressible here, that is what the SQL lobby is for.
MatchmakingMode picks the fill strategy.
FillRoom (the default) fills rooms one after another, which gets players into full matches fastest; SerialMatching distributes players over the matching rooms in order and RandomMatching spreads them randomly, which keeps room populations even at high player counts.
Lobby Statistics
GetLobbyStats() returns a Task<Result<std::vector<LobbyStats>>> with the current player and room counts of each lobby on demand.
Each LobbyStats entry carries the lobby Name, its Type, the PeerCount and the RoomCount.
For a continuously updated picture, construct the client with ClientConstructOptions.AutoLobbyStats = true (default false).
The server then pushes statistics updates through OnLobbyStats; this push is independent of lobby membership and of the SetAutoJoinLobby setting.