ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20200424_01

      【PUN2】ネットワーク管理にPhotonを使ってみる 〜導入からルーム・ロビー管理まで〜

      Unityアセット関連オンラインゲームPhoton
      2020-04-26

      マイケル
      マイケル
      どうもみなさんこんにちは!
      マイケルです!
      エレキベア
      エレキベア
      クマ〜〜〜
      マイケル
      マイケル
      今日はこれまで作っていた3Dゲームに
      ネットワーク機能をつけてマルチプレイできるようにしようとしていたのですが・・・

      ※過去記事はこちら!
      【Unity】オンライン3Dアクションゲームの作り方をまとめる! (前編)

      Unityゲーム開発 オンライン3Dアクションゲームの作り方

      マイケル
      マイケル
      参考にして進めていた書籍の情報が古すぎて全く使えませんでした・・・
      エレキベア
      エレキベア
      2014年に書かれた本だから仕方ないクマ・・・
      マイケル
      マイケル
      そんなわけで現在はどのように実装するのがいいのか調べてみたところ、
      PhotonUnityNetworking(以下、PUN)というサービスを使うのがよさそうだということが分かりました!
      今回はPUNの導入とルーム管理機能について調べてまとめます!
      エレキベア
      エレキベア
      PUNって何なんクマ?
      マイケル
      マイケル
      いい質問だね!
      それじゃまずはPUNの概要についてみていこう!

      PUNとは

      マイケル
      マイケル
      まずはPUNとは何か、なぜ選んだかについて!

      〜PUNとは〜
      Photon Unity Networking の略で、
      マスターサーバやルーム管理などマルチプレイ実装に必要な機能を
      提供してくれるUnityパッケージである。
      ・・・ Photon公式サイトはこちら

      エレキベア
      エレキベア
      これを使うとマルチプレイ機能が簡単に実装できるというわけクマね
      マイケル
      マイケル
      そういうことさ!
      こういったサービスを使わなかったら、サーバの準備などハード面の問題NATパンチスルーなど通信問題等、考慮して組み込まないといけないんだけどその辺の面倒くさい機能を一律提供してくれるんだ!
      マイケル
      マイケル
      ただ、20人までの接続は無料だけど、それ以上は有料プランになってしまうので注意です
      エレキベア
      エレキベア
      すごいクマ〜〜〜!
      PUNの他にも色々あるクマか??
      マイケル
      マイケル
      まずUnityに標準で組み込まれているUNetというのがあったんだけど、これはUnity2019で廃止されたみたいだ。
      Photonと同じようなサービスとしては他にMUN、GameLift、Strix Unity SDKというのもあるみたいだよ!
      マイケル
      マイケル
      今回はその中でも導入例が多くて機能数も多いPUNを使用したというわけさ!
      エレキベア
      エレキベア
      そんなにたくさんあるクマか

      導入方法

      マイケル
      マイケル
      導入方法は以下の手順になります!

      1. Photon公式サイトでアカウント登録をし、アプリIDを取得する。
      Photon公式サイト

      2. AssetStoreから「PUN2」をインポートする。

      3. Window > Photon Unity Networking > PUN Wizard > SetUp ProjectでアプリIDを設定する。
      ※再起動しないとメニューに出てこない場合があります。

      スクリーンショット 2020 04 26 11 08 23

      4. Assets/Photon/PhotonNetworking/Resources配下にあるPhotonServerSettingsの「Fixed Region」に「jp」と入力する。

      マイケル
      マイケル
      以上で導入は完了です!
      エレキベア
      エレキベア
      かんたん4ステップクマ

      実装方法について

      マイケル
      マイケル
      それでは実装に移っていきます
      マイケル
      マイケル
      まず、Photonのルーム管理までの流れは下記の図のようになります!

      スクリーンショット 2020 04 26 11 59 32

      エレキベア
      エレキベア
      Photonサーバの中でロビーやルームを管理してくれてるクマね
      マイケル
      マイケル
      このイラストの矢印部分の

      1. Photonサーバ接続・切断
      2. ロビー入退室
      3. ルーム作成・入退室

      に沿って実装方法を見ていくよ!

      Photonサーバ接続・切断

      
      // コールバック機能を使うためMonoBehaviourPunCallbacksを継承
      public class PhotonManager : MonoBehaviourPunCallbacks
      {
      ・・・略・・・
      
          // Photonサーバ接続処理
          public void ConnectPhoton(bool boolOffline)
          {
              if (boolOffline)
              {
                  // オフラインモードを設定
                  PhotonNetwork.OfflineMode = true; // OnConnectedToMaster()が呼ばれる
                  return;
              }
              // Photonサーバに接続する
              PhotonNetwork.ConnectUsingSettings();
          }
      
          // Photonサーバ切断処理
          public void DisConnectPhoton()
          {
              PhotonNetwork.Disconnect();
          }
      
          // コールバック:Photonサーバ接続完了
          public override void OnConnectedToMaster()
          {
               base.OnConnectedToMaster();
              }
          }
      
          // コールバック:Photonサーバ接続失敗
          public override void OnDisconnected(DisconnectCause cause)
          {
              base.OnDisconnected(cause);
          }
      ・・・略・・・
      }
      マイケル
      マイケル
      Photonサーバへの接続はConnectUsingSettingメソッド
      切断時はPhotonNetwork.Disconnectメソッドを使用します。

      使用するとコールバックで
      OnConnectedToMasterメソッド(接続完了時)
      OnDisconnectedメソッド(失敗時)

      が呼び出されるので接続結果に対しての処理はこちらに記述しましょう!

      マイケル
      マイケル
      コールバック関数を定義するため、継承クラスはMonoBehaviourPunCallbacksに変更するのも忘れずに行って下さい
      エレキベア
      エレキベア
      さっき設定したPhotonServerSettingsを使って接続するクマね
      マイケル
      マイケル
      飲み込みが早いですね
      マイケル
      マイケル
      また、オフラインで接続したい場合には、PhotonNetwork.OfflineModeをtrueに設定します。
      その場合、trueにした時点でコールバック処理が呼び出されるため注意しましょう!
      エレキベア
      エレキベア
      数行かけば済むから簡単クマね〜〜

      ロビー入退室

          // コールバック:Photonサーバ接続完了
          public override void OnConnectedToMaster()
          {
          	・・・略・・・
              // ロビーに接続
              PhotonNetwork.JoinLobby();
          }
      
          // コールバック:ロビー接続完了
          public override void OnJoinedLobby()
          {
              base.OnJoinedLobby();
          }
      
          // コールバック:ルーム一覧更新処理
          // (ロビーに入室した時、他のプレイヤーが更新した時のみ)
          public override void OnRoomListUpdate(List<RoomInfo> roomList)
          {
              base.OnRoomListUpdate(roomList);
          }
      マイケル
      マイケル
      ロビーへの入室はJoinLobbyメソッド を使用します!

      こちらも同じく入室完了後にコールバックとしてOnJoinedLobbyメソッド が呼び出されます。

      エレキベア
      エレキベア
      Photonサーバ接続と同じような感じクマね
      マイケル
      マイケル
      また、ロビー入室時に上記OnRoomListUpdateメソッド が呼び出されて
      ロビー内のルーム一覧が取得できるのですが、これが中々曲者で・・・
      マイケル
      マイケル
      コールバックされるタイミングは
      ・ロビー入室時
      ・他のプレイヤーがルームを更新した時
      だけで、ルーム更新時には更新したルームリストしか引数で渡されてこないんです。
      
          // コールバック:ルーム一覧更新処理
          // (ロビーに入室した時、他のプレイヤーが更新した時のみ)
          public override void OnRoomListUpdate(List<RoomInfo> roomList)
          {
              base.OnRoomListUpdate(roomList);
              // ルーム一覧更新
              foreach (var info in roomList)
              {
                  if (!info.RemovedFromList)
                  {
                      // 更新データが削除でない場合
                      roomDispList.Add(info);
                  }
                  else
                  {
                      // 更新データが削除の場合
                      roomDispList.Remove(info);
                  }
              }
          }
      マイケル
      マイケル
      そのため上記のように渡されてきたルームが削除かどうかを調べて表示するリストを更新しなければならないようです。
      マイケル
      マイケル
      その他にも自動入室するAuto-Join Lobyや、ルーム一覧を取得するgetRoomListなんて機能もあったらしいですが、どうやらPUN2で廃止されたらしく、自分で実装するしかなさそうです・・・

      リンク先(参考):https://doc.photonengine.com/en-us/pun/v2/getting-started/migration-notes

      エレキベア
      エレキベア
      カナシマシマシクマ・・・・

      ルーム作成・入退室

          // ルーム作成・入室処理
          public void CreateRoom(string roomName)
          {
              PhotonNetwork.CreateRoom(roomName);
          }
      
          // ルーム入室処理
          public void ConnectToRoom(string roomName)
          {
              PhotonNetwork.JoinRoom(roomName);
          }
      
          // コールバック:ルーム作成完了
          public override void OnCreatedRoom()
          {
              base.OnCreatedRoom();
          }
      
          // コールバック:ルーム作成失敗
          public override void OnCreateRoomFailed(short returnCode, string message)
          {
              base.OnCreateRoomFailed(returnCode, message);
          }
      
          // コールバック:ルームに入室した時
          public override void OnJoinedRoom()
          {
              base.OnJoinedRoom();
          }
      マイケル
      マイケル
      ルームの作成はCreateRoomメソッド
      ルームの入室はJoinRoomメソッド を使用します。

      こちらも他のメソッド と同じくコールバックとして
      OnCreatedRoomメソッド (ルーム作成完了時)
      OnCreateRoomFailedメソッド (ルーム作成失敗時)
      OnJoinedRoomメソッド (ルーム入室時)
      がそれぞれよばれます!

      マイケル
      マイケル
      ただ一点注意なのは、ルームの作成時には自動で入室処理が行われるということです。
      まあルームにはホストが必要なので当たり前といえばそうですが・・・
      エレキベア
      エレキベア
      これでルーム入室まで出来たクマね
      マイケル
      マイケル
      とりあえずここまでの機能で接続は試せそうだね。
      早速これらを使って接続を試していこう!

      接続テストプログラムの作成

      マイケル
      マイケル
      接続やメソッド呼び出しを確認するため、テスト用の画面を作りました!
      作った画面は下記になります。
      マイケル
      マイケル
      モード選択でONLINEModeを選ぶとPhotonサーバと接続すると・・・
      マイケル
      マイケル
      ルーム作成とロビーにあるルーム一覧が表示される!
      確認する機能だけに絞った画面になります
      マイケル
      マイケル
      下記にGUI表示も含めたソースは全部下に貼っておくので、
      少し長いですが必要に応じて参考にしてください!
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using Photon.Pun;
      using Photon.Realtime;
      
      public class PhotonManager : MonoBehaviourPunCallbacks
      {
          string mode;                 // モード(ONLINE, OFFLINE)
          string dispStatus;           // 画面項目:状態
          string dispMessage;          // 画面項目:メッセージ
          string dispRoomName;         // 画面項目:ルーム名
          List<RoomInfo> roomDispList; // 画面項目:ルーム一覧
      
          // 状態
          public enum Status
          {
              ONLINE,   // オンライン
              OFFLINE,  // オフライン
          };
      
          private void Start()
          {
              initParam();
          }
      
          // 変数初期化処理
          private void initParam()
          {
              dispRoomName = "";
              dispMessage = "";
              dispStatus = Status.OFFLINE.ToString();
              roomDispList = new List<RoomInfo>();
          }
      
          // Photonサーバ接続処理
          public void ConnectPhoton(bool boolOffline)
          {
              if (boolOffline)
              {
                  // オフラインモードを設定
                  mode = Status.OFFLINE.ToString();
                  PhotonNetwork.OfflineMode = true; // OnConnectedToMaster()が呼ばれる
                  dispMessage = "OFFLINEモードで起動しました。";
                  return;
              }
              // Photonサーバに接続する
              mode = Status.ONLINE.ToString();
              PhotonNetwork.OfflineMode = false;
              PhotonNetwork.ConnectUsingSettings();
          }
      
          // Photonサーバ切断処理
          public void DisConnectPhoton()
          {
              PhotonNetwork.Disconnect();
              // 変数初期化
              initParam();
          }
      
          // コールバック:Photonサーバ接続完了
          public override void OnConnectedToMaster()
          {
              base.OnConnectedToMaster();
              if (Status.ONLINE.ToString().Equals(mode))
              {
                  dispStatus = Status.ONLINE.ToString();
                  dispMessage = "サーバに接続しました。";
                  // ロビーに接続
                  PhotonNetwork.JoinLobby();
              }
          }
      
          // コールバック:Photonサーバ接続失敗
          public override void OnDisconnected(DisconnectCause cause)
          {
              base.OnDisconnected(cause);
              dispMessage = "サーバから切断しました。";
              dispStatus = Status.OFFLINE.ToString();
          }
      
          // コールバック:ロビー入室完了
          public override void OnJoinedLobby()
          {
              base.OnJoinedLobby();
          }
      
          // ルーム一覧更新処理
          // (ロビーに入室した時、他のプレイヤーが更新した時のみ)
          public override void OnRoomListUpdate(List<RoomInfo> roomList)
          {
              base.OnRoomListUpdate(roomList);
              // ルーム一覧更新
              foreach (var info in roomList)
              {
                  if (!info.RemovedFromList)
                  {
                      // 更新データが削除でない場合
                      roomDispList.Add(info);
                  }
                  else
                  {
                      // 更新データが削除の場合
                      roomDispList.Remove(info);
                  }
              }
          }
      
          // ルーム作成処理
          public void CreateRoom(string roomName)
          {
              PhotonNetwork.CreateRoom(roomName);
          }
      
          // ルーム入室処理
          public void ConnectToRoom(string roomName)
          {
              PhotonNetwork.JoinRoom(roomName);
          }
      
          // コールバック:ルーム作成完了
          public override void OnCreatedRoom()
          {
              base.OnCreatedRoom();
              dispMessage = "ルームを作成しました。";
          }
      
          // コールバック:ルーム作成失敗
          public override void OnCreateRoomFailed(short returnCode, string message)
          {
              base.OnCreateRoomFailed(returnCode, message);
              dispMessage = "ルーム作成に失敗しました。";
          }
      
          // コールバック:ルームに入室した時
          public override void OnJoinedRoom()
          {
              base.OnJoinedRoom();
              // 表示ルームリストに追加する
              roomDispList.Add(PhotonNetwork.CurrentRoom);
              dispMessage = "【" + PhotonNetwork.CurrentRoom.Name + "】" + "に入室しました。";
          }
      
          // ---------- 設定GUI ----------
          void OnGUI()
          {
              float scale = Screen.height / 480.0f;
              GUI.matrix = Matrix4x4.TRS(new Vector3(
                  Screen.width * 0.5f, Screen.height * 0.5f, 0),
                  Quaternion.identity,
                  new Vector3(scale, scale, 1.0f));
      
              GUI.Window(0, new Rect(-200, -200, 400, 400),
                  NetworkSettingWindow, "Photon接続テスト");
          }
      
          Vector2 scrollPosition;
          void NetworkSettingWindow(int windowID)
          {
              // ステータス, メッセージの表示
              GUILayout.BeginHorizontal();
              GUILayout.Label("状態: " + dispStatus, GUILayout.Width(100));
              GUILayout.FlexibleSpace();
              if (Status.ONLINE.ToString().Equals(dispStatus))
              {
                  // サーバ接続時のみ表示
                  if (GUILayout.Button("切断"))
                      DisConnectPhoton();
              }
              GUILayout.EndHorizontal();
              GUILayout.Label(dispMessage);
              GUILayout.Space(20);
      
              if (!Status.ONLINE.ToString().Equals(dispStatus))
              {
                  // --- 初期表示時、OFFLINEモードのみ表示
                  // マスターサーバに接続する
                  GUILayout.Label("【モード選択】");
                  GUILayout.BeginHorizontal();
                  GUILayout.FlexibleSpace();
                  if (GUILayout.Button("ONLINE Mode"))
                      ConnectPhoton(false);
                  if (GUILayout.Button("OFFLINE Mode"))
                      ConnectPhoton(true);
                  GUILayout.EndHorizontal();
              }
              else if (Status.ONLINE.ToString().Equals(dispStatus))
              {
                  // --- ONLINEモードのみ表示
                  if (!(PhotonNetwork.CurrentRoom != null))
                  {
                      // ルーム作成
                      GUILayout.Label("【ルーム作成】");
                      GUILayout.BeginHorizontal();
                      GUILayout.Label(" ルーム名: ");
                      dispRoomName = GUILayout.TextField(dispRoomName, GUILayout.Width(150));
                      GUILayout.FlexibleSpace();
                      GUILayout.EndHorizontal();
                      // 作成ボタン
                      GUILayout.BeginHorizontal();
                      if (GUILayout.Button("作成 & 入室"))
                      {
                          CreateRoom(dispRoomName);
                      }
                      GUILayout.EndHorizontal();
                      GUILayout.Space(20);
      
                      // ルーム一覧
                      GUILayout.Label("【ルーム一覧 (クリックで入室)】");
                      // 一覧表示
                      scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(380), GUILayout.Height(100));
                      if (roomDispList != null && roomDispList.Count > 0)
                      {
                          // 更新ボタン
                          GUILayout.BeginHorizontal();
                          GUILayout.FlexibleSpace();
                          if (GUILayout.Button("更新"))
                          {
                              // ロビーに入り直す
                              roomDispList = new List<RoomInfo>();
                              PhotonNetwork.LeaveLobby();
                              PhotonNetwork.JoinLobby();
                          }
                          // ルーム一覧
                          GUILayout.EndHorizontal();
                          foreach (RoomInfo roomInfo in roomDispList)
                              if (GUILayout.Button(roomInfo.Name, GUI.skin.box, GUILayout.Width(360)))
                                  ConnectToRoom(roomInfo.Name);
                      }
                      GUILayout.EndScrollView();
                  }
              }
          }
      }
      

      接続テスト結果

      マイケル
      マイケル
      それじゃ作ったテストプログラムで接続を確認していくよ!
      エレキベア
      エレキベア
      楽しみクマね〜〜
      マイケル
      マイケル
      ネットワークテストで実行アプリが2つ以上必要だから、
      ビルドして書き出したAPPとUnityシミュレータでの操作とビルドファイルでの操作が同期されていることを確認します!
      マイケル
      マイケル
      まずUnityシミュレータ側でルームを作成して、、

      スクリーンショット 2020 04 26 13 23 06

      マイケル
      マイケル
      ビルドしたAPPで接続するとUnityシミュレータ側で作ったルームが無事に一覧に表示されています!
      どうやらうまく同期できたみたいです!
      エレキベア
      エレキベア
      おめでとうクマ〜〜〜!!
      マイケル
      マイケル
      ここまで確認出来ればあとはマルチプレイ機能をガンガン実装していくだけだ!
      エレキベア
      エレキベア
      大きな一歩を踏み出せたクマね

      まとめ

      マイケル
      マイケル
      というわけで今回はPhotonを使用したルーム作成の確認でした!
      マイケル
      マイケル
      ネット上も古い情報が多すぎて
      これだけ調べるのでも丸一日かかってつかれたぜ・・・
      エレキベア
      エレキベア
      おつかれさまクマ・・・
      エレキベア
      エレキベア
      でも同期できた時は楽しかったクマね〜〜
      マイケル
      マイケル
      そうだね!
      これからゲームに組み込んでいくのが楽しみだ!
      エレキベア
      エレキベア
      一緒にがんばっていくクマ〜〜〜!
      マイケル
      マイケル
      心強いぜエレキベア!
      それじゃ今回はこの辺で!アデュー!
      エレキベア
      エレキベア
      クマ〜〜〜

      【PUN2】ネットワーク管理にPhotonを使ってみる 〜導入からルーム・ロビー管理まで〜 〜完〜


      Unityアセット関連オンラインゲームPhoton
      2020-04-26

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
      2024-04-14
      【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-30
      【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
      2024-03-30
      【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-28
      【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
      2024-03-28
      【Unity】サウンドミドルウェアに依存しない設計を考える【CRI ADX・Wwise】
      2024-03-27