This guide shows you how to implement a real-time multiplayer game using the Google Play games services in a C++ application for Android.
Before you begin
If you haven't already done so, you might find it helpful to review the real-time multiplayer game concepts.
Before you start to code your real-time multiplayer game, make sure to:
- Enable real-time multiplayer support for your game in the Google Play Console.
- Download and review the real-time multiplayer game code samples in the samples page.
- You should also familiarize yourself with the best practices described in the Quality Checklist.
Your game can start using the real-time multiplayer API once sign-in to Google Play games services is successful,
and the game has fired the GameServices::Builder::SetOnAuthActionFinished
callback, reporting a
success status.
Starting a real-time multiplayer game
Your main screen is the player's primary entry point to start a real-time multiplayer game, invite other players, or accept a pending invitation. We recommend that at minimum you implement these UI components on the main screen of your game:
- Quick Game button - Lets the player play against randomly selected opponents (via auto-matching).
- Invite Players button - Lets the player invite friends to join a game session or specify some number of random opponents for auto-matching.
- Show Invitations button - Lets the player see any pending invitations sent by another player. Selecting this option should launch the invitation inbox, as described in Handling invitations.
Quick Game option
When the player selects the Quick Game option, your game should create a room object to join players, auto-match the player to randomly selected opponents without displaying the player-picker UI, and immediately start the game.
void Engine::QuickMatch() {
gpg::RealTimeRoomConfig config =
gpg::RealTimeRoomConfig::Builder()
.SetMinimumAutomatchingPlayers(MIN_PLAYERS)
.SetMaximumAutomatchingPlayers(MAX_PLAYERS)
.Create();
service_->RealTimeMultiplayer().CreateRealTimeRoom(
config, this /* IRealTimeEventListener */,
[this](gpg::RealTimeMultiplayerManager::RealTimeRoomResponse const &
response) {
LOGI("created a room %d", response.status);
if (gpg::IsSuccess(response.status)) {
// Your code to respond to room-creation goes here. This example
// shows the built-in waiting-room UI.
room_ = response.room;
service_->RealTimeMultiplayer().ShowWaitingRoomUI(
room_,
MIN_PLAYERS,
[this](gpg::RealTimeMultiplayerManager::RealTimeRoomResponse const &
wait_response) {
EnableUI(true);
if (IsSuccess(wait_response.status)) {
// The room is set up. Proceed with gameplay.
}
});
EnableUI(true);
} else
EnableUI(true);
});
EnableUI(false);
}
If your game has multiple player roles (such as farmer, archer, and wizard) and
you want to restrict auto-matched games to one player of each role,
add an exclusive bitmask to your room configuration. When auto-matching with
this option, players will only be considered for a match when the logical AND
of their exclusive bit masks is equal to 0. The following example shows how to
use the bit mask to perform auto matching with three exclusive roles:
const uint64_t ROLE_FARMER = 0x1; // 001 in binary
const uint64_t ROLE_ARCHER = 0x2; // 010 in binary
const uint64_t ROLE_WIZARD = 0x4; // 100 in binary
void Engine::QuickMatch(uint64_t role) {
// auto-match with two random auto-match opponents of different roles
gpg::RealTimeRoomConfig config =
gpg::RealTimeRoomConfig::Builder()
.SetMinimumAutomatchingPlayers(2)
.SetMaximumAutomatchingPlayers(2)
.SetExclusiveBitMask(role)
.Create()
// create room, etc.
// …
}
Invite Players option
When the Invite Players option is selected, your game should either launch a
player-picker UI that prompts the initiating player to select friends to invite
to a real-time game session, or select a number of random players for
auto-matching. The game should create a virtual room object using the player's
criteria; once the room status changes to RealTimeRoomStatus::ACTIVE
, the game session should
begin.
To obtain the user's selection, your game can display the built-in player-picker UI provided by
Google Play games services (default) or a custom player-picker UI. To launch the default UI, call the
RealTimeMultiplayerManager::ShowPlayerSelectUI
method.
void Engine::InviteFriend() {
service_->RealTimeMultiplayer().ShowPlayerSelectUI(
MIN_PLAYERS, MAX_PLAYERS, true,
[this](gpg::RealTimeMultiplayerManager::PlayerSelectUIResponse const &
response) {
LOGI("inviting friends %d", response.status);
// Your code to handle the users's selection goes here.
});
}
An example of the default player-picker UI is shown below.
Once players are selected and your game receives the PlayerSelectUIResponse
, your game can
create a room config from the response:
// Create room config.
gpg::RealTimeRoomConfig config =
gpg::RealTimeRoomConfig::Builder()
.PopulateFromPlayerSelectUIResponse(response)
.Create();
Before you can actually create a RealTimeRoom
from the config, you must create a listener
interface that will receive notifications about the room. Do this by by implementing the
virtual methods of IRealTimeEventListener
on one of your classes.
class YourClass : public IRealTimeEventListener {
public:
// Your implementations of IRealTimeEventListener.
};
More discussion of these methods appears in the sections below.
Once the listener interface has been defined, you can call CreateRealTimeRoom
to set up the new
room with your config:
service_->RealTimeMultiplayer().CreateRealTimeRoom(
config, new MyRealTimeEventListener(...), /* your completion callback */);
Handling room-creation errors
The RealTimeRoomResponse
returned from the async callback provided to CreateRealTimeRoom
notifies the game of any errors. If a room-creation has error occurred, your game should display a
message to notify players, and return to the main screen.
void MyRealTimeRoomCreationCallback(
gpg::RealTimeMultiplayerManager::RealTimeRoomResponse const &response) {
if (gpg::IsError(response.status)) {
// We got an error, notify the user.
}
}
Room status changes
Once your room is created, the IRealTimeEventListener::OnRoomStatusChanged
method will notify
you that the real-time room's status has changed:
virtual void OnRoomStatusChanged(RealTimeRoom const &room) {
if (room.Status() == gpg::RealTimeRoomStatus::ACTIVE) {
// Room is set up, all players are connected, and we can start sending
// messages between them.
}
// Handle other statuses if appropriate.
}
Participant status changes
To be notified when all participants are connected, your game should check for the
OnRoomStatusChanged
method has returned an ACTIVE
status. For more fine-grained information
on participant status changes, you can use the following methods:
Notifications that one or more participants have successfully connected
to the room (and can receive data) are reported via the OnConnectedSetChanged
method:
virtual void OnConnectedSetChanged(RealTimeRoom const &room) {
// Iterating through participants here will show that one or more of them has
// connected or disconnected (.IsConnectedToRoom() has returned `true` or `false`).
}
The OnParticipantStatusChanged
method provides notifications of changes in individual player
statuses:
virtual void OnParticipantStatusChanged(
RealTimeRoom const &room,
MultiplayerParticipant const &participant) {
if (participant.Status() == gpg::ParticipantStatus::JOINED) {
// The participant has accepted our invite and joined the room. We still
// can't send them messages until their .IsConnectedToRoom() method returns
// true.
}
// Handle other statuses if appropriate.
}
OnP2PConnected
and OnP2PDisconnected
methods provide notifications of direct connections to
other participants. Most gamescan safely ignore these notifications. They may be useful for games
that wish to start communicating to other users before the room is fully connected.
virtual void OnP2PConnected(RealTimeRoom const &room,
MultiplayerParticipant const &participant) {
// Our game is simple, so ignore this callback.
}
virtual void OnP2PDisconnected(RealTimeRoom const &room,
MultiplayerParticipant const &participant) {
// Our game is simple, so ignore this callback.
}
Optional: Adding a waiting-room UI
We recommend that your game use a "waiting room" UI so that players can see the current status of the room as participants join and get connected. Your game can display the default waiting-room UI (shown in the figure below) or a custom UI.

To launch the default waiting-room UI, call the ShowWaitingRoomUI()
method.
Your game can launch the waiting-room UI from the asynchronous callback provided to
CreateRealTimeRoom
or AcceptInvitation
.
When the waiting-room UI is dismissed, your game receives the result as the return value of the
API. The reason for the dismissal is indicated by RealTimeRoomResponse::Status
, and can be
one of the following:
MultiplayerStatus::VALID
- All invited players were successfully connected to the room.MultiplayerStatus::ERROR_CANCELED
- The player backed out of the UI.MultiplayerStatus::ERROR_LEFT_ROOM
- The player selected the Leave Room option.
You can implement a different response depending on whether the user
explicitly canceled the game (ERROR_LEFT_ROOM
or quit the waiting-room UI (ERROR_CANCELED
).
If you use the waiting-room UI, you do not need to implement additional
logic to decide when the game should be started or canceled. A game can start right away on
receiving a MultiplayerStatus::VALID
result, since the required
number of participants have been connected. Likewise, when you get an error
result from the waiting-room UI, you can simply leave the room.
Querying a participant's status
The MultiplayerParticipant.Status()
method returns the current status of the participant.
ParticipantStatus::INVITED
: The participant has been invited, but has not responded.ParticipantStatus::DECLINED
: The participant has declined the invitation.ParticipantStatus::JOINED
: The participant has joined the room.ParticipantStatus::LEFT
: The participant has left the room.
Your game can also detect if a participant is connected by
calling MultiplayerParticipant.IsConnectedToRoom()
.
Make sure to construct your game logic carefully to take each participant's status and connectedness into account. For example, to determine if all racers have crossed the finish line, your game should only consider the participants who are connected; some may have left the room or never have accepted the invitation.
Detecting when a player is disconnected
Your player might be disconnected from the room due to network connectivity or
server issues. To be notified when a player is disconnected from the room,
implement the OnConnectedSetChanged
method.
virtual void OnConnectedSetChanged(RealTimeRoom const &room) {
// Check the room's participants to see who connected/disconnected.
}
Handling invitations
Once the player has signed in, your game may be notified of invitations to join a room created by another player. The game should handle such invitations.
During gameplay
To be notified of incoming invitations, your game can register
a callback via the SetOnMultiplayerInvitationEvent
method when configuring
GameServices::Builder
. Incoming invitations do not generate a status bar while a game is open.
Instead, the callback is notified, and your game can then display an
in-game popup dialog or notification to inform the user. If the user accepts,
your game should process the invitation and launch the game screen.
builder.SetOnMultiplayerInvitationEvent([this](
gpg::MultiplayerEvent event,
std::string invitation_id,
gpg::MultiplayerInvitation invitation) {
LOGI("MultiplayerInvitationEvent callback");
if (event ==
gpg::TurnBasedMultiplayerEvent::UPDATED_FROM_APP_LAUNCH) {
gpg::RealTimeMultiplayerManager::RealTimeRoomResponse result =
service_->RealTimeMultiplayer().AcceptInvitationBlocking(
invitation, this /* IRealTimeEventListener */);
// Show the waiting room or take other action on room join.
} else {
// Show default inbox
ShowRoomInbox();
}
})
From the Invitation Inbox
The Invitation Inbox is an optional UI component that your game can display
using RealTimeMultiplayerManager::ShowRoomInboxUI
. The Inbox displays all available
invitations that a player received. If the player selects a pending invitation from the Inbox,
your game should accept the invitation and launch the game screen.
The app accepts the invitation as follows:
if (gpg::IsSuccess(response.status)) {
gpg::RealTimeMultiplayerManager::RealTimeRoomResponse result =
service_->RealTimeMultiplayer().AcceptInvitationBlocking(
response.invitation, this);
if (gpg::IsSuccess(result.status)) {
// Show the waiting room or take other action on room join.
Exchanging game data between clients
Review Sending game data to familiarize yourself with the concepts behind using the real-time multiplayer API for data messaging.
Sending messages
To send a message using an unreliable protocol, use
RealTimeMultiplayerManager::SendUnreliableMessage
.
To send a reliable message, use RealTimeMultiplayerManager::SendReliableMessage
.
The following example shows how to broadcast a score, using either a reliable or an unreliable message.
void Engine::BroadcastScore(bool bFinal) {
std::vector<uint8_t> v;
if (!bFinal) {
v.push_back('U');
v.push_back(static_cast<uint8_t>(score_counter_));
// Send unreliable message
service_->RealTimeMultiplayer().SendUnreliableMessageToOthers(room_, v);
} else {
v.push_back('F');
v.push_back(static_cast<uint8_t>(score_counter_));
const std::vector<gpg::MultiplayerParticipant> participants =
room_.Participants();
for (gpg::MultiplayerParticipant participant : participants) {
// Send reliable message
service_->RealTimeMultiplayer().SendReliableMessage(
room_, participant, v, [](gpg::ResponseStatus const &) {});
}
}
}
Leaving the room
Your game should call RealTimeMultiplayerManager::LeaveRoom
to leave the active room when one of
these scenarios occurs:
- Gameplay is over.
- When activity stops.
- The user cancels the game in the waiting room.
- The response code returned from the ShowWaitUI
UIStatus::ERROR_LEFT_ROOM
.
To leave the room, call LeaveRoom
:
service_->RealTimeMultiplayer().LeaveRoom(room_, [](ResponseStatus const &response) {
if (IsSuccess(response)) {
// We left successfully. We can now join another room.
}
});