【PUN】アバター生成・同期


Photon Unity Networkの基本機能メモ

[【PUN】ログイン](https://qiita.com/mo4_9/items/4aba4601feb3e6440826)
[【PUN】アバター生成・同期](https://qiita.com/mo4_9/items/75324cc31709ec329d26)
[【PUN】ゲーム終了判定](https://qiita.com/mo4_9/items/87b969da060c975736cf)

---

## 開発方法

Unity Collaborateでプロジェクトを共有して、PCを2台並べて開発するのがおすすめ。
（3台で見たい場合はアプリでビルド）

## アバター生成

__PhotonNetwork.Instantiate__
https://doc.photonengine.com/ja-jp/pun/current/gameplay/instantiation
生成するゲームオブジェクトはResources以下にプレファブとして格納
生成対象はクライアント間通信の橋渡しであるPhotonViewコンポーネントをもつことが必須

シーンにローカルプレイヤー用のオブジェクトを置いておいて、配下にアバターを生成する

```csharp:Player.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Player : MonoBehaviour {

	void OnJoinedRoom(){
		CreateAvatar ();
	}

	void CreateAvatar() {
		Debug.Log ("Player: 自分のアバターを生成");

		// 自分のアバターを他のクライアントにも生成
		GameObject avatar = PhotonNetwork.Instantiate ("Avatar", new Vector3(0f,1f,0f), Quaternion.identity, 0);
		avatar.transform.parent = transform;

		GameObject camera = transform.Find ("Camera").gameObject;
		camera.transform.parent = avatar.transform;
	}
}
```

## Transformの同期

アバターにPhotonTransformViewコンポーネントをアタッチ
PositionとRotationを同期にチェック
PhotonViewコンポーネントのObservedComponentsに登録して監視対象にする

![2017-12-23 (2).png](https://qiita-image-store.s3.amazonaws.com/0/136286/f01dca56-89e7-7e62-9fc6-c9d4389289ad.png)

## MonoBehaviourの同期

Avatar.csを作成、アバターにアタッチ
MonoBehaviourからPhoton.MonoBehaviour（自身のスクリプトが紐づくオブジェクトにアタッチされているPhotonViewコンポーネントへのアクセスを提供するヘルパークラス）に変更
PhotonViewコンポーネントのObservedComponentsに登録して監視対象にする

```csharp
using UnityEngine;
using System.Collections;

public class Avatar : Photon.MonoBehaviour {

	[SerializeField] int m_healthPoint = 100;
	[SerializeField] GameObject m_BulletPrefab;
	[SerializeField] GameObject m_DamageEffectPrefab;

	PhotonView m_photonView;

	void Start() {
		m_photonView = GetComponent<PhotonView> ();
		if(m_photonView.isMine) {
			// 初期設定
			SetPlayerName ("Player-Id: " + PhotonNetwork.player.ID);
			SetHealthPoint (m_healthPoint);
		}
	}

	void SetPlayerName(string name) {
		this.gameObject.name = name;
		transform.Find ("NameUI").gameObject.GetComponent<TextMesh> ().text = name;
	}

	void SetHealthPoint(int num) {
		m_healthPoint = num;
		transform.Find ("HealthPointUI").gameObject.GetComponent<TextMesh> ().text = num.ToString();
	}

	// ストリーム同期
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
		if (stream.isWriting) {
			// 自分の情報を送る
			string myName = this.gameObject.name;
			int myHealthPoint = m_healthPoint;
			stream.SendNext (myName);
			stream.SendNext (myHealthPoint);
		} else {
			// 他人の情報を受け取る
			string otherName = (string)stream.ReceiveNext();
			int otherHealthPoint = (int)stream.ReceiveNext();
			SetPlayerName (otherName);
			SetHealthPoint (otherHealthPoint);
		}
	}

	// イベント同期
	[PunRPC]
	void Shoot(Vector3 i_pos, Vector3 i_angle) {
        // 全クライアントの中の自分が一斉に弾丸を発射する
		Quaternion rot = Quaternion.Euler (i_angle);
		GameObject bullet = GameObject.Instantiate (m_BulletPrefab ,i_pos ,rot);
		bullet.GetComponent<Rigidbody> ().AddForce (transform.forward * 20, ForceMode.VelocityChange);
		Destroy(bullet, 3);
	}

	void OnTriggerEnter(Collider i_other) {
		if(i_other.tag == "bullet"){
			// ダメージを受ける
			SetHealthPoint (m_healthPoint - 10);
			Instantiate (m_DamageEffectPrefab, transform.position, Quaternion.identity);
			Destroy (i_other.gameObject);

			if(m_healthPoint <= 0){
				if (m_photonView.isMine) {
					// gameover
				}
			}
		}
	}

	void Update () {

		if(m_photonView.isMine == false){
			return;
		}

		if (Input.GetKeyDown (KeyCode.Space)) {
			// 自分自身の弾丸でトリガーを引かないようにやや手前から発射
			Vector3 pos = transform.position + transform.forward * 2f;
			Vector3 angle = transform.eulerAngles;
			// 処理が重いのでPhotonViewを付けずに位置と角度を渡す
			m_photonView.RPC ("Shoot", PhotonTargets.AllViaServer, pos, angle);
		}

		// 自分のアバターだけを動かす
		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);
	}

}
```

## ストリーム同期 / OnPhotonSerializeView

__OnPhotonSerializeView__
定期的に呼ばれ、パラメータを送信・受信する

[202 2 連続的な値の同期方法](https://github.com/yumemi-inc/vr-studies/wiki/202-2-%E9%80%A3%E7%B6%9A%E7%9A%84%E3%81%AA%E5%80%A4%E3%81%AE%E5%90%8C%E6%9C%9F%E6%96%B9%E6%B3%95)


## イベント同期 / RPC

__PhotonView.RPC__
他クライアントのメソッドを実行する

__[PunRPC]__
void Method(){}
メソッドを定義する際に[PunRPC]属性をつける

[202 3 離散的な値の同期方法](https://github.com/yumemi-inc/vr-studies/wiki/202-3-%E9%9B%A2%E6%95%A3%E7%9A%84%E3%81%AA%E5%80%A4%E3%81%AE%E5%90%8C%E6%9C%9F%E6%96%B9%E6%B3%95)