前回、オブジェクト生成の同期が出来ました。
次にオブジェクト内のパラメータを同期させてみましょう。
Photonではパラメータの同期を取る方法いくつか存在します。
今回はPhotonView
とOnPhotonSerializeView()
を使った同期にチャレンジします。
この記事にはUnity5.4.1f1及びPUN1.75を使用しています。
- まずは何も考えないで同期にチャレンジ
- PhotonView.isMineを使って所有者の判断を
- PhotonViewの力を信じる
- PhotonTransformViewを使って手を抜く
- OnPhotonSerializeView()を実装する
まずは何も考えないで同期にチャレンジ
前回PhotonNetwork.Instantiate()
をつかってオブジェクトを生成しました。
そのオブジェクトにアタッチされているスクリプトにキーで移動制御を行う処理を追加してみましょう。
Photonで生成したオブジェクトなら、何もしなくても勝手に同期をとってくれるかもしれない!
using UnityEngine; public class DemoObject : MonoBehaviour { [SerializeField] private float m_speed = 3.0f; private PhotonView m_photonView = null; private Renderer m_render = null; private readonly Color[] MATERIAL_COLORS = new Color[] { Color.white, Color.red, Color.green, Color.blue, Color.green, }; void Awake() { m_photonView = GetComponent<PhotonView>(); m_render = GetComponent<Renderer>(); } void Start() { int ownerID = m_photonView.ownerId; m_render.material.color = MATERIAL_COLORS[ ownerID ]; } void Update() { Vector3 pos = transform.position; pos.x += Input.GetAxis( "Horizontal" ) * m_speed * Time.deltaTime; pos.y += Input.GetAxis( "Vertical" ) * m_speed * Time.deltaTime; transform.position = pos; } } // class DemoObject
無理でした。でしょうね!
いくつか望んだ挙動とは違うところがありますね。
まず、Player1が作成したオブジェクトだけでなく、Player2が作成したオブジェクトまで操作出来てしまっている点。
もう一つは、Player2のゲーム上では何も動いてない点です。
一つずつ解決していきましょう。
Player1が生成したオブジェクトだけ操作するように改良しましょう。
PhotonView.isMineを使って所有者の判断を
ルーム内にてオブジェクト生成の同期を取る際は、PhotonNetwork.Instantiate()
を使用すればいいんでしたね。
では作成したオブジェクトが、自分が作成したオブジェクトなのか、他の人が作成したオブジェクトなのか判断できるのでしょうか。
PhotonNetwork.Instantiate()
にて作成するオブジェクトに必ずアタッチしろと言われたPhotonView
あたりが何か知っていそうです。
https://photonengine.jp/pun-api/class_photon_view.html
isMine
プロパティが使えそうです。
自分自身が所有者である場合にtrue
が返ってくるそうです。
なるほど、生成者ではなく所有者という考え方なのですね。生成したプレイヤーとそれを制御するプレイヤーが違う可能性は確かにありそうですね。
この所有者については、また別の機会に詳しく調べるとしましょう。
ではisMine
で所有者の場合のみ移動制御が行えるようにスクリプトを改良しましょう。
using UnityEngine; public class DemoObject : MonoBehaviour { [SerializeField] private float m_speed = 3.0f; private PhotonView m_photonView = null; private Renderer m_render = null; private readonly Color[] MATERIAL_COLORS = new Color[] { Color.white, Color.red, Color.green, Color.blue, Color.green, }; void Awake() { m_photonView = GetComponent<PhotonView>(); m_render = GetComponent<Renderer>(); } void Start() { int ownerID = m_photonView.ownerId; m_render.material.color = MATERIAL_COLORS[ ownerID ]; } void Update() { // 持ち主でないのなら制御させない if( !m_photonView.isMine ) { return; } Vector3 pos = transform.position; pos.x += Input.GetAxis( "Horizontal" ) * m_speed * Time.deltaTime; pos.y += Input.GetAxis( "Vertical" ) * m_speed * Time.deltaTime; transform.position = pos; } } // class DemoObject
Player1ならPlayer1のオブジェクトだけが制御でき、Player2ならPlayer2のオブジェクトだけが制御できるようになりました。
さて、今度は各オブジェクト移動情報を他のプレイヤーのゲームにも同期させる必要があります。
どうやって行えばいいのでしょうか。
PhotonViewの力を信じる
ではドキュメントを読んでみましょう。
ちなみに最初にドキュメントを読んだ方が作業効率的がいいと思います。ドキュメントは最初に読みましょう。
同期を取る方法はいろいろありそうですね。
とりあえず一番上にある、PhotonView
のOnPhotonSerializeView()
を使う方法を試してみましょう。
以下にあるPhoton公式ブログを参考にしました。
PhotonView
のObserved Componets
の箇所に自分自身のTransform
をドラッグ&ドロップでセットします。
この状態でゲームを実行してみると以下の結果になりました。
おお、オブジェクトの位置が同期を取っています。
カックカクだけどね!
まあ、こんなもんでしょ。
PhotonTransformViewを使って手を抜く
まあ、うん、このままじゃ駄目でしょうね。
ネットワークを経由してますから毎フレーム位置情報が来るわけではないですもんね。
自分で位置の補間を取らなくちゃいけないのでしょうか。正直面倒くさい……。
もう一度ドキュメントを読んでみましょう。きっと手を抜けるいい方法があるはずです。
ありましたよ、PhotonTransformViewという便利そうなものが!
えーと、PhotonView
のObserved Componets
にTransform
の代わりにセットしてあげればOKっと。
おお、滑らかに動いている!
フレームレート15のGifファイルじゃ分からないとは思いますけど。
OnPhotonSerializeView()を実装する
Transform
も同期は出来たのですが、自分で作成したスクリプトのパラメータの同期は取れるのでしょうか。
少なくとも、PhotonView
のObserved Componets
にスクリプトをセットしてみましたが、何も起こりませんでした。これだけでは駄目なようですね。
そりゃそうだ、どのパラメータの同期をとればいいのかを記述してませんもの。
再びPhotonの公式ブログを見てみましょう。
どうやらPhotonView
のObserved Componets
にセットするスクリプトにOnPhotonSerializeView()
という関数が必要なようですね。
void OnPhotonSerializeView( PhotonStream stream, PhotonMessageInfo info )
https://photonengine.jp/pun-api/group__public_api.html#ga78c69bbb6f79d1e4fb23d3f761eaf4aa
ふむふむ、OnPhotonSerializeView()
もOnJoinedRoom()
などと同様に関数名と引数の型が一緒でなければいけないようですね。
パラメータを送信する際も受信する際もこの関数一つで行うみたいですね。
第一引数であるPhotonStream
のisWriting
で送信か受信かを判断できるみたいです。
Photonの公式ブログでは色のパラメータの同期を行っているようなので、こちらも真似してやってみましょう。
using UnityEngine; public class DemoObject : MonoBehaviour { [SerializeField] private float m_speed = 3.0f; [SerializeField] private float m_colorSpeed = 1.0f; private PhotonView m_photonView = null; private Renderer m_render = null; private Color m_color = Color.white; private readonly Color[] MATERIAL_COLORS = new Color[] { Color.white, Color.red, Color.green, Color.blue, Color.green, }; void Awake() { m_photonView = GetComponent<PhotonView>(); m_render = GetComponent<Renderer>(); } void Start() { int ownerID = m_photonView.ownerId; m_color = MATERIAL_COLORS[ ownerID ]; } void Update() { // 持ち主でないのなら制御させない. if( !m_photonView.isMine ) { return; } Vector3 pos = transform.position; pos.x += Input.GetAxis( "Horizontal" ) * m_speed * Time.deltaTime; pos.y += Input.GetAxis( "Vertical" ) * m_speed * Time.deltaTime; transform.position = pos; // マテリアルの青の成分のみを時間経過によって変化させる. m_color.b += m_colorSpeed * Time.deltaTime; m_color.b = Mathf.Repeat( m_color.b, 1.0f ); m_render.material.color = m_color; } void OnPhotonSerializeView( PhotonStream i_stream, PhotonMessageInfo i_info ) { if( i_stream.isWriting ) { //データの送信 i_stream.SendNext( m_color.r ); i_stream.SendNext( m_color.g ); i_stream.SendNext( m_color.b ); i_stream.SendNext( m_color.a ); } else { //データの受信 float r = (float)i_stream.ReceiveNext(); float g = (float)i_stream.ReceiveNext(); float b = (float)i_stream.ReceiveNext(); float a = (float)i_stream.ReceiveNext(); m_color = new Color( r, g, b, a ); m_render.material.color = m_color; } } } // class DemoObject
色の同期もちゃんと行われています。
Photonには他にも同期を取る方法があるみたいです。
もう少し各項目を掘り下げていきたいところですが、一度他の同期方法も試してみるとしましょう。