うら干物書き

ゲームを作っています。

【Unity】僕もPhotonを使いたい #06 オブジェクトを作ろう

 前回まででPhotonの接続からルームへ入室までの流れが、なんとなくですが掴めてきました。
 今回はPhotonを使ってオブジェクトの生成に挑戦してみますよ。


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

Photonのオブジェクト生成API

 Unity上でオブジェクトを生成するにはGameObject.Instantiate()を使用して引数で渡したオブジェクトをコピーして生成しますよね。
 ただ、マルチゲームでこの関数を使用しても、自分のゲーム上にはオブジェクトが生成されるけど、他のプレイヤーのゲーム上にはオブジェクトが生成されません。
 さてどうしましょうか。
 とりあえずPhotonNetworkクラスが何とかしてくれそうな気がするのでスクリプトリファレンスを見てみましょう。

http://photonengine.jp/pun-api/class_photon_network.htmlphotonengine.jp

 PhotonNetwork.Instantiate()PhotonNetwork.InstantiateSceneObject()という生成関数らしきものが見つかりました。
 引数にGameObjectではなく、作成するオブジェクトのResourcesフォルダからのパスを渡す必要があるようです。
 GameObject.Instantiate()Resources.Load()を組み合わせた、GameObject.Instantiate( Resources.Load() )みたいな形でしょうか。

 早速使ってみますよ。
 適当なオブジェクトをResourcesフォルダに作ってそれをPhotonNetwork.Instantiate()を使ってゲーム上で生成してみます。

f:id:urahimono:20160918143452p:plain

using UnityEngine;

public class DemoNetwork : MonoBehaviour
{
    [SerializeField]
    private string  m_resourcePath  = "";
    [SerializeField]
    private float   m_randomCircle  = 4.0f;

    private const string ROOM_NAME  = "RoomA";

    void Start()
    {
        PhotonNetwork.ConnectUsingSettings( null );
    }
    
    void OnJoinedLobby()
    {
        PhotonNetwork.JoinOrCreateRoom( ROOM_NAME, new RoomOptions(), TypedLobby.Default );
    }

    public void SpawnObject()
    {
        PhotonNetwork.Instantiate( m_resourcePath, GetRandomPosition(), Quaternion.identity, 0 );
    }

    private Vector3 GetRandomPosition()
    {
        var rand = Random.insideUnitCircle * m_randomCircle;
        return rand;
    }
} // class DemoNetwork

 エラーが出た!
 どういうことだ。

f:id:urahimono:20160918143612p:plain

Failed to Instantiate prefab:Cube. Prefab must have a PhotonView component.

 PhotonViewがどうとか言っていますね。
 うーん、どういうことでしょう。

PhotonViewが必要なのです

 ドキュメントを読んでみましょう。というか最初から読めばよかった……。

https://doc.photonengine.com/ja-jp/pun/current/tutorials/instantiationdoc.photonengine.com

 うん、PhotonViewがいるって書いてますね。
 PhotonViewについては同期処理を行う際に、詳しく調べてみます。

 ではPhotonViewを先ほどのオブジェクトに追加して、もう一度試してみましょう。

f:id:urahimono:20160918143734p:plain f:id:urahimono:20160918145317g:plain

 プレイヤー1とプレイヤー2の両方のゲームにオブジェクトが生成出来ました。
 これで今回のお題であるオブジェクトの生成ができました。
 ここで筆をおいてもいいのですが、確かPhotonにはもう一つオブジェクト生成関数がありましたね。そちらも使ってみましょう。

PhotonNetwork.Instantiate()とPhotonNetwork.InstantiateSceneObject()

 PhotonNetwork.InstantiateSceneObject()を使ってオブジェクトを生成してみましょう。
 PhotonNetwork.Instantiate()と何が違うんでしょうか。

using UnityEngine;

public class DemoNetwork : MonoBehaviour
{
    [SerializeField]
    private string  m_resourcePath  = "";
    [SerializeField]
    private float   m_randomCircle  = 4.0f;

    private const string ROOM_NAME  = "RoomA";

    void Start()
    {
        PhotonNetwork.ConnectUsingSettings( null );
    }

    void OnJoinedLobby()
    {
        PhotonNetwork.JoinOrCreateRoom( ROOM_NAME, new RoomOptions(), TypedLobby.Default );
    }

    public void SpawnObject()
    {
        PhotonNetwork.Instantiate( m_resourcePath, GetRandomPosition(), Quaternion.identity, 0 );
    }

    public void SpawnSceneObject()
    {
        PhotonNetwork.InstantiateSceneObject( m_resourcePath, GetRandomPosition(), Quaternion.identity, 0, null );
    }

    private Vector3 GetRandomPosition()
    {
        var rand = Random.insideUnitCircle * m_randomCircle;
        return rand;
    }
} // class DemoNetwork

f:id:urahimono:20160918145156g:plain

 ふむ、何が違うのかがわからない!
 ただ、部屋の主(MasterClient)であるPlayer1でないとPhotonNetwork.InstantiateSceneObject()は機能しませんでした。

 もう一度ドキュメントを読んでみましょう。

https://doc.photonengine.com/ja-jp/pun/current/tutorials/instantiationdoc.photonengine.com

 ネットワークオブジェクトのライフタイムの項目に以下のような記述がありました。

PhotonNetwork.Instantiateで生成されるゲームオブジェクトは、通常、同じルーム内にいる限り存在し続けます。
ルームを替えても、Unityのシーンを切り替えるときと同様、オブジェクトが移動することはありません。
クライアントがルームを離れるとき、プレイヤーが生成した所有オブジェクトは全て破棄されます。

マスタークライアントは、PhotonNeworkInstantiateSceneObject()を用いて、ルームと同じライフタイムを持つゲームオブジェクトを生成することができます。
このオブジェクトはマスタークライアントではなく、ルームに結びつけられます。
デフォルトの設定では、マスタークライアントがこれらのオブジェクトを管理しますが、PhotonView.TransferOwnership()を用いてその権限を渡すことができます。

 ふぅむ、PhotonNetwork.Instantiate()は生成したプレイヤーに紐づけられ、PhotonNetwork.InstantiateSceneObject()はルームに紐づけられるわけですか。
 これを確認するために、プレイヤーを途中で退室させて生成したオブジェクトがどうなるのかを見てみましょう。

 ただ、現在のプロジェクトでは、どのオブジェクトが誰の作ったオブジェクトなのかがわかりにくいため、コンポーネントを追加して視覚的にわかりやすくしましょう。

using UnityEngine;

public class DemoObject : MonoBehaviour
{
    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 Update()
    {
        int ownerID             = m_photonView.ownerId;
        m_render.material.color = MATERIAL_COLORS[ ownerID ];
    }
} // class DemoObject

f:id:urahimono:20160918144544p:plain

 このコンポーネントを生成されるオブジェクトにアタッチすることで、生成者によって色が変わります。
 ではこのオブジェクトを生成した後に、各プレイヤーが退室したらどうなるのかを見てみましょう。

Player2が退室する場合
f:id:urahimono:20160918145032g:plain

Player1が退室する場合
f:id:urahimono:20160918144905g:plain

 PhotonNetwork.Instantiate()で生成したオブジェクトは、生成したプレイヤーが退室したら消えますが、PhotonNetwork.InstantiateSceneObject()で生成したオブジェクトは残り続けていますね。

 このプレイヤーが退室した際にオブジェクトが消えるのを抑制したい場合は、PhotonNetwork.autoCleanUpPlayerObjectsfalseを設定してあげればいいみたいです。
 ちゃんと各自で消さないとゴミが残ってしまうんですけどね。

 次回は位置などの同期について調べていきます。

 次回 www.urablog.xyz

 前回 www.urablog.xyz