Androidゲームでのリアルタイム型マルチプレイヤーのサポート



  • この記事は、Google Playゲームサービスに関する記事を和訳したものです。
  • 原文: Real-time Multiplayer Support in Android Games
  • 元記事のライセンスは CC-BYで、この和訳記事のライセンスは CC-BYです。
  • 自己責任でご利用ください。
  • 和訳した時期は 2019年7月ころです。

このガイドでは、Androidアプリケーションにて Google Playゲームサービスを使用するリアルタイム型マルチプレイヤーゲームを実装する方法する方法を示します。 APIは、com.google.android.gms.games.multiplayercom.google.android.gms.games.multiplayer.realtime、および com.google.android.gms.gamesパッケージにあります。

始める前に

もしまだそうしていなければ、リアルタイム型マルチプレイヤーゲームのゲームコンセプトをレビューすることは役立つかもしれません。

あなたのリアルタイム型マルチプレイヤーゲームをコーディングし始める前に:

リアルタイム型マルチプレイヤークライアントを取得する

リアルタイム型マルチプレイヤー APIを使用し始めるには、あなたのゲームは、まず RealTimeMultiplayerClientオブジェクトを取得しなければなりません。 Games.getRealTimeMultiplayerClient()メソッドを呼び出し、アクティビティと、現在のプレイヤーの GoogleSignInAccountを渡すことによって、これを行うことができます。 プレイヤーのアカウント情報を取得する方法については、Androidゲームでのサインインを参照してください。

リアルタイム型マルチプレイヤーゲームを開始する

あなたのメイン画面は、リアルタイム型マルチプレイヤーゲームを開始し、他のプレイヤーを招き、あるいは保留中の招待を受け入れるためのプレイヤーの主要なエントリポイントです。 最低でも、あなたのゲームのメイン画面上にこれらの UIコンポーネントを実装することをお勧めします:

  • クイックゲームボタン - プレイヤーに、ランダムに選択された相手とプレイさせます(オートマッチを経由して)。
  • プレイヤー招待ボタン - プレイヤーに、ゲームセッションに参加するための友人を招待させ、あるいは、オートマッチのためにランダムな相手の幾つかの数を指定させます。
  • 招待表示ボタン - プレイヤーは他のプレイヤーから送信されたいかなる保留中の招待を見ることができます。 招待を処理するで説明したように、このオプションを選択すると、招待の受信トレイを起動する必要があります。

クイックゲームオプション

プレイヤーがクイックゲームオプションを選択したとき、あなたのゲームはプレイヤーを参加させるための仮想的なルームオブジェクトを作成し、プレイヤーをプレイヤーピッカー UIを表示することなくランダムに選択されたプレイヤーとオートマッチし、すぐにゲームを開始する必要があります。

もしあなたのゲームが複数のプレイヤーの役割(農家、アーチャー、ウィザードといった)を持っており、それぞれの役割の 1人のプレイヤーにオートマッチのゲームを制限したければ、あなたのルーム設定に排他的なビットマスクを追加します。 このオプションを用いてオートマッチするとき、プレイヤーは彼らの排他的なビットマスクの論理 ANDがゼロのときのみ、マッチのために考慮されるでしょう。

次の例は、クイックゲームオプションを実装し、3つの排他的なプレイヤーキャラクタの役割を伴うオートマッチを実行するためにビットマスクを使用する方法を示しています。

private static final long ROLE_ANY = 0x0; // can play in any match.
private static final long ROLE_FARMER = 0x1; // 001 in binary
private static final long ROLE_ARCHER = 0x2; // 010 in binary
private static final long ROLE_WIZARD = 0x4; // 100 in binary

private void startQuickGame(long role) {
  // auto-match criteria to invite one random automatch opponent.
  // You can also specify more opponents (up to 3).
  Bundle autoMatchCriteria = RoomConfig.createAutoMatchCriteria(1, 1, role);

  // build the room config:
  RoomConfig roomConfig =
      RoomConfig.builder(mRoomUpdateCallback)
          .setOnMessageReceivedListener(mMessageReceivedHandler)
          .setRoomStatusUpdateCallback(mRoomStatusCallbackHandler)
          .setAutoMatchCriteria(autoMatchCriteria)
          .build();

  // prevent screen from sleeping during handshake
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

  // Save the roomConfig so we can use it if we call leave().
  mJoinedRoomConfig = roomConfig;

  // create room:
  Games.getRealTimeMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
      .create(roomConfig);
}

招待プレイヤーオプション

プレイヤー招待オプションが選択されたとき、あなたのゲームは、開始したプレイヤーにリアルタイムのセームセッションに招待するための友人を選択する、あるいはオートマッチに用いるランダムプレイヤーの数を選択することを促す、プレイヤーピッカー UIを起動する必要があります。 あなたのゲームはプレイヤーの基準を使用して仮想的なルームオブジェクトを作成し、それから、ひとたびプレイヤーがルームに接続されたならば、ゲームセッションを開始する必要があります。

ユーザの選択を取得するために、あなたのゲームは Google Playゲームサービスによって提供されるビルトインのプレイヤーピッカー UI、あるいは、カスタムプレイヤーピッカー UIを表示することができます。 デフォルトのユーザーピッカー UIを起動するには、getSelectOpponentsIntent()メソッドを呼び出し、それが返した IntentActivityを開始するために使用します。

private static final int RC_SELECT_PLAYERS = 9006;

private void invitePlayers() {
  // launch the player selection screen
  // minimum: 1 other player; maximum: 3 other players
  Games.getRealTimeMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
      .getSelectOpponentsIntent(1, 3, true)
      .addOnSuccessListener(new OnSuccessListener<Intent>() {
        @Override
        public void onSuccess(Intent intent) {
          startActivityForResult(intent, RC_SELECT_PLAYERS);
        }
      });
}

デフォルトのプレイヤーピッカー UIの一例を以下に示します。

あなたのゲームは onActivityResult()コールバックで開始するプレイヤーの基準を受信します。 それから、ルームを作成し、ルームの状態の変更や受信メッセージの通知を受信するためのリスナーをセットアップする必要があります。

@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 some other error.
      return;
    }

    // Get the invitee list.
    final ArrayList<String> invitees = data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);

    // Get Automatch criteria.
    int minAutoPlayers = data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
    int maxAutoPlayers = data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);

    // Create the room configuration.
    RoomConfig.Builder roomBuilder = RoomConfig.builder(mRoomUpdateCallback)
        .setOnMessageReceivedListener(mMessageReceivedHandler)
        .setRoomStatusUpdateCallback(mRoomStatusCallbackHandler)
        .addPlayersToInvite(invitees);
    if (minAutoPlayers > 0) {
      roomBuilder.setAutoMatchCriteria(
          RoomConfig.createAutoMatchCriteria(minAutoPlayers, maxAutoPlayers, 0));
    }

    // Save the roomConfig so we can use it if we call leave().
    mJoinedRoomConfig = roomBuilder.build();
    Games.getRealTimeMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
        .create(mJoinedRoomConfig);
  }
}

ルーム作成エラーを処理する

ルームの作成中のエラーを通知されるために、あなたのゲームは RoomUpdateCallbackクラスを使用することができます。 もしルーム作成エラーが発生したならば、あなたのゲームはプレイヤーに通知するためのメッセージを表示し、メイン画面に戻る必要があります。

private RoomUpdateCallback mRoomUpdateCallback = new RoomUpdateCallback() {
  @Override
  public void onRoomCreated(int code, @Nullable Room room) {
    // Update UI and internal state based on room updates.
    if (code == GamesCallbackStatusCodes.OK && room != null) {
      Log.d(TAG, "Room " + room.getRoomId() + " created.");
    } else {
      Log.w(TAG, "Error creating room: " + code);
      // let screen go to sleep
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    }
  }

  @Override
  public void onJoinedRoom(int code, @Nullable Room room) {
    // Update UI and internal state based on room updates.
    if (code == GamesCallbackStatusCodes.OK && room != null) {
      Log.d(TAG, "Room " + room.getRoomId() + " joined.");
    } else {
      Log.w(TAG, "Error joining room: " + code);
      // let screen go to sleep
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    }
  }

  @Override
  public void onLeftRoom(int code, @NonNull String roomId) {
    Log.d(TAG, "Left room" + roomId);
  }

  @Override
  public void onRoomConnected(int code, @Nullable Room room) {
    if (code == GamesCallbackStatusCodes.OK && room != null) {
      Log.d(TAG, "Room " + room.getRoomId() + " connected.");
    } else {
      Log.w(TAG, "Error connecting to room: " + code);
      // let screen go to sleep
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    }
  }
};

プレイヤーを接続する

すべてのプレイヤーが接続されたときに通知されるために、あなたのゲームは RoomUpdateCallback.onRoomConnected()メソッドを使用することができます。

さらには、あなたのゲームは参加者の接続状態をモニタリングするために RoomStatusUpdateCallbackクラスを使用することができます。 参加者の接続状態に基づいて、あなたのゲームはゲームセッションを開始するかキャンセルするかを決定することができます。

例えば:

// are we already playing?
boolean mPlaying = false;

// at least 2 players required for our game
final static int MIN_PLAYERS = 2;

// returns whether there are enough players to start the game
boolean shouldStartGame(Room room) {
  int connectedPlayers = 0;
  for (Participant p : room.getParticipants()) {
    if (p.isConnectedToRoom()) {
      ++connectedPlayers;
    }
  }
  return connectedPlayers >= MIN_PLAYERS;
}

// Returns whether the room is in a state where the game should be canceled.
boolean shouldCancelGame(Room room) {
  // TODO: Your game-specific cancellation logic here. For example, you might decide to
  // cancel the game if enough people have declined the invitation or left the room.
  // You can check a participant's status with Participant.getStatus().
  // (Also, your UI should have a Cancel button that cancels the game too)
  return false;
}

private Activity thisActivity = this;
private Room mRoom;
private RoomStatusUpdateCallback mRoomStatusCallbackHandler = new RoomStatusUpdateCallback() {
  @Override
  public void onRoomConnecting(@Nullable Room room) {
    // Update the UI status since we are in the process of connecting to a specific room.
  }

  @Override
  public void onRoomAutoMatching(@Nullable Room room) {
    // Update the UI status since we are in the process of matching other players.
  }

  @Override
  public void onPeerInvitedToRoom(@Nullable Room room, @NonNull List<String> list) {
    // Update the UI status since we are in the process of matching other players.
  }

  @Override
  public void onPeerDeclined(@Nullable Room room, @NonNull List<String> list) {
    // Peer declined invitation, see if game should be canceled
    if (!mPlaying && shouldCancelGame(room)) {
      Games.getRealTimeMultiplayerClient(thisActivity,
          GoogleSignIn.getLastSignedInAccount(thisActivity))
          .leave(mJoinedRoomConfig, room.getRoomId());
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
  }

  @Override
  public void onPeerJoined(@Nullable Room room, @NonNull List<String> list) {
    // Update UI status indicating new players have joined!
  }

  @Override
  public void onPeerLeft(@Nullable Room room, @NonNull List<String> list) {
    // Peer left, see if game should be canceled.
    if (!mPlaying && shouldCancelGame(room)) {
      Games.getRealTimeMultiplayerClient(thisActivity,
          GoogleSignIn.getLastSignedInAccount(thisActivity))
          .leave(mJoinedRoomConfig, room.getRoomId());
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
  }

  @Override
  public void onConnectedToRoom(@Nullable Room room) {
    // Connected to room, record the room Id.
    mRoom = room;
    Games.getPlayersClient(thisActivity, GoogleSignIn.getLastSignedInAccount(thisActivity))
        .getCurrentPlayerId().addOnSuccessListener(new OnSuccessListener<String>() {
      @Override
      public void onSuccess(String playerId) {
        mMyParticipantId = mRoom.getParticipantId(playerId);
      }
    });
  }

  @Override
  public void onDisconnectedFromRoom(@Nullable Room room) {
    // This usually happens due to a network error, leave the game.
    Games.getRealTimeMultiplayerClient(thisActivity, GoogleSignIn.getLastSignedInAccount(thisActivity))
        .leave(mJoinedRoomConfig, room.getRoomId());
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    // show error message and return to main screen
    mRoom = null;
    mJoinedRoomConfig = null;
  }

  @Override
  public void onPeersConnected(@Nullable Room room, @NonNull List<String> list) {
    if (mPlaying) {
      // add new player to an ongoing game
    } else if (shouldStartGame(room)) {
      // start game!
    }
  }

  @Override
  public void onPeersDisconnected(@Nullable Room room, @NonNull List<String> list) {
    if (mPlaying) {
      // do game-specific handling of this -- remove player's avatar
      // from the screen, etc. If not enough players are left for
      // the game to go on, end the game and leave the room.
    } else if (shouldCancelGame(room)) {
      // cancel the game
      Games.getRealTimeMultiplayerClient(thisActivity,
          GoogleSignIn.getLastSignedInAccount(thisActivity))
          .leave(mJoinedRoomConfig, room.getRoomId());
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
  }

  @Override
  public void onP2PConnected(@NonNull String participantId) {
    // Update status due to new peer to peer connection.
  }

  @Override
  public void onP2PDisconnected(@NonNull String participantId) {
    // Update status due to  peer to peer connection being disconnected.
  }
};

オプション: 待合室 UIを追加する

私たちは、プレイヤーが、参加し、接続を獲得した参加者としてルームの現在のステータスを見ることができるために、あなたのゲームが "waiting room" UIを使用することをお勧めします。 あなたのゲームはデフォルトの待合室 UI(下の図に示されている)、あるいはカスタム UIを表示することができます。

デフォルトの待合室 UIを起動するには、RealTimeMultiplayerClient.getWaitingRoomIntent()メソッドを呼び出し、それが返したインテントをアクティビティを開始するために使用します。

private static final int RC_WAITING_ROOM = 9007;

private void showWaitingRoom(Room room, int maxPlayersToStartGame) {
  Games.getRealTimeMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
      .getWaitingRoomIntent(room, maxPlayersToStartGame)
      .addOnSuccessListener(new OnSuccessListener<Intent>() {
        @Override
        public void onSuccess(Intent intent) {
          startActivityForResult(intent, RC_WAITING_ROOM);
        }
      });
}

あなたのゲームは RoomUpdateCallback.onRoomConnected()RoomUpdateCallback.onJoinedRoom()メソッドから待合室 UIを起動することができます。

待合室 UIが却下されたとき、あなたのゲームは、あなたのアクティビティの onActivityResult()コールバックを通して結果を受信します。 却下された理由は responseCodeコールバックパラメータで示され、次のいずれかになります:

次に、あなたのゲームはその onActivityResult()コールバックにて待合室の結果を処理する必要があります:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RC_WAITING_ROOM) {

    // Look for finishing the waiting room from code, for example if a
    // "start game" message is received.  In this case, ignore the result.
    if (mWaitingRoomFinishedFromCode) {
      return;
    }

    if (resultCode == Activity.RESULT_OK) {
      // Start the game!
    } else if (resultCode == Activity.RESULT_CANCELED) {
      // Waiting room was dismissed with the back button. The meaning of this
      // action is up to the game. You may choose to leave the room and cancel the
      // match, or do something else like minimize the waiting room and
      // continue to connect in the background.

      // in this example, we take the simple approach and just leave the room:
      Games.getRealTimeMultiplayerClient(thisActivity,
          GoogleSignIn.getLastSignedInAccount(this))
          .leave(mJoinedRoomConfig, mRoom.getRoomId());
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    } else if (resultCode == GamesActivityResultCodes.RESULT_LEFT_ROOM) {
      // player wants to leave the room.
      Games.getRealTimeMultiplayerClient(thisActivity,
          GoogleSignIn.getLastSignedInAccount(this))
          .leave(mJoinedRoomConfig, mRoom.getRoomId());
      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
  }
}

ユーザが明示的にゲームをキャンセルした(GamesActivityResultCodes.RESULT_LEFT_ROOM)か、ユーザが待合室 UIを終了した(GamesActivityResultCodes.Activity.RESULT_CANCELED)かによって異なるレスポンスを実装することができます。 例えば、もしプレイヤーが Backボタンを用いて UIを放棄したならば、アプリを最小化し、バックグラウンドでハンドシェイクのプロセスを続けます。 しかしながら、もしプレイヤーが待合室 UIから Leave Roomボタンを選択したならば、ハンドシェイクをキャンセルすることができます。

もし待合室 UIを使用するならば、ゲームが開始された、あるいは、キャンセルされたときに決定するための追加的なロジックを実装する必要はありません。 Activity.RESULT_OK結果を取得したときに、必要とされる数の参加者が接続されているので、すぐに開始することができます。 同様に、待合室 UIからエラーの結果を取得したとき、単純にルームから離れることができます。

すべてのプレイヤーが接続される前にゲームを開始する

待合室を作成するとき、あなたのゲームはゲームセッションを開始するために必要とされるプレイヤーの最小数を指定することができます。 もし、接続された参加者の数がゲームを開始するために指定した最小以上であれば、システムは、待合室 UIの Start Playingオプションを有効にします。 ユーザがこのオプションをクリックしたとき、システムは待合室 UIを却下し、Activity.RESULT_OK結果コードを配信します。

プレイヤーが Start Playingオプションをクリックしたとき、ゲーム内の他のプレイヤーのための待合室 UIは自動的には却下されません。 他のプレイヤーの待合室を却下するために、あなたのゲームはゲームが開始していることを示す信頼できるリアルタイムメッセージを他のプレイヤーに早期に送信する必要があります。 あなたのゲームがメッセージを受信したとき、待合室 UIを却下する必要があります。

例えば:

boolean mWaitingRoomFinishedFromCode = false;

private void onStartGameMessageReceived() {
  mWaitingRoomFinishedFromCode = true;
  finishActivity(RC_WAITING_ROOM);
}

mWaitingRoomFinishedFromCodeフラグは必要です、なぜなら、上に示したように待合室を却下することは、Activity.RESULT_CANCELEDの結果コードが返されるということを引き起こすからです。 あなたはこのケースを、プレイヤーがバックボタンを使用して待合室を却下したケースと区別しなければなりません。

参加者のステータスをクエリする

Participant.getStatus()メソッドは、参加者の現在のステータスを返します。

  • STATUS_INVITED: 参加者は招待されたが、まだ招待の決定をしていない。
  • STATUS_DECLINED: 参加者は招待を断った。
  • STATUS_JOINED: 参加者はルームに参加した。
  • STATUS_LEFT: 参加者はルームから離れた。

さらには、あなたのゲームは参加者が接続されているかどうかを Participant.isConnectedToRoom()を呼び出すことによって検出することができます。

互いの参加者のステータスとアカウントへのつながりを取るために、あなたのゲームロジックを慎重に構築することを確認してください。 例えば、レースゲームでは、すべてのレーサーがフィニッシュラインを通過したかどうかを決定するには、あなたのゲームは接続された参加者のみを考慮する必要があります。 あなたのゲームはルームのすべてのプレイヤーがフィニッシュラインを通過することを待つべきでありません、なぜならすべての参加者が接続されていないかもしれないからです(例えば、1人以上のプレイヤーがルームから離れた、あるいは招待を断ったかもしれません)。

例えば:

Set<String> mFinishedRacers;

boolean haveAllRacersFinished(Room room) {
  for (Participant p : room.getParticipants()) {
    String pid = p.getParticipantId();
    if (p.isConnectedToRoom() && !mFinishedRacers.contains(pid)) {
      // at least one racer is connected but hasn't finished
      return false;
    }
  }
  // all racers who are connected have finished the race
  return true;
}

プレイヤーが切断されたときを検出する

あなたのプレイヤーは、ネットワークの接続性あるいはサーバの問題が原因でルームから切断される可能性があります。 プレイヤーがルームから切断されたときに通知されるには、RoomStatusUpdateCallback.onDisconnectedFromRoom()メソッドを実装します。

招待を処理する

ひとたびプレイヤーがサインインしたならば、あなたのゲームは他のプレイヤーによって作成されたルームに参加するための招待を通知されるかもしれません。 あなたのゲームはこれらのシナリオのための招待を処理する必要があります。

プレイヤーのサインイン時

もしサインインしているプレイヤーが Androidのステータスバーの通知領域からの招待を受け入れたならば、あなたのゲームは招待を受け入れ、直接ゲームスクリーンに行く必要があります(メインメニューをスキップして)。

まず、プレイヤーが正常にサインインした後、招待が利用可能かどうかをチェックします。 受け入れるための招待があるかどうかを確認するために、GamesClient.getActivationHint()メソッドを使用します。

private void checkForInvitation() {
  Games.getGamesClient(this, GoogleSignIn.getLastSignedInAccount(this))
      .getActivationHint()
      .addOnSuccessListener(
          new OnSuccessListener<Bundle>() {
            @Override
            public void onSuccess(Bundle bundle) {
              Invitation invitation = bundle.getParcelable(Multiplayer.EXTRA_INVITATION);
              if (invitation != null) {
                RoomConfig.Builder builder = RoomConfig.builder(mRoomUpdateCallback)
                    .setInvitationIdToAccept(invitation.getInvitationId());
                mJoinedRoomConfig = builder.build();
                Games.getRealTimeMultiplayerClient(thisActivity,
                    GoogleSignIn.getLastSignedInAccount(thisActivity))
                    .join(mJoinedRoomConfig);
                // prevent screen from sleeping during handshake
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
              }
            }
          }
      );

}

もし、端末がハンドシェイクあるいはゲームプレイ中にスリープ状態になったならば、プレイヤーはルームから切断されるでしょう。 端末が、マルチプレイヤーのハンドシェイクあるいはゲームプレイ中にスリープ状態になることを防ぐために、アクティビティの onCreate()メソッドにて FLAG_KEEP_SCREEN_ONフラグを有効にすることをお勧めします。 ゲームプレイの終了のとき、あるいは、ゲームがキャンセルされたとき、このフラグをクリアすることを忘れないでください。

ゲームプレイ中

受信した招待を通知されるために、あなたのゲームは InvitationCallbackクラスを使用します。 受信した招待はステータスバーの通知を生成しないでしょう。 代わりに、コールバックは onInvitationReceived()メソッドを経由して Invitationオブジェクトを受信し、あなたのゲームは、それからユーザに知らせるためのゲーム内のポップアップダイアログあるいは通知を表示することができます。 もしユーザが受け入れたならば、あなたのゲームは招待を処理し、ゲーム画面を起動する必要があります。

private InvitationCallback mInvitationCallbackHandler = new InvitationCallback() {
  @Override
  public void onInvitationReceived(@NonNull Invitation invitation) {
    RoomConfig.Builder builder = RoomConfig.builder(mRoomUpdateCallback)
        .setInvitationIdToAccept(invitation.getInvitationId());
    mJoinedRoomConfig = builder.build();
    Games.getRealTimeMultiplayerClient(thisActivity,
        GoogleSignIn.getLastSignedInAccount(thisActivity))
        .join(mJoinedRoomConfig);
    // prevent screen from sleeping during handshake
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  }

  @Override
  public void onInvitationRemoved(@NonNull String invitationId) {
    // Invitation removed.
  }
};

招待の受信トレイから

招待の受信トレイは、あなたのゲームが InvitationsClient.getInvitationInboxIntent()からの Intentを使用することによって表示することができるオプションの UIコンポーネントです。 受信トレイは、プレイヤーが受信したすべての利用可能な招待を表示します。 もしプレイヤーが受信トレイから保留中の招待を選択したならば、あなたのゲームは招待を受け入れ、ゲーム画面を起動する必要があります。

あなたのゲームのメイン画面から招待の受信トレイを起動するためのボタンを追加することができます。

受信トレイを起動するには:

private static final int RC_INVITATION_INBOX = 9008;

private void showInvitationInbox() {
  Games.getInvitationsClient(this, GoogleSignIn.getLastSignedInAccount(this))
      .getInvitationInboxIntent()
      .addOnSuccessListener(new OnSuccessListener<Intent>() {
        @Override
        public void onSuccess(Intent intent) {
          startActivityForResult(intent, RC_INVITATION_INBOX);
        }
      });
}

プレイヤーが受信トレイから招待を選択したとき、あなたのゲームは onActivityResult()を経由して通知されます。 あなたのゲームは、それから招待を処理します。

例えば:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RC_INVITATION_INBOX) {
    if (resultCode != Activity.RESULT_OK) {
      // Canceled or some error.
      return;
    }
    Invitation invitation = data.getExtras().getParcelable(Multiplayer.EXTRA_INVITATION);
    if (invitation != null) {
      RoomConfig.Builder builder = RoomConfig.builder(mRoomUpdateCallback)
          .setInvitationIdToAccept(invitation.getInvitationId());
      mJoinedRoomConfig = builder.build();
      Games.getRealTimeMultiplayerClient(thisActivity,
          GoogleSignIn.getLastSignedInAccount(this))
          .join(mJoinedRoomConfig);
      // prevent screen from sleeping during handshake
      getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
  }
}

クライアント間でゲームデータを交換する

リアルタイム型マルチプレイヤー APIを使用したデータメッセージ送信の背後にあるコンセプトを理解するために ゲームデータを送信するをレビューしてください。

メッセージを送信する

メッセージを送信するには、あなたのゲームは送信するためのメッセージの種類に応じて、RealTimeMultiplayerClient.sendReliableMessage()あるいは RealTimeMultiplayerClient.sendUnreliableMessage()メソッドを使用することができます。 代わりに、あなたのゲームは、ブロードキャストメッセージを送信するために RealTimeMultiplayerClient.sendUnreliableMessageToOthers()メソッドを使用することができます。

例えば、すべての他の参加者に信頼できるメッセージを送信するには:

void sendToAllReliably(byte[] message) {
  for (String participantId : mRoom.getParticipantIds()) {
    if (!participantId.equals(mMyParticipantId)) {
      Task<Integer> task = Games.
          getRealTimeMultiplayerClient(this, GoogleSignIn.getLastSignedInAccount(this))
          .sendReliableMessage(message, mRoom.getRoomId(), participantId,
              handleMessageSentCallback).addOnCompleteListener(new OnCompleteListener<Integer>() {
            @Override
            public void onComplete(@NonNull Task<Integer> task) {
              // Keep track of which messages are sent, if desired.
              recordMessageToken(task.getResult());
            }
          });
    }
  }
}

sendReliableMessage()を使用するとき、パラメータとして RealTimeMultiplayerClient.ReliableMessageSentCallbackを指定する必要があります。 もし sendReliableMessage()呼び出しが成功したならば、メッセージは内部キューに配置され、Taskオブジェクトが返されます。 Taskオブジェクトは、保留中のメッセージのためのメッセージトークン IDを提供します。 システムが実際にメッセージを送信したとき、あなたのゲームは RealTimeMultiplayerClient.ReliableMessageSentCallbackを通して通知されます。

例えば:

HashSet<Integer> pendingMessageSet = new HashSet<>();

synchronized void recordMessageToken(int tokenId) {
  pendingMessageSet.add(tokenId);
}

private RealTimeMultiplayerClient.ReliableMessageSentCallback handleMessageSentCallback =
    new RealTimeMultiplayerClient.ReliableMessageSentCallback() {
      @Override
      public void onRealTimeMessageSent(int statusCode, int tokenId, String recipientId) {
        // handle the message being sent.
        synchronized (this) {
          pendingMessageSet.remove(tokenId);
        }
      }
    };

メッセージを受信する

あなたのゲームがメッセージを受信したとき、それは OnRealTimeMessageReceivedListener.onRealTimeMessageReceived()メソッドによって通知されます: このメソッドは、それが信頼できる、あるいは信頼性の低いメッセージかに関わらず呼び出されるでしょう。 あなたのルームの構成をセットアップするとき、このリスナを登録してください。 このメソッドは、それが信頼できる、あるいは信頼性の低いメッセージかに関わらず呼び出されるでしょう。

例えば:

private OnRealTimeMessageReceivedListener mMessageReceivedHandler =
    new OnRealTimeMessageReceivedListener() {
      @Override
      public void onRealTimeMessageReceived(@NonNull RealTimeMessage realTimeMessage) {
        // Handle messages received here.
        byte[] message = realTimeMessage.getMessageData();
        // process message contents...
      }
    };

ルームから離れる

あなたのゲームはこれらのシナリオのいずれかが発生したとき、アクティブなルームから離れる必要があります:

  • ゲームプレイが終了した - もしあなたがルームから離れないならば、Google Playゲームサービスはあなたのルームのリスナに、そのルームのための通知を送信し続けるでしょう。
  • onStop()が呼ばれた - もし onStop()メソッドが呼び出されたならば、これはあなたのアクティビティが破壊され、ルームから離れる必要があることを示しているかもしれません。
  • ユーザが待合室 UIにてゲームをキャンセルした - さらには、もし onActivityResult()コールバックにて返されるレスポンスコードが GamesActivityResultCodes.RESULT_LEFT_ROOMであるならば、あなたのゲームはルームから離れる必要があります。

ルームから離れるには、RealTimeMultiplayerClient.leave()を呼び出します。

例えば:

Games.getRealTimeMultiplayerClient(thisActivity, GoogleSignIn.getLastSignedInAccount(this))
    .leave(mJoinedRoomConfig, mRoom.getRoomId());
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

ルームから離れた後、別のルームを開始し、あるいは参加しようとする前に、RoomUpdateCallback.onLeftRoom()メソッドへの呼び出しを受信するまで待ちます: