Photon Fusion for UnityのサンプルSocial HubとProjectilesをあわせて物理シューターを作る　その２

# はじめに

前回の記事「[Photon Fusion for UnityのサンプルSocial HubとProjectilesをあわせてシューターを作る](https://qiita.com/nimushiki/items/f753196ff9014e080ab6)」では、シーン構成による実装上の注意点やトポロジーの併用方法を、2つの公式サンプルを組み合わせたものをベースに紹介しました。
今回は同じサンプルをコインプッシャーとシューターを合わせたゲームとして改造し、大量の物理オブジェクトを扱う際に早い段階で考えるべき内容について解説します

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/e426bb24-15aa-3925-5aad-59bfbe0529f3.gif)

### 前提記事

Photon Fusionの基本的な解説は以下の記事で行っていますので、そちらを参照下さい。

https://qiita.com/nimushiki/items/d6fe4f0cbe0437677fa8

### 動作確認環境

Windows 11 Home 22H2
Unity 2022.3.2f1
Fusion SDK 1.1.6 F Build 696

# 大量の物理オブジェクトを扱うにあたって

今回のような大量の物理オブジェクトを同期するゲームを作るにあたって、後々の実装に影響の大きい設定がいくつかあります。

## ネットワークトポロジーをどうするか

ネットワークトポロジーはマシン同士がどのようにオンライン接続するかということです。Photon Fusionではいくつか選択できますが、今回対象となるのはそのうち2つです。
それ以外の内容や詳細については以下の記事を参照してください。

https://qiita.com/nimushiki/items/5cd0ddfb49993afce365

### ホストモードを使う

ホストモードはホストがほとんどのオブジェクトを処理し、クライアントがその結果を同期する、という方式です。
同期処理のうち物理挙動に関して、クライアント側でも物理計算を行うかどうかを設定するNetworkProjectConfigのServerPhysicsModeをどうするかが重要になってきます。

- ServerOnly

ホストが計算した物理挙動を同期する方式です。物理挙動は正確ですが、クライアント側はホストの処理を待つ必要があるため遅延が起こりやすいです。

- Client Predition

各クライアントがそれぞれ物理挙動の処理を行い統合する方式です。各々で物理計算があったり統合処理が増える分、処理が重くなりますがクライアント側の遅延は少ないです。
今回のようにオブジェクトが多数ある場合、折り重なるとバタつきが酷くなります。

### 共有モードを使う

共有モードは同期するオブジェクトを手分けして処理するような方式です。
ホストモードに比べ機能の制限が多くあります。その分学習コストが控えめになっているので初心者が試しやすいのはもちろん、PUN2と近い動作のため経験を活かしやすいメリットがあります。
なおベースにするサンプルの作りの都合上、今回こちらは扱いません。

## 採用した組み合わせ

今回目標とするゲームはシューターとコインプッシャーを融合したものになります。そのため今回は、コインプッシャー単体に比べ物理の精度を下げられるのと、シューター部分の気持ちよさを考え遅延が少ない方がよさそう、という判断によりクライアントホストモードでClient Preditionにする方式を採用しました。
ただし、コインプッシャーゲームとしての実装を考えるならさほど正確な同期は必要ないかと思います。そのため、ServerPhysicsModeをserver onlyにし遅延を誤魔化していく方向か、ネットワークトポロジーを共有モードにして位置情報の同期精度を落とす、という方向がよさそうです。
本記事の内容は、自身のゲームの特徴や重視する部分に合わせて参考にしてください。

# 大量の物理オブジェクトのバタつきを抑える

Client Preditionモードで大量の物理オブジェクトを扱ったとき一番問題になったのは、物理オブジェクトが折り重なった時のバタつきです。

![MassPhys_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/7b327dc3-7f02-3f83-18c3-3282a72a406d.gif)

これはClient Preditionでは端末ごとに物理の計算を行いあとから統合するという処理が主な原因となります。

## 前提となる実装の紹介

大量に出すオブジェクトとしてCoinを作成して利用します。

![大量に出す物理オブジェクトcoinの実装例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/1d3e0f1d-7868-bf74-cefd-d68b1ab55afb.png)

- Network Object

Photon Fusionで同期を行うオブジェクトに必須なコンポーネント

- Rigidbody、Network Rigidbody

Photon Fusionで同期を行う物理オブジェクトに必須なコンポーネント

- SmashReactions、CoinData、Health

着弾時のリアクションや色、点数の制御などゲーム的な振る舞いを設定する自作コンポーネント
今回の実装には関係しません

- CustomPhysics

オブジェクトの親子設定を動的に変更するコンポーネント

- Network Transform Anchor

オブジェクトの親子設定をランタイム時に変更する時に必要なコンポーネント


## バタつきを抑える効果のあった項目

### Network Rigidbodyの設定

影響が大きかったのはCoinについているNetwork Rigidbodyの設定で、以下の２つです。

![Network Rigidbodyの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/0c6fc6bd-6679-ce2b-f6d1-f64c359e2706.png)

1つ目はInterpolation Data Sourceで、これは物理挙動の補間をするデータに何を使うかという設定になります。

- SnapShot
    
    同期する位置データを用いて補間を行います。最もバタつきを抑えることができます。
    
- Predicted
    
    予測した値で補間を行います。そのため急な速度変化があった場合、移動がキャンセルされ元の位置に戻る現象が発生します。
    
- no Interpolation
    
    補間を行わない設定です。補間がないため描画が飛び飛びになります。またUnity PhysicsのSleepThresholdオプションが有効になります。
    
2つ目はInterpolate Error Correctionで、これは補間処理での誤差修正機能です。
物理オブジェクトが多い場合はオフにするとでバタつきを抑えることができます。特にInterpolation Data SourceがPredictedの時にオンにすると、オブジェクトが非常に暴れる現象がおきるため、オフにしておく必要があります。
    

## バタつきを抑える効果のなかった項目

### PhysicsMaterialを設定する

これはPhoton FusionではなくUnity Physicsの機能です。PhysicsMaterialのDynamic FrictionとStatic Frictionを1に近くすると滑りにくくなるため、挙動の自然さの向上に寄与します。
ただしバタつきを抑えたり処理負荷軽減の効果は少なめです。

### Project SettingsのSleepThresholdをあげる

これもUnity Physicsの設定で、物体がSleepモード（物理の計算を停止する）をどれくらいの運動量で切り替えるか、という値です。通常、物理オブジェクトのバタつきを抑えたい場合はこの項目を変更（高く）します。
しかしInterpolation Data Sourceがno Interpolation以外の場合、物理エンジンはPhotonがコントロールするためか、この設定は無視されるようです。

### NetworkRigidbody.WriteVelocity

これはPhoton Fusionの機能です。Rigidbody.velocityと同様に物体の速度を書き換えるものと予想しましたが、移動することはありませんでした。特殊な用途に使うもののようです。
Coinが一定以下の速度になった場合に停止させるために使用しましたが、思った効果は得られませんでした。

### WriteNetworkRigidbodyFlags(NetworkRigidbodyBase.NetworkRigidbodyFlags.IsSleeping)

これもPhoton Fusionの機能です。Rigidbody.Sleepと同様に物体のスリープモードを書き換えるものと予想しましたが、効果は確認できませんでした。
こちらも同様に、思った効果は得られませんでした。

# プッシャーを動かしコインと同期する

コインプッシャーといえば前後に動くプッシャー部分です。自動で動き続けるような実装は、速度を与えて動かすか、位置情報を書き換えて動かす方法の2通りがあります。
Photon Fusionにおいては前述の通り速度を与える関数は適当なものがないと思われます。Projectiles AdvancedサンプルのSimpleMoveComponentが後者の方法として実装されているため、今回はそれを利用しました。
しかしこの方法だとコインはプッシャーとの摩擦を無視し上面を滑るような動きになります。これはPhoton Fusion関係なく起きる問題です。

![プッシャーを位置変更で移動させた場合の挙動](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/64e121e8-2c0d-7131-04b3-d7594f551017.gif)

対処方法としては、Colliderで接触判定をとりSetParentでCoinをプッシャーの子にするという方法です。
これによりCoinはプッシャーと同期するようになり、上面に張り付いて動くようになります。またCoinがプッシャーの上面から落ちた場合に親から切り離すことで、張り付きっぱなしにならないようにします。

```CustomPhysics.cs
public override void FixedUpdateNetwork()
{
    if(parentTransform != null)
    {
        if (parentPos.y - transform.position.y > - 0.5f) {
            parentTransform = null;
            transform.parent = null;
        }
    }
}

private void OnCollisionEnter(Collision collision)
{
    if (collision?.gameObject.tag != "Pusher") return;

    if (Object.HasStateAuthority)
    {
        parentTransform = collision.gameObject.GetComponentInParent<Transform>();
        parentPos = parentTransform.position;
        transform.SetParent(parentTransform, true);
    }
}
```

![親子関係の設定を追加](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/73da1c8a-c956-00a3-288b-225e318de68c.gif)

なおPhoton Fusionでは親子関係をランタイム時に変更する場合、どちらかのオブジェクトにNetwork Transform Anchorコンポーネントをつけておく必要があります。

# その他処理を軽くできる項目

その他調整項目として、NetworkProjectConfigのTickRateを下げるというのが挙げられます。これはPhoton Fusionのシミュレーション頻度で、内部的なFPSといったイメージです。
ただし下げるとプッシャーにコインがめり込みやすくなってしまうため、下げるには工夫が必要かもしれません。
また大量のオブジェクトを出すため、コインのポリゴン数を減らしたりテクスチャを簡素化したりするのも有効です。