SSISO Community

시소당

Turn-based Multiplayer Support in Android Games

Turn-based Multiplayer Support in Android Games

This guide shows you how to implement a real-time multiplayer game using the Google Play games services in an Android application. The APIs can be found in the com.google.android.gms.games.multiplayer, com.google.android.gms.games.multiplayer.turnbased, and com.google.android.gms.games packages.

Before you begin

If you haven't already done so, you might find it helpful to review the turn-based multiplayer game concepts.

Before you start to code your turn-based multiplayer game:

Getting the turn-based multiplayer client

To start using the real-time multiplayer API, your game must first obtain a TurnBasedMultiplayerClient object. You can do this by calling the Games.getTurnBasedMultiplayerClient() method and passing in the activity and the GoogleSignInAccount for the current player. To learn how to retrieve the player account information, see Sign-in in Android Games.

Starting a match

To begin a turn-based match, your game can prompt players to select how they want to be matched to other participants, or build the match using some default criteria. Google Play games services uses the match configuration data from your game to return a TurnBasedMatch object to your game; this object is updated and shared asynchronously with all participants over the lifecycle of the turn-based match.

To start a match, follow these steps:

  1. In your app, gather specifications for the type of turn-based match that the player wants to play and who can participate in the match.
    • Your game can display the built-in player picker user interface (UI) provided by the SDK or use a own custom UI to gather this data. To learn how to use the player picker UI, see Selecting players with the default user interface.
    • Alternatively, you can implement a Quick Start button to let users bypass the player picker UI, and instead use some default criteria to build the match.
  2. Use a TurnBasedMatchConfig.Builder to set the match configuration data in a TurnBasedConfigObject.
  3. Call TurnBasedMultiplayerClient.createMatch() and pass in the TurnBasedConfigObject you created. If auto-matching is specified, Google Play games services will attempt to match players to an existing game; for more details, see Implementing auto-matching.

Selecting players with the default user interface

Google Play games services provides a default player picker UI that lets players select their friends to invite to a match or select to be auto-matched with random players.

To start a match from the default player picker UI, call the TurnBasedMultiplayerClient.getSelectOpponentsIntent() method and use the intent it returns to start an activity.

For example:

private static final int RC_SELECT_PLAYERS = 9010;


public void onStartMatchClicked(View view) {
 
boolean allowAutoMatch = true;
 
Games.getTurnBasedMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
     
.getSelectOpponentsIntent(1, 7, allowAutoMatch)
     
.addOnSuccessListener(new OnSuccessListener<Intent>() {
       
@Override
       
public void onSuccess(Intent intent) {
          startActivityForResult
(intent, RC_SELECT_PLAYERS);
       
}
     
});
}

Next, override the onActivityResult() callback to construct a TurnBasedMatchConfig object using the criteria provided by the user. The TurnBasedMatchConfig object defines the characteristics of the match that the user wants to play. Google Play games services uses this configuration data to determine whether to build a new match or to auto-match the user to an existing game.

For example:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 
super.onActivityResult(requestCode, resultCode, data);
 
if (requestCode == RC_SELECT_PLAYERS) {
   
if (resultCode != Activity.RESULT_OK) {
     
// Canceled or other unrecoverable error.
     
return;
   
}
   
ArrayList<String> invitees = data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);

   
// Get automatch criteria
   
Bundle autoMatchCriteria = null;
   
int minAutoPlayers = data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
   
int maxAutoPlayers = data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);

   
TurnBasedMatchConfig.Builder builder = TurnBasedMatchConfig.builder()
       
.addInvitedPlayers(invitees);
   
if (minAutoPlayers > 0) {
      builder
.setAutoMatchCriteria(
         
RoomConfig.createAutoMatchCriteria(minAutoPlayers, maxAutoPlayers, 0));
   
}
   
Games.getTurnBasedMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
       
.createMatch(builder.build()).addOnCompleteListener(new OnCompleteListener<TurnBasedMatch>() {
     
@Override
     
public void onComplete(@NonNull Task<TurnBasedMatch> task) {
       
if (task.isSuccessful()) {
         
TurnBasedMatch match = task.getResult();
         
if (match.getData() == null) {
           
// First turn, initialize the game data.
           
// (You need to implement this).
            initializeGameData
(match);
         
}

         
// Show the turn UI.
         
// (Game specific logic)
          showTurnUI
(match);
       
} else {
         
// There was an error. Show the error.
         
int status = CommonStatusCodes.DEVELOPER_ERROR;
         
Exception exception = task.getException();
         
if (exception instanceof ApiException) {
           
ApiException apiException = (ApiException) exception;
            status
= apiException.getStatusCode();
         
}
          handleError
(status, exception);
       
}
     
}
   
});
 
}
}

If you want to create a match without using the default player picker UI, your game must provide the invitee player IDs and auto-match criteria in the TurnBasedMatchConfig object that you pass to TurnBasedMultiplayerClient.createMatch().

Optionally, you might want to ensure that only players who are interested in a specific type of game variant are auto-matched together. If there are different versions of your app, your game can also use variants to ensure that only players who are on compatible versions are auto-matched.

To specify a variant when creating a turn-based match, use the setVariant() method. Your game can also use the exclusiveBitMask parameter in createAutoMatchCriteria() to pair auto-matched players who are interested in playing specific exclusive roles in a game.

Taking the first turn

If the TurnBasedMultiplayerClient.createMatch() call is successful, Google Play games services returns a Task object which asynchronously loads a TurnBasedMatch instance.

Before proceeding, remember to initialize the game data as needed by your game. For example, you might need to initialize the starting positions for players in a strategy game or initialize the first hand for players in a card game.

To implement the first turn, follow these steps:

  1. Call the getData() method on the TurnBasedMatch instance.
    • If the call returns a null value, this indicates that the game data is uninitialized because no player has taken a turn yet (that is, the current player is the first player in this match). Your game can initialize the game data and store it in a byte array. The size of the game data must be less than the size returned by TurnBasedMultiplayerClient.getMaxMatchDataSize(). The data size returned is guaranteed to be at least 128 KB.
    • If the call returns a non-null value, this indicates that the game has already started and the game data is already initialized, so make sure your game does not reinitialize the data.
  2. Optionally, call TurnBasedMultiplayerClient.takeTurn()now. If your initial state is based on a random value (for example, a card game where the player starts with a random hand) and your game does not call TurnBasedMultiplayerClient.takeTurn()to persist the data to Google's servers, the player could keep restarting the first turn to try to get a better starting position.
  3. Let the player perform game actions, and if appropriate, update the byte array containing the game data.
  4. Save the game data to Google’s servers by calling TurnBasedMultiplayerClient.takeTurn(). Your game can specify the next player's participant ID, or specify null to let Google Play games services find a player for auto-matching. Your game can let match participants take turns even when not all invited players have joined or when there are auto-match slots still available. To get players into gameplay faster, your game should let players take their turn as soon as they join. When you call TurnBasedMultiplayerClient.takeTurn(), Google Play games services sends a notification to the pending participant and updates the turn data on all participants' devices asynchronously.
  5. When the next player takes a turn, make sure to load the initialized data by calling getData() before you let the player perform game actions.

Implementing auto-matching

When your game calls TurnBasedMultiplayerClient.createMatch() and requests auto-matching, Google Play games services first attempts to match the player to an existing game.

If a match is found that meets the player's auto-match criteria, Google Play games services automatically joins the player and any invited players as auto-matched players into the existing match.

If no match is found, your game should signal that it is available for auto-matching, by performing these steps:

  1. Prompt the initiating player to take the first turn.
  2. Optionally, after the initiating player completes their first turn, your game can also let other invited players who joined the match take their first turns.
  3. To trigger auto-matching, call TurnBasedMultiplayerClient.takeTurn()and pass in null in the pendingParticipantId parameter.

The following code shows how your game can get the next participant in a round-robin fashion, where all known players take a turn before all automatch players:

public String getNextParticipantId(String myPlayerId, TurnBasedMatch match) {
 
String myParticipantId = match.getParticipantId(myPlayerId);

 
ArrayList<String> participantIds = match.getParticipantIds();

 
int desiredIndex = -1;

 
for (int i = 0; i < participantIds.size(); i++) {
   
if (participantIds.get(i).equals(myParticipantId)) {
      desiredIndex
= i + 1;
   
}
 
}

 
if (desiredIndex < participantIds.size()) {
   
return participantIds.get(desiredIndex);
 
}

 
if (match.getAvailableAutoMatchSlots() <= 0) {
   
// You've run out of automatch slots, so we start over.
   
return participantIds.get(0);
 
} else {
   
// You have not yet fully automatched, so null will find a new
   
// person to play against.
   
return null;
 
}
}

Handling invitations

Once a player has signed in to your game, the player may receive invitations to join a turn-based match created by another player. To save coding time and provide users with a consistent UI for responding to match invitations across applications, you can use the default match inbox UI provided by the SDK.

To launch the default match inbox UI, call the TurnBasedMultiplayerClient.getInboxIntent() method, then use the intent it returns to start an activity.

Taking a turn

After the first player takes a turn, your game can let the same participant or another participant take the next turn. Generally, turn-taking involves loading the most recent match data from Google Play games services, letting the participant interact with your game, and calling TurnBasedMultiplayerClient.takeTurn()to update the match data and pass the turn to another participant.

To implement turn-taking, follow these steps:

  1. Optionally, if you want be notified whenever any participant in the match takes a turn, attach a TurnBasedMatchUpdateCallback to your activity. Whenever the match is updated following a player's turn, your activity is notified via the TurnBasedMatchUpdateCallback.onTurnBasedMatchedReceived() method.
  2. In order for a participant to take a turn, the match must be in active state and it must be that participant's turn. Your game can use the TurnBasedMatch object to verify this.
  3. Load the most recent match data from Google Play games services by calling getData() and render your game accordingly.
  4. Let the user perform game actions and, if appropriate, persist the updated game data to a byte array.
  5. Determine the pending participant who should take the next turn. This is usually dependent on the order of play specified by your game design. The pending participant can be another player in the match or the current participant whose turn it is. Your game can also pass the turn to the next player joining by auto-match, by setting the pending participant to null.
  6. Call TurnBasedMultiplayerClient.takeTurn()to update the Google Play games services with the latest game data and pass the turn to the pending participant. If the update is successful, Google Play games services sends a notification to the pending participant to inform them that it's their turn.

For example:

private void playTurn(TurnBasedMatch match) {
 
String nextParticipantId = getNextParticipantId(mMyPlayerId, match);

 
// This calls a game specific method to get the bytes that represent the game state
 
// including the current player's turn.
 
byte[] gameData = serializeGameData();

 
Games.getTurnBasedMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
     
.takeTurn(match.getMatchId(), gameData, nextParticipantId)
     
.addOnCompleteListener(new OnCompleteListener<TurnBasedMatch>() {
       
@Override
       
public void onComplete(@NonNull Task<TurnBasedMatch> task) {
         
if (task.isSuccessful()) {
           
TurnBasedMatch match = task.getResult();
         
} else {
           
// Handle exceptions.
         
}
       
}
     
});
}

Saving game state

You can use the turn-based multiplayer API to manage your game state if your game state data fits within the allowed size. The data size limit can be retrieved by calling TurnBasedMultiplayerClient.getMaxMatchDataSize(). The data size returned is guaranteed to be at least 128 KB.

To save game state with the turn-based multiplayer API:

  1. Call TurnBasedMultiplayerClient.takeTurn()and pass in your game state data as the matchData parameter.
  2. If the call is successful, Google Play games services notifies other participants in the match about the update and makes the match data available on all participant devices.
  3. Your game can then call getData() to retrieve the updated game state.

Your game should try to save game data for a partially-finished turn whenever a player's turn is interrupted and the player has to temporarily leave the game (for example, because of an incoming phone call). To do this, override your activity's onStop() method to call TurnBasedMultiplayerClient.takeTurn(). Make sure to specify the current player as the pending participant by using the same participant ID as in the last call to TurnBasedMultiplayerClient.takeTurn(). If successful, the call stores the game data in Google’s servers but does not generate a new turn notification.

Completing a match

When the match has been played to completion (for example, a user has won the game), your game should call TurnBasedMultiplayerClient.finishMatch() to upload the user’s game data and signal to the other participants that the match is over. When your game invokes this method for the first time during a match, it must be during the user's turn. The match then appears under the Completed Matches category in the user's match list UI.

Once a player calls TurnBasedMultiplayerClient.finishMatch() for the first time in the match, your game cannot call TurnBasedMultiplayerClient.takeTurn()again in this match. Google Play games services sends a notification to all other match participants to inform them that the match is over. These participants see this match under Your Turn category in their respective match list UIs. At this point, your game can call TurnBasedMultiplayerClient.finishMatch() for these participants to save their final game data. Invoking this method also moves the match to the Completed Matches category in the participant’s match list UI.

Leaving a match

Participants can choose to leave at any time during the match, while allowing the match to continue. To signal that a participant is leaving, your game should call either TurnBasedMultiplayerClient.leaveMatch() or TurnBasedMultiplayerClient.leaveMatchDuringTurn(). When a participant leaves a match, the match can still continue with other participants as long as these conditions are met:

  • There are two or more other remaining participants, or
  • There is one remaining participant and at least one empty auto-match slot available.

Otherwise, the match is canceled.

In general, when a participant leaves a match, another player cannot join and take that participant’s place. One exception is when a player who joined by auto-match and who has not taken a turn calls TurnBasedMultiplayerClient.leaveMatchDuringTurn(). In this case, the match reverts to the MATCH_STATUS_AUTO_MATCHING state and another player can take over the place of the participant who left.

Canceling a match

Your game can end a match for all participants before the match is played to normal completion, by calling TurnBasedMultiplayerClient.cancelMatch(). After calling this method, your game cannot call TurnBasedMultiplayerClient.takeTurn()again in this match; the match now appears under the Completed Matches category in the match list UI.

Dismissing a match

To dismiss a match, call TurnBasedMultiplayerClient.dismissMatch().

Your game can let users dismiss a turn or invitation so that they do not have to see the match again. This hides the match from the dismisser's match list UI and causes the match to eventually expire. Other match participants can continue to play until the dismissed match expires after two weeks, or until the match is played to completion or canceled (whichever happens first). To other participants, the dismisser still appears as a participant in the match. Another player cannot take the dismisser's place.

Tracking match expiration

A match can expire if a player does not respond to a match or turn notification for two weeks (for example, after a player whose turn it is selects Dismiss in the default match list UI).

You can use the following approach on the game client to identify which player caused a game to expire:

  1. Load a list of the currently signed-in player’s list of games by calling TurnBasedMultiplayerClient.loadMatchesByStatus().
  2. Next, call getCompletedMatches() to get a TurnBasedMatchBuffer that contains a list of matches in the MATCH_TURN_STATUS_COMPLETE state.
  3. Then, filter that list for matches in the MATCH_STATUS_EXPIRED state.
  4. If a match participant has STATUS_UNRESPONSIVE state, it indicates that this player has let the game expire.

Implementing a rematch

When a match is over, a participant may want to play a rematch with the same set of invited and auto-match opponents. Your game can initiate a rematch by invoking TurnBasedMultiplayerClient.rematch(). Your game can only invoke this method when the match state is MATCH_STATUS_COMPLETE and no other participants have requested a rematch. If the call is successful, Google Play games services sends invitation notifications to all rematched opponents.

If your game uses the default match inbox UI, players can also initiate rematches from that UI. When a player initiates a rematch from the match inbox UI, Google Play games services returns a new match object as an Intent extra in the onActivityResult() callback of the match inbox activity. If your game needs to initialize game data for the new match, make sure to use the match object returned in the onActivityResult() callback.


2576 view

4.0 stars