Photon Fusion for Unityでネットワークトポロジーを複数採用する

# はじめに

オンラインゲームの開発では、クライアントやサーバーがどのようなネットワークを構成するか、ということを決めて実装する必要があります。これを「ネットワークトポロジー」といいます。サーバーが介在するもの、プレイヤー同士だけで完結するものもあります。

ネットワークトポロジー毎に向いているゲームタイプや、掛かるコスト、実装方法など様々な差異があるため、オンラインマルチプレイゲームの設計をするにあたってその選定は非常に重要です。

これに関して「[ソーシャルハブ](https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-social-hub)」という公式サンプルが公開されています。これは同一プロジェクト内でネットワークトポロジーを複数使い分けるというものです。

https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-social-hub

そこで本記事ではネットワークトポロジー毎の基本的な特徴を紹介しつつ、公式サンプルを用いて複数のトポロジーを併用する方法について紹介します。

### 前提記事

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

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

### 動作確認環境

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

# Photon Fusionで扱えるネットワークトポロジー

Photon Fusionでは、以下4種類のネットワークトポロジーから設定できます。

## サーバーモード

オンラインゲームでよくある形態で、サーバー上で専用プログラムが常時動いており、クライアント（＝プレイヤー）がそこへ接続する形です。

サーバーがシミュレーションの権限を持つためチートに強く、接続の安定性も高いですがサーバーの運用コストが掛かるのが最大のネックとなります。

## ホストモード

プレイヤーの一人がサーバーも担うような接続方法です。

https://doc.photonengine.com/ja-jp/fusion/current/tutorials/host-mode-basics/overview

サーバーコストも掛からずPhoton Fusionの様々な機能も使えますが、ホストがシミュレーションの権限を持つためチート行為に弱くなってしまいます。

また、ホストが抜けたときにゲームを継続する仕組みであるホストマイグレーションが必要になると、実装の難易度がやや上がります。

https://zenn.dev/o8que/articles/685eeab6473214

## 共有モード

接続する全てのプレイヤーが同程度の権限を持って接続をする方法です。

https://doc.photonengine.com/ja-jp/fusion/current/tutorials/shared-mode-basics/overview

オブジェクト操作の権限が分散しているため、最後の一人が抜けるまでルームの維持が可能です。

ラグ補償ヒットボックスなどいくつかの機能が使えませんが、学習コストや実装難易度が比較的低くなっています。

なお、Photon Fusionの前身であるPUN2とほぼ同様の動作となっています。

## シングルモード

一切通信を行わないオフラインモードです。

通信するかどうか以外の実装や動作などについては、ホストモードのホストと同じになります。つまりホストモードとして実装をした後、後述する方法で設定を変更すればそのまま問題なく動作します。

## ネットワークトポロジーの選択方法

ネットワークトポロジーを選択するには、Photon Cloudへ接続する際のオプションとしてコード内で指定する必要があります。

まず初期化処理オプションを設定するためのStartGameArgsのインスタンスを作成します。その中のGameModeメンバ変数でネットワークトポロジーを設定します。

その後Photon Cloudとの接続用メソッドであるStartGameを実行すれば、設定したネットワークトポロジーでの接続が実行されます。

```Sample.cs
await _runner.StartGame(new StartGameArgs()
{
	GameMode = mode,
	SessionName = "TestRoom",
	Scene = SceneManager.GetActiveScene().buildIndex,
	SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
```

ネットワークトポロジーはこの変数だけで設定できるため簡単に変更が可能ですが、それぞれ実装の指針に大きな差異があります。そのため開発の初期段階で決めておかないと、大きな手戻りが発生する可能性があることに注意が必要です。

その差異については「Photon Fusion for UnityのWebGLビルドを1週間ゲームジャムで試して得た知見」の記事で少し解説していますが、詳しくは今後の記事で解説する予定です。

https://qiita.com/nimushiki/items/bf5670762624cb4c8ec3#%E5%85%B1%E6%9C%89%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

# ネットワークトポロジーの選定基準

|  | サーバー | ホスト | 共有 |
| --- | --- | --- | --- |
| コスト | ☓ | ○ | ○ |
| ルーム人数 | ○ | △ | △ |
| ルームの維持 | ○ | △ | ○ |
| チート耐性 | ○ | △ | △ |
| Fusionの機能 | ○ | ○ | △ |
| 通信安定性 | ○ | △ | ○ |

- コスト
サーバーの運用コスト（支払い）が掛けられるかが焦点になります。

- ルーム人数
同じルームで同時に遊ぶ人数です。ホストモードや共有モードでMMOやバトルロイヤルなどの多人数参加型のゲームを作るのは現実的ではありません。

- ルームの維持
主に少人数のマッチにおけるルーム維持のしやすさです。

- チート耐性
サーバーモードなら安心というわけではないですが、プレイヤーに権限がないため堅牢です。
Photon Fusionとは関係ない部分として、課金や育成、対戦、ランキングなどの要素がある場合はチートを行う動機も強くなります。

- Photon Fusion特有の機能を使う必要があるか
ラグ補償（Lag Compensation）や、クライアントサイド予測（Client-Side Prediction）とサーバーリコンシリエーション（Server Reconciliation）によるロールバック・再シミュレーションが必要かどうかです。共有モードでは使えないという場合がほとんどです。

- 通信安定性
ホストモードではプレイヤーの環境に大きく依存します。

# ネットワークトポロジーを複数利用する

前述の通り、ネットワークトポロジーの切り替えは接続パラメータを変更するだけでできます。そのため、1つのゲーム内において複数のネットワークトポロジーを採用することも容易にできます。

ただし接続後の実装については差があるため、ロビー機能だけ共有モードを使うなど、ゲームモード毎に最適なものを選択する方がよいでしょう。同一ゲームモードを複数のトポロジーに対応させるのは実装難易度が上がってしまいます。

## 公式サンプルについて

ソーシャルハブはネットワークトポロジーを複数利用するための実装サンプルになっています。

https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-social-hub

- 操作
左クリック　攻撃（木箱を破壊する、ダンジョン内のみ）
Eキー　インタラクト（NPCに話しかける）
WASDキー　移動

サンプルは、ロビーに集まり数人のPTでインスタンスダンジョンに行くMOのような作りになっています。

ゲームを起動すると、プレイヤーはまずロビーに接続します。他のプレイヤーをロビー上部の出口で待ち受け、任意のタイミングでダンジョンへ出発します。

## 公式サンプルのネットワークトポロジーについて

ロビーはプレイヤーの出入りが多く、メインのゲーム部分に比べ高度な同期が不要なことから、ネットワークトポロジーは共有モードが採用されています。

ダンジョンはプレイヤーが出発時点でほぼ固定され、アクションや射撃などの高度な同期が必要になります。そのためネットワークトポロジーにはクライアントホストが採用されています。

なお、サンプルはシングルピアモード前提で実装されており、マルチピアモードに対応させるには工夫が必要です。

## ネットワークトポロジーを切り替える処理のポイント

サンプルでは主にConnectionManager.csで切り替えを実行しています。

- ConnectToRunnerメソッド
新しいRunnerを生成　connection.Runner = child.AddComponent<NetworkRunner>();
StartGameで接続処理　var startResult = await connection.Runner.StartGame

- LoadDungeonLevel
shutdownで使わない方のRunnnerをDispose（破棄）
SetActiveSceneで次のシーンへ切り替え

ポイントはNetworkRunnerを複数同時に生成し接続処理をすることができるところです。これにより現在の接続を維持したまま次の接続の準備ができます。

ただし単一プレイヤーだとしても、複数の接続を行うとその分CCUとして計上されてしまいます。Photon Fusionの利用料金にも掛かってくるため、Dispose処理を忘れないように注意する必要があります。