あなたのゲームにリアルタイム型マルチプレイヤーサポートを追加する



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

このガイドでは、Android用の C++のアプリケーションにて Google Playゲームサービスを使用してリアルタイム型マルチプレイヤーゲームを実装する方法を示します。

始める前に

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

リアルタイム型マルチプレイヤーゲームをコーディングし始める前に、次のことを確認してください:

あなたのゲームは、ひとたび Google Playゲームサービスにサインインすることに成功したならば、リアルタイム型マルチプレイヤー APIを使用し始めることができ、ゲームは GameServices::Builder::SetOnAuthActionFinishedコールバックを発射します、それは、成功したステータスを報告します。

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

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

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

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

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

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

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

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.
    // …
}

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

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

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

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

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

ひとたびプレイヤーが選択され、あなたのゲームが PlayerSelectUIResponseを受信したならば、あなたのゲームはレスポンスからルーム構成を作成することができます:

  // Create room config.
  gpg::RealTimeRoomConfig config =
      gpg::RealTimeRoomConfig::Builder()
          .PopulateFromPlayerSelectUIResponse(response)
          .Create();

実際に、構成から RealTimeRoomを作成する前に、ルームについての通知を受信するリスナインタフェースを作成しなければなりません。 あなたのクラスのいずれかに IRealTimeEventListenerの仮想メソッドを実装することによって、これを行います。

  class YourClass : public IRealTimeEventListener {
   public:
    // Your implementations of IRealTimeEventListener.
  };

これらのメソッドの詳細な議論は、以下のセクションに現れます。

ひとたびリスナインタフェースが定義されれば、あなたの構成を用いた新しいルームをセットアップするために CreateRealTimeRoomを呼び出すことができます:

  service_->RealTimeMultiplayer().CreateRealTimeRoom(
        config, new MyRealTimeEventListener(...), /* your completion callback */);

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

CreateRealTimeRoomに提供される非同期のコールバックから返される RealTimeRoomResponseは、すべてのゲームのエラーを通知します。 もしルーム作成エラーが発生したならば、あなたのゲームはプレイヤーに通知するためのメッセージを表示し、メイン画面に戻る必要があります。

  void MyRealTimeRoomCreationCallback(
      gpg::RealTimeMultiplayerManager::RealTimeRoomResponse const &response) {
    if (gpg::IsError(response.status)) {
      // We got an error, notify the user.
    }
  }

ルームステータスの変更

Once your room is created, the IRealTimeEventListener::OnRoomStatusChanged method will notify you that the real-time room's status has changed: ひとたびあなたのルームが作成されれば、IRealTimeEventListener::OnRoomStatusChangedメソッドは、リアルタイム型ルームのステータスが変更されたことをあなたに通知するでしょう:

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

参加者のステータスの変更

すべての参加者が接続されたときに通知されるには、あなたのゲームは、ACTIVEステータスを返した OnRoomStatusChangedメソッドをチェックする必要があります。 参加者のステータスの変更のよりきめ細かい情報については、以下のメソッドを使用することができます:

1人以上の参加者が正常にルームに接続した(そしてデータを受信することができる)という通知は、OnConnectedSetChangedメソッドを経由して報告されます:

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

OnParticipantStatusChangedメソッドは、個々のプレイヤのステータスの変更の通知を提供します:

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および OnP2PDisconnectedメソッドは、他の参加者への直接の接続の通知を提供します。 ほとんどのゲームはこれらの通知を安全に無視することができます。 これらは、ルームが完全に接続される前に他のユーザと通信を開始したいゲームに有用かもしれません。

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

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

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

デフォルトの待合室 UIを起動するには、ShowWaitingRoomUI()メソッドを呼び出します。

あなたのゲームは、CreateRealTimeRoomあるいは AcceptInvitationに提供される非同期のコールバックから待合室 UIを起動することができます。

待合室 UIが却下されたとき、あなたのゲームは APIの戻り値として結果を受信します。 却下の理由は RealTimeRoomResponse::Statusで示され、次のいずれかになります:

  • MultiplayerStatus::VALID - 招待されたすべてのプレイヤーは正常にルームに接続された。
  • MultiplayerStatus::ERROR_CANCELED - プレイヤーが UIからバックした。
  • MultiplayerStatus::ERROR_LEFT_ROOM - プレイヤーが Leave Roomオプションを選択した。

ユーザが明示的にゲームをキャンセルした(ERROR_LEFT_ROOM)か、待合室 UIを終了した(ERROR_CANCELED)かに応じて、異なるレスポンスを実装することができます。

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

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

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

  • ParticipantStatus::INVITED: 参加者は招待されたが、まだレスポンスしていない。
  • ParticipantStatus::DECLINED: 参加者は招待を断った。
  • ParticipantStatus::JOINED: 参加者はルームに参加した。
  • ParticipantStatus::LEFT: 参加者はルームから離れた。

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

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

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

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

virtual void OnConnectedSetChanged(RealTimeRoom const &room) {
  // Check the room's participants to see who connected/disconnected.
}

招待を処理する

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

ゲームプレイ中

受信した招待を通知されるために、あなたのゲームは GameServices::Builderを構成しているときに SetOnMultiplayerInvitationEventメソッドを経由してコールバックを登録することができます。 受信した招待はゲームが開いている間はステータスバーを生成しません。 代わりに、コールバックが通知され、あなたのゲームは、それからユーザに知らせるためのゲーム内のポップアップダイアログあるいは通知を表示することができます。 もしユーザが受け入れたならば、あなたのゲームは招待を処理し、ゲーム画面を起動する必要があります。

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

招待の受信トレイから

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

アプリは次のように招待を受け入れます:

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.

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

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

メッセージを送信する

信頼性の低いプロトコルを使用してメッセージを送信するには、RealTimeMultiplayerManager::SendUnreliableMessageを使用します。 信頼できるメッセージを送信するには、RealTimeMultiplayerManager::SendReliableMessageを使用します。

次の例は、信頼できる、あるいは信頼性の低いメッセージのいずれかを使用して、スコアをブロードキャストする方法を示しています。

      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 &) {});
          }
        }
      }

ルームから離れる

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

  • ゲームプレイが終わった。
  • アクティビティが停止したとき。
  • ユーザが待合室にてゲームをキャンセルした。
  • ShowWaitUIから返されたレスポンスコードが UIStatus::ERROR_LEFT_ROOMだった。

ルームから離れるには、LeaveRoomを呼び出します:

service_->RealTimeMultiplayer().LeaveRoom(room_, [](ResponseStatus const &response) {
  if (IsSuccess(response)) {
    // We left successfully. We can now join another room.
  }
});