PUN2でプレイヤーごとにカメラを割り当てたいときの解決法

この記事は、[Unity #3 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/unity3) 17日目の記事です。
昨日は、 @_stripeNA さんの「[スマートフォン課金実装(Unity IAP)について](https://qiita.com/_stripeNA/items/58fa0e1031bdbde758d1)」でした。

# はじめに
最近[PUN2](https://assetstore.unity.com/packages/tools/network/pun-2-free-119922)を使ったマルチプレイを実装する機会があったのですが、プレイヤーごとにカメラを割り当てても別のプレイヤーのカメラが表示されてしまうという問題に遭遇しました。そこで今回はこの問題と自分なりに考えたその解決法について書こうと思います。

※ 本記事ではPUN2のセットアップが終わっている前提で書いていきます。PUN2についてやセットアップの仕方については、以下の記事などが参考になるので、ご参照ください。
https://learning.unity3d.jp/1490/
https://connect.unity.com/p/pun2deshi-meruonraingemukai-fa-ru-men-sono1

# 今回起きた問題
ネットワークオブジェクトとして、Avatar用のゲームオブジェクトとカメラをセットにしてプレハブを用意したところ、実際に表示される視点がお互いに相手の視点になってしまいました。

以下で詳細を説明します。
## 前提
シーンは以下のように構成されています。

- `PhotonLogin` はEmpty Objectで、`PhotonLogin`スクリプトがアタッチされています。
- Prefabの `Avatar` はただのCubeです。MainCameraを子オブジェクトにもっていて、`Avatar` スクリプトがアタッチされています。

<img width="1276" alt="スクリーンショット 2018-12-16 18.44.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/52234/cd6ea352-7e09-e468-cf32-294c36faafae.png">

各スクリプトの内容は以下の通りです。
`PhotonLogin` スクリプトではルームの作成、ジョイン、ネットワークオブジェクトのインスタンス化を行なっています。
`Avatar`スクリプトではAvatarをキーボードから動かすためのスクリプトが書かれています。

```csharp:PhotonLogin.cs
public class PhotonLogin : MonoBehaviourPunCallbacks {
    private GameObject player;
    void Start() {
        PhotonNetwork.ConnectUsingSettings();

        player = GameObject.FindGameObjectWithTag("Player");
    }

    public override void OnConnectedToMaster() {
        // "room"という名前のルームに参加する（ルームが無ければ作成してから参加する）
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.PublishUserId = true;
        PhotonNetwork.JoinOrCreateRoom("room", roomOptions, TypedLobby.Default);
    }

    public override void OnJoinedRoom() {
        int num = PhotonNetwork.CurrentRoom.PlayerCount;
        PhotonNetwork.Instantiate("Prefabs/Avatar", new Vector3(0, num*2, 0), Quaternion.identity);

    }
}
```

```csharp:Avatar.cs
public class Avatar : MonoBehaviour {
	private PhotonView photonView;

	void Start () {
		photonView = GetComponent<PhotonView>();
	}
	
	void Update () {
		if( photonView.IsMine == false ){
			return;
		}

		// キーボード入力による移動処理
		var v = Input.GetAxis ("Vertical");
		Vector3 velocity = new Vector3 (0, 0, v);
		velocity = transform.TransformDirection (velocity);
		velocity *= 5f;
		transform.localPosition += velocity * Time.fixedDeltaTime;

		// キーボード入力による回転処理
		var h = Input.GetAxis ("Horizontal");
		transform.Rotate (0, h * 3f, 0);
	}
}
```

## 動かしてみる
実際に動作させてみた様子です。上と下で別のUnityを立ち上げていて、中身が同じプロジェクトを開いています。
上のUnityにフォーカスして回転させた時は下のUnityの方で回転しており、逆に下のUnityにフォーカスして回転させた時は上のUnityの方で回転している様子がわかると思います。
これはお互いに相手の方のカメラが表示されてしまっているため、期待した表示ではありません。
![pun.gif](https://qiita-image-store.s3.amazonaws.com/0/52234/7644ebe2-a4a6-1144-7595-11b3222947af.gif)

# 解決法
なぜこうなってしまうのかの原因はまだ調べられていませんが、「ネットワークオブジェクトにカメラを含めない」ことでひとまず解決することができます。

カメラをネットワークオブジェクトから除いてシーンに追加しておき、Avatarが生成された後にスクリプトでカメラを子にします。

```csharp
    public override void OnJoinedRoom() {
        int num = PhotonNetwork.CurrentRoom.PlayerCount;
        GameObject avatar = PhotonNetwork.Instantiate("Prefabs/Avatar", new Vector3(0, num*2, 0), Quaternion.identity);
        camera.transform.parent = avatar.transform;
        camera.transform.position = avatar.transform.position;
    }
```

動かしてみると、以下のようになります。
上にフォーカスしている時は上が回転し、下にフォーカスしているときは下が回ります。つまりちゃんと各プレイヤーがの視点で表示することができています。
![pun2.gif](https://qiita-image-store.s3.amazonaws.com/0/52234/98826d08-004b-a131-4481-6a2245f5fa23.gif)

カメラをネットワークオブジェクトから除いたことでシーン内にカメラが１つだけになるので、当然といえば当然の動作ですね。

# まとめ
PUN2を使って各プレイヤーにカメラを割り当てたい時は、ネットワークオブジェクトにカメラを含めない方がいいという知見をご紹介しました。

PUN2を触ったのが初めてだったのでかなり当たり前な内容だったかもしれませんが、同じ問題に遭遇している方のお役に立てれば幸いです。
(原因はこれから調べてみようと思います。Depthは同じだから、IsMineあたりの処理なのだろうか... :thinking:  )

---
明日は @tyutana12 さんです。おたのしみに！