Photon Fusion for Unityでマルチピアモードを利用したデバッグの始め方

# はじめに

オンラインゲームをデバッグする上で重要度も難易度も高いのが、実際の接続状況を再現したマルチプレイテストだと思います。通常、以下のような方法があります。

- UnityEditorに加えビルドしたものを複数起動
- Parallel SyncというUnityプラグインを利用する
    - 同一プロジェクトを多重起動可能にするもの
- 複数プロセスでUnityEditorを起動

上記いずれの方法であっても、ネットワークの状態をシミュレートできるわけではないため、（通常ありえないほど）遅延のない状態でデバッグすることになります。
Windowsの場合はClumsyというフリーソフトでネットワークの状態を調整することはできます。しかしエディタやビルド物の多重起動が必要となり、時間やマシンパワーが余分に消費されてしまいます。
そこで本記事では、Photon Fusionに備わる、上記問題を解決するマルチピアモード（Multi-Peer Mode）を紹介します。

### 前提記事

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

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

[Photon Fusion for Unityの新機能　描画補間、ラグ補償、同期範囲設定](https://qiita.com/nimushiki/items/c82763817a8c7c4f2278)

### 動作確認環境

Windows 10 Home 21H2
Unity 2020.3.27f1
Fusion SDK 1.1.1 F Build 512

# マルチピアモード（Multi-peer Mode）とは

通常、1つのUnityエディタ内でピア（NetworkRunner、PhotonFusionの機能を管理するもの）は1つに制限されます。
マルチピアモードでは、1つのUnity Editor内に、完全に独立した複数のゲームインスタンス（≒ピア）を作ることができます。これにより、以下の特徴があります。

- ビルドをしたり追加のUnityエディタを開くことなく、複数クライアントのテストが可能
- 複数のゲームセッションに同時に接続可能
- 複数のゲームセッションを提供できる専用サーバーを構築可能

## 基本動作

マルチピアモードは、Unity Editor 上部から、Fusion > NetworkProjectConfig を選択し、Peer Mode を Multiple に設定することで有効にすることができます。
![マルチピアモードの設定方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/94bb3958-1213-884b-59ba-0bc0ea776b1c.png)
あとはゲームの実行中にNetworkRunner.StartGame()を実行（チュートリアルだとHost/Joinボタン）すれば、実行した回数だけ新しいシーン（ピア）が生成されます。以下の画像の例では、1つのシーンが1つのクライアントとして2つ立ち上がっており、P0とP1という名前で表示されています。
![複数のピアが生成されている様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/d6adc3e4-a4b1-3934-f35e-9dbe3040a3c4.png)
NetworkRunner.StartGame()実行時、実行したシーンは削除され、指定されたシーンを生成してPhotonFusion管理下にします。既に管理下の場合、マルチピアモードではシーンが追加生成されますが、シングルピアモードではエラーとなります。

# 実装方法

マルチピアモードはマルチシーンのように動作するため、AudioListenerやEventSystem、MainCamera、Lightなどシーンに重複して存在して欲しくないオブジェクトの実装には工夫が必要です。
主に以下の3点が挙げられます。大まかにいうと、前者2つがそもそも重複させない実装、3つ目が重複したら無効化する実装、となります。

- Singletonパターンを導入
- 別シーンに実装
- RunnerVisibilityNodesコンポーネントを利用

## Singletonパターンを導入

該当コンポーネントにSingletonパターンを適用することで、対象のコンポーネントが重複しないようにします。主に自作のクラスに適用します。

## 別シーンに実装

初めから該当オブジェクトを別シーンで実装しておきます。NetworkRunner.StartGame()ではシーンの削除・生成をしますが、それ以外のシーンについては影響ないため、そちらに該当オブジェクトを実装すれば重複することはありません。
IndividualSceneというシーンに重複させないオブジェクトを実装した状態
![IndividualSceneに実装した状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/2099661e-73ad-6cf7-a2eb-ad4f4a32693b.png)
マルチピア状態になっても該当オブジェクトが重複していない状態
![マルチピア状態でも重複していない様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/7296c714-4eba-0371-5a83-532c05a15cf9.png)

## RunnerVisibilityNodesコンポーネント

これはPhoton Fusionに実装されているコンポーネントです。これに登録されたコンポーネントが重複して存在している場合、一つを除きdisableにする、という機能を持っています。　
無効化したいコンポーネントと同じか親オブジェクトにアタッチし、Componentsに該当のコンポーネントを登録することで、実行時に自動的に有効無効のコントロールをしてくれます。
![2022-08-01 13.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/e5f50722-6c01-33e6-6eff-8e52535743bc.png)
数が多い場合は、対象のオブジェクトを全て子オブジェクトにしておきFind in Nested Childrenボタン押下で一旦全て登録し、不要なものを削除するのがお手軽です。

## 実装上の注意

マルチピアモードを利用する場合、全てのNetworkRunnerは互いに完全に独立している必要があります。シミュレーションに影響を与えるものを静的変数やSingletonにしないことが強く推奨されています。

また、NetworkRunnerは1プレイヤーにつき1つ必要になる点についても注意が必要です。チュートリアルでいうBasicSpawnerに上記の実装パターンを用いてしまうと、NetworkRunnerが1つしか存在できないため、エラーとなってしまいます。
どういうことかというと、BasicSpawnerではAddComponent<NetworkRunner>()→NetworkRunner.StartGame()という動作をします。1人目は問題なく接続ができますが、2人目が同じBasicSpawnerオブジェクトでAddComponent<NetworkRunner>()しようとした時、既にNetworkRunnerがあるためエラーとなってしまいます。
そのため、例えば、複製されるシーンにBasicSpawnerを含めて接続処理はそこで行う、といった実装が必要です。

# デバッグ方法

準備として、前述の例に倣いマルチピア状態にしておきます。

## Runner Visibility Controls

Fusion > Windows > Runner Visibility Controls　でコントロールウィンドウを開く事ができます。
![Runner Visibility Controlsの表示方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/9eccc27d-e1d7-a084-a2a8-369f46cae7f2.png)
公式よりRunner Visibility Controlsの表示
![multipeer-runner-visibility-controls.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/a6a55be6-9573-7929-4834-ef5d6759926a.png)
Runner Visibility Controlsウィンドウの各部をクリックすることで以下の機能が利用できます。

- Runner
    - 該当のNetworkRunnerを選択する
- PlayerRef
    - 該当のNetworkRunnerに登録されたLocalPlayerObjectを選択する
- isVisible
    - ピア管理下のオブジェクト（≒シーン）の表示非表示を切り替える
- isProvidingInputs
    - プレイヤーの入力（NetworkInputData）を有効化無効化する
- AddFusionStatus Left/Right
    - 画面左/右にFusionのステータス画面を表示する
    - パケットサイズや補間、予測の状態がリアルタイムで表示

## Network Conditions
まず最初にFusion > Toggle Debug Dllsでデバッグ機能を有効化します。図のようにActivated Fusion Debug dllsと出ればOKです。
![Debug Dllsを有効化した様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/45fe45da-caed-651e-3a5e-2dccce5951c6.png)
するとFusion > Network Project Config > Network Conditionsの項目が現れるので、Enabledにチェックを入れると有効化されます。この項目を調整して、遅延やパケットロストなどのテストが可能です。
![Network Conditions](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/7fdc0b55-af0a-8a40-4b30-f14cbf8ffe49.png)

# その他

## 複数画面表示

マルチピアモードは１つのエディタで実行する都合上、そのままでは画面が1つしか表示できません。
ローカルマルチプレイゲームのような画面分割をすれば複数画面を使ってデバッグができますが、いま把握出来ている範囲ではカメラ毎に表示をコントロールすることは出来ません。

お互いAOI範囲外なので見えていないはずが、どちらのカメラも同じ表示になっている様子
![画面分割](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/b518819c-af6f-db73-1de0-d0a25119b0a5.png)
一応、すべてのオブジェクトのLayerを変更しカメラのCullingMaskで表示をコントロールすれば、各プレイヤーが見ることになる表示とカメラの一対一対応表示が可能になります。
全てのオブジェクトのLayerをplayer1、player2に変更し、CullingMaskで表示コントロールした例
![CullingMaskでの表示コントロールの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/c9531369-7fab-24c6-7fa1-0ab3c30642fa.png)
しかし本来の実装への影響も少なくないため、複数画面を同時に確認したい場合は別の手段を取るほうが無難かもしれません。