Photon Fusion for Unityの新機能　描画補間、ラグ補償、同期範囲設定

# はじめに

「Photon」シリーズは、ドイツのExit Games社が開発し、GMOグローバルサイン・ホールディングス株式会社が日本展開を行っているオンラインゲーム開発向けネットワークエンジンです。

Exit Gamesは専用のホスティングサービスも合わせて提供しており、自分でサーバーを用意しなくても簡単にオンライン要素のあるゲームを作り始める事が出来ます。

10年近い実績もあるため、Unity開発者の皆さんは「Photon Unity Networking 2 (PUN2)」としてPhotonを触ったことがあるのではないでしょうか。

本記事では、そんなPhotonの新しい製品である「Photon Fusion」の新機能について紹介していきます。

### 前提記事

本記事は以下の記事の続きとなっています。Fusionの基本的な解説はそちらを参照下さい。

[Photon Fusion for Unityの導入手順とPUN2との機能比較](https://qiita.com/nimushiki/items/d6fe4f0cbe0437677fa8)

### 動作確認環境

Windows 10 Home 21H2
Unity 2020.3.27f1
Fusion SDK 1.0.0 F Build 439

# 機能概要

Photon Fusionでは以下のような機能、特徴が挙げられます。前半の3点については前記事にて解説していますので、そちらを参照下さい。

- ティックベースシミュレーション
- ネットワークトポロジー複数対応
- 物理挙動
- スナップショット補間
- ラグ補償ヒットボックス
- レプリケーションシステム

## スナップショット補間

オンラインマルチプレイ時の遅く不安定な同期に依って速く正確なレンダリングを行おうとする場合、なにかしら調整や補間をする機能の実装が必要になってきます。Photon Fusonのスナップショット補間機能はその調整や補間を行い、安定した高速なレンダリングを可能にします。

よほどの事情がある場合を除き、ゲームのレンダリング周期は高く保ちたい、最低60Hz、動きの激しいゲームであれば90Hz（あるいはそれ以上）にしたい、と考えると思います。

しかし帯域には当然限界があるため、スナップショットの通信周期はそれよりも数分の１程度遅くならざるをえません。

また通信は正確な周期で到達するわけではないため、通信とレンダリングを同期させるとぎこちない動きになってしまいます。

![2022_06セミナー用-Page-4.drawio (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/77282dc6-3cd8-93c9-5228-550ab170337a.png)

ところで前記事にて、Photon Fusionはクライアントやホストから切り離されたティックと呼ばれる単位時間に基づいて進行し、ティックごとに正しい状態を表すスナップショットが存在、それを通信することにより各クライアントを同期すると説明しました。

前述の問題を解決するために、Photon Fusionにはそのスナップショット間でのレンダリングを補間する、スナップショット補間という機能が備わっています。これにより、通信頻度よりも高い周期でレンダリングが可能になり、また用意されたバッファにより多少の通信の乱れの影響を吸収してなめらかなレンダリングを可能にします。

![2022_06セミナー用-Page-4.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/3c38d6a2-43d6-60af-5ddd-6b29f9f48fe7.png)

NetworkProjectConfigファイルでティックレート（秒間いくつのスナップショットを作成するか）を設定できますが、補間自体は自動で動いてくれるため追加でなにか実装や設定をする必要はありません。

![ティックレートの設定方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/58a02738-ec1a-616f-26d6-692bf05b9b40.png)

## ラグ補償ヒットボックス

通信の遅延があるため、ネットワーク上の全マシンのゲーム内時間が同じになることは稀です。ラグ補償ヒットボックスはゲームプレイ体験を損なうことなくその時間差を埋めることが可能です。

例えばFPSなどにおいて、プレイヤーAから見て物陰に隠れる寸前のプレイヤーBを狙撃したとします。リアルタイムで完全に同時刻、プレイヤーBは自身のクライアント上では既に隠れ終わっている状態とします。

![2022-06-17 17.32.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/877a4854-ec19-b22c-11fe-6ac660c2c43d.png)

当たり判定の処理はサーバーかクライアントどちらかが行う必要があります。

先程の説明であった通り、通信の遅延によりマシン同士のゲーム内時間はずれています。（ここではプレイヤーAのクライアントとサーバーの時間）サーバーが判定を行う場合、Aが狙撃したタイミングではない時に判定が行われることになります。つまり、Aが見たままの情報で狙撃するのは困難となります。

![2022-06-17 17.35.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/0b701586-f71c-b618-404f-af75eab2a8af.png)

次にクライアントが判定を行う場合を考えてみます。

当然、見たままの情報で狙撃することが可能です。しかしクライアントが権限を持つということは、チートに弱いという大きなデメリットが存在してしまいます

この状態を解決するのがラグ補償ヒットボックスで、FusionにはそれがHitboxというコンポーネントで実装されています。FusionはこのHiboxの位置情報履歴を持っているため、過去にレイキャストを行うことが可能です。

先の例でいうと、AはBの過去を見ているにも関わらず、Bを狙撃することが出来ます。
Aの判定結果を同期し、Bがその結果を受け取ります。当然Aの射撃は過去の出来事なため、そのままでは正しい判定かわかりません。ここで、ヒットボックスの位置情報履歴を使い、Aが射撃を行った時間で再度判定を行います。

![2022-06-17 17.44.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/e970042d-9c6b-e5bf-8ea2-cad2c71797f0.png)

Bからすれば過去を改変された形になってしまいますが、WYSIWYG（見た目と処理の結果が一致すること）の体験は非常に重要なため、B（見られている方）よりもA（見ている方）を優先する方がメリットが大きいです。

このようにして、必ず存在する通信ラグの影響を抑え、WYSIWYGのゲームプレイ体験を向上させることができます。さらに、結果の検証が行われるため、クライアントに権限があるにも関わらずチートを防ぐことが可能になります。

使い方は簡単で、Unity標準のColliderの代わりにPhotonFusionのHitboxコンポーネントを使い、RaycastはRunner.LagCompensation.Raycastメソッドを利用するだけです。
HitboxRootは入れ子にすることも可能で、装備や乗り物などオブジェクトのアタッチ・デタッチに対応できます。

![2022_06セミナー用-Page-5.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/14a3c208-cdc4-30db-467b-907d9933b8b8.png)

Hitboxは負荷が高いため、HitboxRootの他にも実装の工夫が必要です。

地形や遮蔽物などの動かないが判定は欲しいオブジェクトや、キャラクターが地面に立つなどの物理挙動にはUnity標準のColliderを使います。

HitboxはColliderの内側に設定されることになりますが、ラグ補正ヒットボックス用のレイキャストは通常のColliderにも当たるため、HitboxとColliderはレイヤー分けをします。

![ラグ補正ヒットボックスのレイヤー分け.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/cf4a0578-3a5d-c3bf-3fdb-90337a3a60b7.jpeg)

ラグ補正ヒットボックス用レイキャストは、Hitboxと静的コライダーに当たって欲しいわけですが、動的コライダーには当ってほしくありません。

図のようにUnityにおけるレイヤーを分けることで、実装・動作共に低コストにこの要件を満たすことができます。

実際には、さらにHitboxと静的Colliderも分けてレイヤーを３つにしたほうが運用しやすいと思われます。

設定はNetworkProjectConfigファイルで行えます。精密な判定を行う場合はBufferを増やす、そもそもヒットボックスがいらないタイプであれば無効にする、などの設定ができます。

![ラグ補償の設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/c3d06e41-366c-661e-e64f-2e4491ac3557.png)


## レプリケーションシステム

ゲームの同時参加人数が増えると、その分同期オブジェクトが増え通信帯域を圧迫してしまいます。Fusionではレプリケーションモードとインタレストマネジメントにより、同期するオブジェクトの範囲を指定し通信量を節約することが出来ます。

オンラインゲームには2人でターン制のバトルをするもの、数人で銃を撃ち合うもの、数千人が同時に色々な活動するものと、様々なタイプがあり、そのワールドの広さも同期オブジェクトの数もバラバラです。

前述の通り、Photon Fusionではスナップショットを同期の基本としています。同期オブジェクトが多ければ多いほどそのスナップショットのデータ、つまり通信量が増えてしまいます。

レプリケーションシステムは、そのスナップショットのレプリカ（複製）をどう作るか、またそのレプリカをどうマージ（ゲームプレイに反映）していくか、という一連の処理を指します。

そのうち設定が可能なのは、そのレプリカの作り方についてで、レプリケーションモードの項目で2種類から選択可能です。

- Delta Snapshots（デルタスナップショット）
    - 完全なスナップショットを転送する
    - プレイヤー数が少ない、テンポが早いゲームに向く
        - 格闘ゲーム、チームFPS
- Eventual Consistency（結果整合性）
    - 自分の周辺や特定のオブジェクトに限定したスナップショットを転送する
        - 専用の設定（関心領域など）が追加される
    - 範囲が広い、同期オブジェクトが多い、仕組みが複雑なゲームに向く
        - MMO、サバイバル、バトルロイヤル
    - 各プレイヤーの状態の同期権限の管理方式
        - Server Auth（サーバー型、プレイヤーホスト型の場合）
            - 単一のマシンが行うため、正確
        - Client Auth（共有モードの場合）
            - 各自で行うため、同期時に競合が起きる可能性がある

レプリケーションシステムの設定はNetworkProjectConfigファイル（FusionHubでの表示はNetwork Project Setting）のSimulationセクションのReplicationModeで行えます。

![FusionHubからのコンフィグ表示方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/d0b315a3-cc5b-f7a8-52d0-59c4e6d193ca.png)

![コンフィグファイルの場所](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/e96982de-eaaa-5d07-8ee0-3783db04388a.png)

![レプリケーションシステムの変更方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/669fc53f-c27d-7af3-e636-01bf6d5c7f53.png)

Eventual Consistencyモードにすると以下の変化が起こります。

- Object Interestが出現、オンにするとInterest Management Settingsが有効に
- NetworkObjectコンポーネントにInterest Management Settingsの項目が出現

![出現したObject Interestの表示](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/8953ac49-80f8-427d-78fb-33dccbe3c775.png)

![Interest Management Settingsの出現箇所](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/39ce6a2e-abb2-f892-48fe-7a215ea2af1f.png)

Interest Management SettingsのObject Interestオプションでは、誰が同期（情報の取得）をするか、という設定ができます。一般的に「インタレストマネジメント」と呼ばれており、それぞれ以下のような特徴があります。

- AreaOfInterest
    - AOIが設定されたオブジェクトのその範囲に入った場合に同期
    - プレイヤー（NetworkCharacterController）や大抵のオブジェクト
- AllPlayers
    - 別名　グローバルオブジェクト
    - すべてのプレイヤーが同期
    - レイドボス、GM、NPC
- ExplicitPlayers
    - 別名　カスタムインタレストマネジメント
    - 専用フラグを立てられたプレイヤーのみが同期
    - イベントキャラ、アシスタントキャラ

関心領域の設定をする場合は、プレイヤー制御スクリプトに以下のような記述を追加します。この例では、自分自身の位置から半径32の範囲以内のオブジェクトを同期します。

```sample.cs
protected enum AuthorityType { InputAuthority = 1, StateAuthority = 2 }
[SerializeField]
protected AuthorityType TargetPlayerBy = AuthorityType.InputAuthority;
public float Radius = 32f;

public override void FixedUpdateNetwork() {
  var target = TargetPlayerBy == AuthorityType.InputAuthority ? Object.InputAuthority : Object.StateAuthority;

  if (target) {
    Runner.AddPlayerAreaOfInterest(target, position: transform.position, radius: Radius);
  }
}
```

# まとめ

今回新機能の紹介ということで３点紹介しました。どれもオンラインゲームを作る上で重要となってくる機能であり、かつオンラインゲーム特有の問題を解決してくれます。

特にレプリケーションシステムは複雑な実装が不要であり、気軽に設定変更ができます。作ろうとしているゲームのタイプで決まってくる事が多いですが、仕様変更や開発途中のパフォーマンスチューニングでも気軽に変更して検討ができるということは一つのメリットかと思います。