Adding Real-time Multiplayer Support to Your Game

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:

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.
  }
});

Enviar comentarios sobre…

Play Games Services for C++
Play Games Services for C++