さて前回、乱入型のゲームを目指してゲームを作り始めました。
そしたら早くも問題にぶつかりました。
今回は私がシーン切り替えの際に問題になったPhotonNetwork.isMessageQueueRunning
について調べてみました。
この記事にはUnity5.4.1f1及びPUN1.76を使用しています。
シーンを切り替えてオブジェクト生成する
ゲームの基礎部分を作り始めした。
ロビー用のシーンを作成し、そのシーンでネットワークに接続しロビーに入りルームに入ります。
ルームに入室した後は、ゲーム用のシーンに遷移しプレイヤーが操作するオブジェクトを生成する。
この辺りは今までの僕もPhotonを使いたいシリーズでやってきました。
PhotonNetwork.Instantiate()
を使ってプレイヤー用のオブジェクトを作れば、後からルームに入室プレイヤーにもオブジェクトが同期されるはずです。
using UnityEngine; using UnityEngine.SceneManagement; public class DemoNetwork : MonoBehaviour { [SerializeField] private string m_resourcePath = ""; [SerializeField] private float m_randomCircle = 4.0f; private const string ROOM_NAME = "RoomA"; private const string SCENE_NAME = "DemoB"; void Start() { DontDestroyOnLoad( gameObject ); // シーンの読み込みコールバックを登録. SceneManager.sceneLoaded += OnLoadedScene; PhotonNetwork.ConnectUsingSettings( Application.version ); } public void JoinRoom() { PhotonNetwork.JoinOrCreateRoom( ROOM_NAME, new RoomOptions(), TypedLobby.Default ); } void OnJoinedRoom() { // ルームに入ると同時にシーンを遷移させる. SceneManager.LoadScene( SCENE_NAME ); } private void OnLoadedScene( Scene i_scene, LoadSceneMode i_mode ) { // シーンの遷移が完了したら自分用のオブジェクトを生成. if( i_scene.name == SCENE_NAME ) { Vector3 pos = Random.insideUnitCircle * m_randomCircle; PhotonNetwork.Instantiate( m_resourcePath, pos, Quaternion.identity, 0 ); } } } // class DemoNetwork
おかしいですね。
ルームに後から入室したプレイヤー2のシーンにはプレイヤー1のオブジェクトがありません。
前回調べたときはちゃんと他のプレイヤーにもオブジェクトが生成されたはずなのですが……。
前回と違うのはシーンを切り替えたことぐらいです。
では上記のスクリプトを少し改良して、ルームに入室後少し時間をあけてからシーンを切り替えるようにしましょう。
using UnityEngine; using UnityEngine.SceneManagement; using System.Collections; public class DemoNetwork : MonoBehaviour { [SerializeField] private string m_resourcePath = ""; [SerializeField] private float m_randomCircle = 4.0f; private const string ROOM_NAME = "RoomA"; private const string SCENE_NAME = "DemoB"; void Start() { DontDestroyOnLoad( gameObject ); // シーンの読み込みコールバックを登録. SceneManager.sceneLoaded += OnLoadedScene; PhotonNetwork.ConnectUsingSettings( Application.version ); } public void JoinRoom() { PhotonNetwork.JoinOrCreateRoom( ROOM_NAME, new RoomOptions(), TypedLobby.Default ); } void OnJoinedRoom() { // 少し時間をおいてからシーンを遷移させる. StartCoroutine( LoadScene( 2.0f ) ); } private void OnLoadedScene( Scene i_scene, LoadSceneMode i_mode ) { // シーンの遷移が完了したら自分用のオブジェクトを生成. if( i_scene.name == SCENE_NAME ) { Vector3 pos = Random.insideUnitCircle * m_randomCircle; PhotonNetwork.Instantiate( m_resourcePath, pos, Quaternion.identity, 0 ); } } private IEnumerator LoadScene( float i_time ) { // 一定時間経ってからシーンを読む. yield return new WaitForSeconds( i_time ); SceneManager.LoadScene( SCENE_NAME ); } } // class DemoNetwork
Player2にはルームに入室した瞬間にはPlayer1のオブジェクトがあります。
そして、シーンを切り替えたらオブジェクトが消えてなくなってしまいます。
なぜなら生成するオブジェクトは、DontDestroyOnLoad()
を呼んで設定していないから。
なるほど、オブジェクトはちゃんと同期されていたけどシーンが切り替わったことでオブジェクトが破棄されているだけだったんですね!
理由はわかりましたが、ちょっと困りますね。
そもそもルームに入室瞬間に同期を取り始めるのではなく、シーンを切り替え終わってから同期をとってほしいものです。
何か方法はあるのでしょうか。
PhotonNetwork.isMessageQueueRunning
PhotonNetwork
にはこんなプロパティがあります。isMessageQueueRunning
です。
リファレンスを見てみましょう。
https://photonengine.jp/pun-api/class_photon_network.html
PhotonNetwork.isMessageQueueRunning
受信イベント(RPCやインスタンス作成やその他受信するすべて)の送出を一時停止するのに使われます。
IsMessageQueueRunning == falseのとき、OnPhotonSerializeViewは呼ばれず、クライアントには何も送られません。
着信メッセージも、メッセージキューを再有効化するまではキューに追加されません。
この設定が便利なのは、最初にレベルを読み込んで、次にPhotonViewとRPCのデータを受信し続けようとしたときです。
クライアントは、着信パケットとRPC/イベントの応答を、受信し送信し続けます。
これは「ラグ」を増加させ、一時停止が長引き着信メッセージはすべてキューに貯まるだけという問題を引き起こす可能性があります。
なるほど、isMessageQueueRunning
をfalse
にしておけばイベントを受け取らないみたいですよ。
試してみましょう。
using UnityEngine; using UnityEngine.SceneManagement; public class DemoNetwork : MonoBehaviour { [SerializeField] private string m_resourcePath = ""; [SerializeField] private float m_randomCircle = 4.0f; private const string ROOM_NAME = "RoomA"; private const string SCENE_NAME = "DemoB"; void Start() { DontDestroyOnLoad( gameObject ); // シーンの読み込みコールバックを登録. SceneManager.sceneLoaded += OnLoadedScene; PhotonNetwork.ConnectUsingSettings( Application.version ); } public void JoinRoom() { PhotonNetwork.JoinOrCreateRoom( ROOM_NAME, new RoomOptions(), TypedLobby.Default ); } void OnJoinedRoom() { // ルームに入ると同時にシーンを遷移させる. PhotonNetwork.isMessageQueueRunning = false; SceneManager.LoadScene( SCENE_NAME ); } private void OnLoadedScene( Scene i_scene, LoadSceneMode i_mode ) { PhotonNetwork.isMessageQueueRunning = true; // シーンの遷移が完了したら自分用のオブジェクトを生成. if( i_scene.name == SCENE_NAME ) { Vector3 pos = Random.insideUnitCircle * m_randomCircle; PhotonNetwork.Instantiate( m_resourcePath, pos, Quaternion.identity, 0 ); } } } // class DemoNetwork
ルームに入った瞬間にPhotonNetwork.isMessageQueueRunning = false
を呼んでイベントを受け取らないようにします。
そしてシーン切り替えた後にPhotonNetwork.isMessageQueueRunning = true
を呼んでイベントを受け取るようにしています。
そうすることで、オブジェクトの生成の同期がシーン切り替え後に行われるようになりました。
これで自分の任意のタイミングで同期がとれるようになりました。
このPhotonNetwork.isMessageQueueRunning
、少し気になることがあります。
PhotonNetwork.isMessageQueueRunning
はRPC()
に対してはどのような挙動になるのでしょうか。
調べてみましょう。
RPC()とisMessageQueueRunning
では以下のスクリプトを使って調査してみます。
RPC()
を使ってオブジェクトの生成を同期します。
オブジェクトの生成には、PhotonNetwork.Instantiate()
ではなくGameObject.Instantiate()
を使うので、オブジェクトそのものは同期しません。
using UnityEngine; public class DemoNetwork : MonoBehaviour { [SerializeField] private string m_resourcePath = ""; [SerializeField] private float m_randomCircle = 4.0f; private PhotonView m_photonView = null; private const string ROOM_NAME = "RoomA"; void Start() { m_photonView = GetComponent< PhotonView >(); PhotonNetwork.ConnectUsingSettings( Application.version ); } public void JoinRoom() { PhotonNetwork.JoinOrCreateRoom( ROOM_NAME, new RoomOptions(), TypedLobby.Default ); } public void SwitchMessageQueueRunning() { PhotonNetwork.isMessageQueueRunning = !PhotonNetwork.isMessageQueueRunning; } public void SpawnObject() { Vector3 pos = Random.insideUnitCircle * m_randomCircle; m_photonView.RPC( "SpawnObjectRPC", PhotonTargets.All, pos ); } public void SpawnObjectBuffered() { Vector3 pos = Random.insideUnitCircle * m_randomCircle; m_photonView.RPC( "SpawnObjectRPC", PhotonTargets.AllBuffered, pos ); } [PunRPC] private void SpawnObjectRPC( Vector3 i_pos ) { Vector3 pos = Random.insideUnitCircle * m_randomCircle; GameObject.Instantiate( Resources.Load( m_resourcePath ), i_pos, Quaternion.identity ); } } // class DemoNetwork
まずは、RPC()
のPhotonTargets
を復習しましょう。
PhotonTargets.All
とPhotonTargets.AllBuffered
についてです。
ルームに入室前に呼ばれたRPC()
の情報はPhotonTargets.AllBuffered
のようなBufferedタイプでなければ受け取りません。
では、PhotonNetwork.isMessageQueueRunning
のプロパティを切り替えてRPC()
の情報を受け取ってみましょう。
isMessageQueueRunning
をfalse
にした後に、PhotonTargets.All
とPhotonTargets.AllBuffered
のRPC()
を呼んで、その後isMessageQueueRunning
をtrue
にしてイベントを受け取れるようにします。
この場合は、PhotonTargets.All
とPhotonTargets.AllBuffered
両方でイベントを受け取れています。
この違いはちゃんと覚えておいたほうがよさそうですね。
さて、これで乱入型ゲームの基礎が出来ました。
引き続きゲームを作っていきますー。