Rooms and Players
Creating a Room
CreateRoom(name, options) creates a room on the server and returns a Task<Result<MutableRoomView>>.
On success the client is already inside the new room as its first player, which also makes it the master client.
The returned MutableRoomView reads and modifies the room, as described below.
A typical creation sets a name, a player limit and the visibility:
C++
Task<Result<void>> CreateMatch(RealtimeClient& client)
{
CreateRoomOptions options;
options.MaxPlayers = 4;
options.IsVisible = false; // Not listed in the lobby; players join by name.
Result<MutableRoomView> result = co_await client.CreateRoom(PHOTON_STR("match-42"), options);
if (result.IsErr())
{
co_return Result<void>::Err(result.GetError());
}
co_return Result<void>::Ok();
}
Room names are unique within a region and application.
Creating a name that already exists fails with ErrorCode::RoomAlreadyExists; use JoinOrCreateRoom when either outcome is acceptable.
Room Creation Options
| Field | Type | Default | Description |
|---|---|---|---|
IsVisible |
bool |
true |
Lists the room in the lobby and makes it eligible for random matchmaking. |
IsOpen |
bool |
true |
Allows players to join. A closed room rejects joins but keeps its current players. |
MaxPlayers |
uint8_t |
0 |
Maximum number of players. 0 means no limit. |
CustomProperties |
RealtimeMap |
empty | Initial custom properties of the room. See Custom Properties. |
LobbyProperties |
std::vector<StringType> |
empty | Keys of the custom properties that are also visible in the lobby. |
LobbyName |
StringType |
empty | Name of the lobby the room is listed in. |
Lobby |
LobbyType |
Default |
Type of that lobby. See Lobbies and Matchmaking. |
PlayerTtlMs |
int |
0 |
How long a leaving player stays inactive in the room and may rejoin, in milliseconds. |
EmptyRoomTtlMs |
int |
0 |
How long the room survives without any players, in milliseconds. |
SuppressRoomEvents |
bool |
false |
Stops the server from sending join and leave events for this room. |
PublishUserId |
bool |
false |
Makes each player's UserId visible to the other players in the room. |
DirectMessaging |
DirectMode |
None |
Peer-to-peer topology of the room. See Direct Messaging. |
Plugins |
std::vector<StringType> |
empty | Names of the server plugins to run for this room. |
ExpectedUsers |
std::vector<StringType> |
empty | User ids to reserve slots for. |
A few fields deserve extra attention because they shape the room's whole life.
PlayerTtlMs decides whether leaving players may rejoin their slot, EmptyRoomTtlMs lets the room survive being empty for a while, and IsVisible and IsOpen separate matchmaking visibility from joinability.
SuppressRoomEvents and PublishUserId control what the server tells the players about each other.
The lobby-related fields Lobby, LobbyName and LobbyProperties control where and how the room appears in matchmaking; they are covered in Lobbies and Matchmaking and SQL Lobby Matchmaking.
DirectMessaging enables peer-to-peer messaging inside the room and is covered in Direct Messaging.
Joining a Room
JoinRoom(name, options) joins a room you already know by name, for example from a room listing or an invite.
JoinOrCreateRoom(name, createOptions, joinOptions) joins the room if it exists and creates it otherwise, in one atomic operation.
Both return the same Task<Result<MutableRoomView>> as CreateRoom.
| Field | Type | Default | Description |
|---|---|---|---|
Rejoin |
bool |
false |
Rejoins as a returning, inactive player instead of joining as a new one. |
CacheSliceIndex |
int |
0 |
First event cache slice to receive on join. See Event Caching. |
ExpectedUsers |
std::vector<StringType> |
empty | User ids to reserve slots for. |
Joining a friend's room works without coordinating who arrives first:
C++
Task<Result<MutableRoomView>> JoinPartyRoom(RealtimeClient& client)
{
CreateRoomOptions createOptions;
createOptions.MaxPlayers = 4;
// Joins "party-of-anna" if it already exists, creates it otherwise.
co_return co_await client.JoinOrCreateRoom(PHOTON_STR("party-of-anna"), createOptions);
}
Joining can fail for expected reasons: RoomNotFound when no room has that name, RoomFull when MaxPlayers is reached, RoomClosed when IsOpen is false and AlreadyJoined when the client is already in a room.
Handle these through the returned Result; the full error list is in Errors and Disconnects.
The Current Room
GetCurrentRoom() returns a std::optional<MutableRoomView>.
It is empty whenever the client is not in a room, so checking the optional replaces any separate state query.
MutableRoomView is a copyable value view: its getters read a snapshot of the room, while its mutation methods act on the live room through the client.
A view taken in a previous room becomes inert after leaving, its getters keep returning the old snapshot, but its mutations fail and return false.
Fetch a fresh view from GetCurrentRoom() when you need one, rather than storing views long-term.
| Getter | Returns | Description |
|---|---|---|
GetName() |
const StringType& |
The room name. |
GetPlayerCount() |
int |
Number of players currently in the room. |
GetMaxPlayers() |
uint8_t |
Player limit, 0 when unlimited. |
IsOpen() |
bool |
Whether players can join. |
IsVisible() |
bool |
Whether the room is listed in the lobby. |
GetCustomProperties() |
const RealtimeMap& |
The room's custom properties. |
GetPlayers() |
const std::vector<PlayerView>& |
All players in the room, including the local one. |
GetMasterClientId() |
int |
Player number of the current master client. |
IsMasterClient() |
bool |
Whether the local player is the master client. |
IsMasterClient(int) |
bool |
Whether the given player number is the master client. |
GetPlayerTtlMs() |
int |
Inactive-player timeout in milliseconds. |
GetEmptyRoomTtlMs() |
int |
Empty-room timeout in milliseconds. |
GetPublishUserId() |
bool |
Whether user ids are visible to the other players. |
GetDirectMode() |
DirectMode |
The room's peer-to-peer topology. |
GetExpectedUsers() |
const std::vector<StringType>& |
User ids with reserved slots. |
GetLobbyProperties() |
const std::vector<StringType>& |
Property keys visible in the lobby. |
GetSuppressRoomEvents() |
bool |
Whether join and leave events are suppressed. |
GetPlugins() |
const std::vector<StringType>& |
Server plugins active for the room. |
Modifying the Room
The mutation methods: SetOpen, SetVisible, SetMaxPlayers, SetExpectedUsers, SetLobbyProperties, SetMasterClient and the property setters send the change to the server and return bool.
They return false when the view is no longer attached to a live room, for example after leaving.
When the match starts, close and hide the room so matchmaking stops routing players into it:
C++
std::optional<MutableRoomView> room = client.GetCurrentRoom();
if (room && room->IsMasterClient())
{
room->SetOpen(false);
room->SetVisible(false);
}
Changing the room's custom properties, SetProperties or the single-key SetProperty<T> and the compare-and-swap overload, is covered in Custom Properties.
Players in the Room
GetPlayers() returns a std::vector<PlayerView> with one entry per player.
PlayerView is a plain value struct carrying Number, Name, UserId, CustomProperties, IsInactive and IsMasterClient.
Other players' UserId fields are only filled when the room was created with PublishUserId.
GetLocalPlayer() on the client returns your own PlayerView.
Player numbers are positive and unique within the room, which makes Number the standard way to address a player in events and callbacks.
Listing the roster is a straight iteration:
C++
std::optional<MutableRoomView> room = client.GetCurrentRoom();
if (room)
{
for (const PlayerView& player : room->GetPlayers())
{
std::string name(player.Name.begin(), player.Name.end());
std::printf("#%d %s%s\n", player.Number, name.c_str(), player.IsMasterClient ? " (master)" : "");
}
}
The Master Client
Every room has exactly one master client. There is no authoritative server logic in a plain Realtime room, so the master client is the designated peer for decisions that must run exactly once per room, starting the match, spawning pickups or scoring a round.
MutableRoomView::IsMasterClient() answers whether the local player currently holds the role, GetMasterClientId() returns the master client's player number and SetMasterClient(playerNumber) transfers the role to another player.
When the master client leaves, the server promotes another player automatically, so the role is never vacant.
Every client is notified through OnMasterClientChanged(newId, oldId); use it to hand over any master-only responsibilities.
Leaving and Rejoining
LeaveRoom(willComeBack) leaves the current room and returns a Task<Result<void>>.
With willComeBack = true and a room created with a non-zero PlayerTtlMs, the player is not removed but stays in the room as inactive until the TTL expires.
To resume, call JoinRoom with JoinRoomOptions::Rejoin = true before the TTL expires.
The player returns to the same slot with the same player number, and the other players see the IsInactive flag flip back instead of a new join.
Leaving with the intent to return and rejoining take one option each:
C++
Task<Result<void>> StepAway(RealtimeClient& client)
{
// The room must have been created with a non-zero PlayerTtlMs.
co_return co_await client.LeaveRoom(/*willComeBack=*/true);
}
Task<Result<MutableRoomView>> Return(RealtimeClient& client)
{
JoinRoomOptions options;
options.Rejoin = true;
co_return co_await client.JoinRoom(PHOTON_STR("match-42"), options);
}
Room and Player Callbacks
| Callback | Fires When |
|---|---|
OnRoomJoined() |
The local client has entered a room, whether by create, join or rejoin. |
OnRoomLeft() |
The local client has left the room or was removed from it. |
OnPlayerJoined(const PlayerView&) |
A remote player joins the current room. |
OnPlayerLeft(int playerNumber, bool isInactive) |
A player leaves the current room or becomes inactive. |
OnMasterClientChanged(int newId, int oldId) |
The master client role moves to another player. |
OnRoomPropertiesChanged(const RealtimeMap&) |
The room's custom properties change, delivering the changed keys. |
OnPlayerPropertiesChanged(int playerNumber, const RealtimeMap&) |
A player's custom properties change, delivering the changed keys. |
OnPlayerLeft distinguishes a real leave from a TTL-suspended one via its isInactive parameter.
true means the player may still rejoin the reserved slot; false means the player is gone and the slot is released.