【PhotonCloud】 PhotonTransformViewでTransformの同期を行う

Unityで簡単にネットワーク同期が実現できるPUN(Photon Unity Network)。
v1.50から**PhotonTransformView**なるTransform同期をサポートしてくれるコンポーネントが追加されました。

そもそもTransformの同期とは、「キャラクタの位置・姿勢・大きさ」の同期を意味します。
Transformの同期が上手くいかないと、突然キャラクタがワープしたり、カクカクの動きをしたりととてもじゃないが快適とは言えないゲームになってしまします。

今まではTransformを滑らかに同期してあげるためにはスクリプトを書き、OnPhotonSerializeViewでパラメータ同期をした上でスクリプトで補間を入れてあげる必要があり、そこそこ手間のかかるものでした。
(たまにPhotonViewに直接Transformを指定し、動きがカクカクになってしまっているゲームをリリースしている人を見かけます。**正気ですか？**)

その面倒臭かったTransformの滑らかな同期が、PhotonTransformViewを使うことでとても簡単にできるようになりました。
そこで今回はこちらの使い方を紹介したいと思います。

#PhotonTransformViewの使い方

## 1. GameObjectにComponentを貼り付ける

* PhotonView
* PhotonTransformView

Transformの同期を行いたいGameObjectに上記のコンポーネントを貼り付け、PhotonViewのObservedComponentsにPhotonTransformViewを追加します。
![PhotonView.png](https://qiita-image-store.s3.amazonaws.com/0/47146/8c5505d1-b5fc-c0cf-718c-695998745862.png)
PhotonViewの監視対象にPhotonTransformViewを追加

## 2. 同期したい要素にチェックを入れる

同期したい対象にチェックを入れます。
PositionとRotationだけの同期で良いなら「Synchronize Position」「Synchronize Rotation」の2つをチェックすればOKです。

![Synchronize.png](https://qiita-image-store.s3.amazonaws.com/0/47146/7075d2b0-2a98-603e-347e-bc591367c18f.png)

## 3.パラメータ同期の手法を設定する

### Synchronize Positionの設定
![Position.png](https://qiita-image-store.s3.amazonaws.com/0/47146/713c47d7-7fca-698f-4118-24e8c4b4a18a.png)

* **Enable teleport for greater distances**

同期ズレが起きてオブジェクトの位置が大幅にズレた時に、オブジェクトをワープさせて補正するかどうかの設定です。
ONにすると指定距離以上離れた時にオブジェクトの座標が強制的に上書きされます。
アクションゲームなど、相手の現在位置が重要なゲームではONにした方が良いかもしれません。

* **Interpolate Option**

補間処理。設定することでオブジェクトの動きが滑らかに同期されるようになります。
詳しくは後述に。

* **Extrapolate Option**

補外処理。未来の座標を予測してオブジェクトを予め移動させておくことで同期による遅延を低減することができます。
**急激に速度が変化したりする動きが予測できないオブジェクトには使わない方が良いです**

* **Draw synchronized position error**

ONにするとSceneViewに同期座標が描画されるようになります。たぶんデバッグ用かな。

#### Interpolate Optionの設定
![Interpolate.png](https://qiita-image-store.s3.amazonaws.com/0/47146/31978852-0c6a-0ae1-a8a4-c7d0f546721c.png)

* **Disable**

補間を使わない設定です。**動きがカクカクになるのでオススメしない。**

* **Fixed Speed**

常に一定速度でオブジェクトを追従させて補間します。
同期対象のオブジェクトの速度が固定されている場合に有効だそうです。

* **Estimated Speed**

受け取った「直前の座標」と「現在の座標」の差分を現在の速度と仮定してオブジェクトを移動させて補間します。
ゆっくりと加速／減速するオブジェクトに有効だそうです。

* **Synchronize Values**

スクリプト側で直接現フレームにおける速度と回転速度を与え、それを使って補間します。
**激しく動くオブジェクトに最適。アクションゲームにオススメします。**


使い方はスクリプトのUpdate内でPhotonTransformView.SetSynchronizedValuesにspeedとturnSpeedを指定してあげればOKです。
```SetSynchronizedValues(Vector3 speed, float turnSpeed)```


```csharp:SetSynchronizedValuesの使い方
private PhotonTransformView photonTransformView;
private CharacterController characterController;
void Start()
{
    photonTransformView = GetComponent<PhotonTransformView>();
    characterController = GetComponent<CharacterController>();
}

private void Update()
{
    /*
        * 
        ココらへんで移動処理とか
        * 
        * 
        */

    if (photonView.isMine)
    {
        //現在の移動速度
        var velocity = characterController.velocity;
        //移動速度を指定
        photonTransformView.SetSynchronizedValues(speed: velocity, turnSpeed: 0);
    }
}
```

turnSpeedはExtrapolate OptionでSynchronize Valuesを指定した際に使用されるパラメータであり、Interpolate Optionのみを使用する場合は不要(0指定で良い）です。


* **Lerp**

お馴染みの線形補間。**面倒くさい時はとりあえずコレ。**
激しく動くオブジェクトも滑らかに同期されるようになるが、動きがワンテンポ遅れるので注意。

#### Extrapolate Optionの設定

だいたいInterpolate Optionと同じなので省略。
ただしInterpolateと違い、ちゃんと設定しないと凄まじく同期ズレするので注意。わからなかったらDisableでいいかも。

***

### Synchronize Rotationの設定

Rtationには補間処理しかありません。

![rotation.png](https://qiita-image-store.s3.amazonaws.com/0/47146/7f1c58c0-2a36-90ec-76d7-968c716d8316.png)


* **Disable**

補間しない設定。激しく回転するオブジェクトの場合は回転がカクカクになります。

* **Rotate Toward**

指定速度で補間する設定。

* **Lerp**

線形補間。**面倒ならこれでいい気かも。**

***

### Synchronize Scaleの設定

* **Disable**

補間しない設定。

* **Rotate Toward**

指定速度で補間する設定。

* **Lerp**

線形補間。

## 4. 実際に動かして試す

パラメータの値を調整して、一番自然に見える値を設定しましょう。

# まとめ

PhotonViewとPhotonTransformViewの2つのコンポーネントを貼るだけで滑らかにTransformを同期できるようになりました。スクリプトを書かずに面倒くさい補間処理をやってもらえるのはとても楽で良いですね。

ただ自分が試したところ、Positionの同期は**Synchronize ValuesにCharacterController.velocityを食わせる**のが最も滑らかに同期するように感じました。**結局スクリプトは書くことからは逃れられない。**