徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】僕もPhotonを使いたい #07 位置の同期

 前回、オブジェクト生成の同期が出来ました。
 次にオブジェクト内のパラメータを同期させてみましょう。
 Photonではパラメータの同期を取る方法いくつか存在します。
 今回はPhotonViewOnPhotonSerializeView()を使った同期にチャレンジします。


この記事にはUnity5.4.1f1及びPUN1.75を使用しています。

まずは何も考えないで同期にチャレンジ

 前回PhotonNetwork.Instantiate()をつかってオブジェクトを生成しました。
 そのオブジェクトにアタッチされているスクリプトにキーで移動制御を行う処理を追加してみましょう。
 Photonで生成したオブジェクトなら、何もしなくても勝手に同期をとってくれるかもしれない!

f:id:urahimono:20160919182535p:plain

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

f:id:urahimono:20160919182549g:plain

 無理でした。でしょうね!

 いくつか望んだ挙動とは違うところがありますね。
 まず、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

f:id:urahimono:20160919182905g:plain

 Player1ならPlayer1のオブジェクトだけが制御でき、Player2ならPlayer2のオブジェクトだけが制御できるようになりました。

 さて、今度は各オブジェクト移動情報を他のプレイヤーのゲームにも同期させる必要があります。
 どうやって行えばいいのでしょうか。

PhotonViewの力を信じる

 ではドキュメントを読んでみましょう。
 ちなみに最初にドキュメントを読んだ方が作業効率的がいいと思います。ドキュメントは最初に読みましょう。

doc.photonengine.com

 同期を取る方法はいろいろありそうですね。
 とりあえず一番上にある、PhotonViewOnPhotonSerializeView()を使う方法を試してみましょう。

 以下にあるPhoton公式ブログを参考にしました。

blog.photoncloud.jp

 PhotonViewObserved Componetsの箇所に自分自身のTransformをドラッグ&ドロップでセットします。

f:id:urahimono:20160919182956p:plain

 この状態でゲームを実行してみると以下の結果になりました。

f:id:urahimono:20160919183010g:plain

 おお、オブジェクトの位置が同期を取っています。
 カックカクだけどね!

 まあ、こんなもんでしょ。

PhotonTransformViewを使って手を抜く

 まあ、うん、このままじゃ駄目でしょうね。
 ネットワークを経由してますから毎フレーム位置情報が来るわけではないですもんね。
 自分で位置の補間を取らなくちゃいけないのでしょうか。正直面倒くさい……。

 もう一度ドキュメントを読んでみましょう。きっと手を抜けるいい方法があるはずです。

doc.photonengine.com

 ありましたよ、PhotonTransformViewという便利そうなものが!
 えーと、PhotonViewObserved ComponetsTransformの代わりにセットしてあげればOKっと。

f:id:urahimono:20160919183140p:plain

f:id:urahimono:20160919183316g:plain

 おお、滑らかに動いている!
 フレームレート15のGifファイルじゃ分からないとは思いますけど。

OnPhotonSerializeView()を実装する

 Transformも同期は出来たのですが、自分で作成したスクリプトのパラメータの同期は取れるのでしょうか。
 少なくとも、PhotonViewObserved Componetsにスクリプトをセットしてみましたが、何も起こりませんでした。これだけでは駄目なようですね。
 そりゃそうだ、どのパラメータの同期をとればいいのかを記述してませんもの。

 再びPhotonの公式ブログを見てみましょう。

blog.photoncloud.jp

 どうやらPhotonViewObserved ComponetsにセットするスクリプトにOnPhotonSerializeView()という関数が必要なようですね。

void OnPhotonSerializeView( PhotonStream stream, PhotonMessageInfo info )

https://photonengine.jp/pun-api/group__public_api.html#ga78c69bbb6f79d1e4fb23d3f761eaf4aa

 ふむふむ、OnPhotonSerializeView()OnJoinedRoom()などと同様に関数名と引数の型が一緒でなければいけないようですね。
 パラメータを送信する際も受信する際もこの関数一つで行うみたいですね。
 第一引数であるPhotonStreamisWritingで送信か受信かを判断できるみたいです。

 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

f:id:urahimono:20160919183447p:plain

f:id:urahimono:20160919183500g:plain

 色の同期もちゃんと行われています。

 Photonには他にも同期を取る方法があるみたいです。
 もう少し各項目を掘り下げていきたいところですが、一度他の同期方法も試してみるとしましょう。

 次回 www.urablog.xyz

 前回 www.urablog.xyz