Unity Fusion Golfの要約

# はじめに
3DゲームはKarts、Tanknarok、Projectilesときて次はGolfが提供されました。

https://doc.photonengine.com/en-us/fusion/current/samples/game-samples/fusion-golf

|環境|バージョン|
|--|--|
|Fusion Golf|1.1.2 Build 3|
|Fusion Golf Unity|2020.3.35f1|
|筆者のUnity|2021.3.8f1|

ざっくり中身を見ておこうと思います。
目新しい機能は大きく分けて3つほどでしょうか。

---

### 1.状態遷移
ネットワーク共有される状態遷移が設計されています。

### 2.ステージ進行
インゲーム部分はKartsやProjectilesよりもシンプルですが、ステージ(レベル)の切替が実装されています。Levelクラスが付いたプレファブをロード、アンロードする形です。

### 3.マッチメイキング
マッチメイキング機能があります。これでセッション名を入力する必要が無くなり、一覧から選択できるようになります。

---
以下、アウトゲームを構成する上での土台となるクラスを要約します。
インゲームは開発者の好きにやる部分なので割愛気味です。

クラスごとに重要度を３段階で付けています。個人的主観です。今回のサンプルの仕組みを理解する上で知っておくべきと感じた度合いを基準にしています。

# イベントトリガー

* InterfaceManager

|オブジェクト名|イベント名|ターゲット|
|--|--|--|
|Options Button|m_OnClick|In Game UI Screen (UIScreen)Focus|
|Play Button|m_OnClick|Main Menu Screen (UIScreen)Focus|
|Options Button|m_OnClick|Main Menu Screen (UIScreen)Focus|
|Host Button|m_OnClick|Mode Selection Screen (UIScreen)Focus|
|Join Button|m_OnClick|Mode Selection Screen (UIScreen)FocusScreen|
|||InterfaceManager (Golf.Matchmaker)TryJoinLobby|
|Back Button|m_OnClick|Mode Selection Screen (UIScreen)Back|
|Back Button|m_OnClick|Host Screen (UIScreen)Back|
|Confirm Button|m_OnClick|Host Screen (Golf.SessionSetup)TryCreateSession|
|Back Button|m_OnClick|InterfaceManager (Golf.Matchmaker)CloseLobby|
|Confirm Button|m_OnClick|InterfaceManager (Golf.Matchmaker)TryJoinSessionUI|
|Slider|EndDrag|Session Screen (Golf.SessionScreenUI)ApplyColorChange|
||PointerClick|Session Screen (Golf.SessionScreenUI)ApplyColorChange|
|Slider|EndDrag|Session Screen (Golf.SessionScreenUI)ApplyColorChange|
||PointerClick|Session Screen (Golf.SessionScreenUI)ApplyColorChange|
|Slider|EndDrag|Session Screen (Golf.SessionScreenUI)ApplyColorChange|
||PointerClick|Session Screen (Golf.SessionScreenUI)ApplyColorChange|
|Start Button|m_OnClick|Session Screen (Golf.SessionScreenUI)StartGame|
|MoveTo Button|m_OnClick|Session Screen (Golf.SessionScreenUI)ToggleSpectate|
|Leave Button|m_OnClick|Session Screen (Golf.SessionScreenUI)Leave|
|Audio Button|m_OnClick|Options Menu Screen (Golf.OptionScreen)SetActivePanel|
|Graphics Button|m_OnClick|Options Menu Screen (Golf.OptionScreen)SetActivePanel|
|Controls Button|m_OnClick|Options Menu Screen (Golf.OptionScreen)SetActivePanel|
|Back Button|m_OnClick|Options Menu Screen (UIScreen)Back|
|Resume Button|m_OnClick|Pause Screen (Golf.PauseMenuUI)Resume|
|Options Button|m_OnClick|Pause Screen (UIScreen)FocusScreen|
|Leave Button|m_OnClick|Pause Screen (Golf.PauseMenuUI)Leave|

# ResourcesManagerクラス
重要★★☆
プレファブなどのリソースの格納庫です。
常駐シングルトンです。

往々にしてFusionのサンプルには常駐(DontDestroyOnLoad)シングルトンが多い印象です。サンプルで学習すべき視点を絞る上での適切な使い方をしているように見えます。

* Putter playerControllerPrefab
キャラクター用プレファブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/7f98a7f7-d88d-ea10-340b-4acc2fb38ef4.png)

* PlayerScoreboardUI playerScoreUI
スコアボードレイアウトプレファブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/1ba8c921-70f8-d981-9428-b5ecde26ffa1.png)

* ScoreItem scoreItem
スコアボード要素プレファブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/c2d5bdf3-645d-d055-e980-aaf18c0f754f.png)

* PlayerSessionItemUI playerSessionItemUI
プレイヤーセッション要素プレファブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/ac4bd25c-5879-2bc9-035a-5ede3f5206c0.png)

* WorldNickname worldNicknamePrefab
自分以外のキャラクターの上でに出すニックネームプレファブ

* GameObject splashEffect
水に落ちたときのエフェクトプレファブ

# Matchmakerクラス
重要★★★
Menuシーンにあります。
マッチメイキングを担当します。
InterfaceManagerにより、こちらも常駐シングルトンになっています。

* NetworkRunnerプレファブ
* NetworkManagerプレファブ
* SessionItemUIプレファブ(セッション一覧のUI要素)
* セッションアイテムの親
* ロビー関連のUnityEvent

NetworkRunnerの生成および破棄、セッションの作成と参加を処理しています。

INetworkRunnerCallbacksからセッションリスト更新をOnSessionListUpdated()に受け取り、UI要素を生成します。

やっていること自体はPUNとあまり違いが無さそうです。

# PlayerObjectクラス
重要★★☆
セッション中に存在するプレイヤー１人分の情報を持つオブジェクトです。

* ニックネーム
* カラー
* コントローラへの参照
* ローカルプレイヤーへの参照

```
public static PlayerObject Local { get; private set; }
```

# PlayerRegistryクラス
重要★★★
生成されたPlayerObjectを受け取って保持し、アクセスを提供します。
常駐シングルトンです。

* プレイヤーの最大数
* プレイヤーの数
* 観戦者の数
* 観戦者とプレイヤーを区分けしたPlayerObjectの一覧
* UIクラスへ提供するためのネットワークコールバック

# InterfaceManagerクラス
重要★★☆
Menuシーンにあります。
ゲーム全体のUIへのアクセスを提供する常駐シングルトンです。

* メインメニュー画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/820fc8ed-6954-cb14-1c7e-bb2d4de616f9.png)

* インゲーム画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/17c71723-fca7-0b0b-c2d4-2ff78f2bc885.png)

* オプション画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/f19e9dd8-2c1f-a5ff-207a-c3ccf572cda4.png)

* ホスト設定画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/3c5a27f1-557d-c415-5499-d128c2645fec.png)

* クライアント設定画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/4319a8b9-9d26-b970-29ab-323eec3ca384.png)
ソースコードからではなく"Join Button"のEventTriggerで表示されます。

* セッション画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/703134e5-3443-00e0-4453-a2202a26de66.png)
この時点でセッションは作成済みです。

* ステージリザルト画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/1ba8c921-70f8-d981-9428-b5ecde26ffa1.png)

* パフォーマンス（個人のスコア）画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/e6c6eba0-7cef-e5e5-9b42-af4fbdb61241.png)

* ポーズ画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/66b9fef4-9f5b-8e26-5f93-506e1faa9b98.png)

* ゲームリザルト画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/022933a3-fe16-1a87-220c-38d14e6c0c5b.png)

* ニックネーム用ワールドキャンバス
* 画面表示切替用の参照(UIScreen)

その他、ゲーム開始カウントダウンアニメーションとショートカットキーからの画面制御も行っています。

# GameStateクラス
重要★★★
シングルトンです。
PlayerRegistryにより常駐します。
ネットワーク上でゲームの状態を管理し、プレイヤー間で共有します。

* 現在のステート
* 直前のステート
* 遅延時間
* 遅延用ステート

状態を変更するにはServer_SetState()とServer_DelaySetState()を使います。
後者を使うと指定秒数遅延(ゲーム終了後に自動でロビーに戻す)します。
以下のステートを切替えながら同期を取っています。

|ステート名|設定タイミング|
|---|---|
|Off|初期状態|
|Pregame|スポーンされた直後|
|Loading|ゲームスタート時|
||Outro遷移時に全ステージを完了していないとき|
|Intro|カウントダウン中|
|Game|Intro開始後（遅延）|
|Outro|全員がゴールしたとき|
|Postgame|Outro遷移時に全ステージを完了していたとき|

Postgameに入ったときの処理例
```
StateMachine[EGameState.Postgame].onEnter = prev =>
{
	Level.Unload();
	InterfaceManager.Instance.postgameUI.SetWinner(PlayerRegistry.OrderDesc(p => p.TotalScore).First());
	UIScreen.Focus(InterfaceManager.Instance.postgameUI.screen);
	Server_DelaySetState(EGameState.Pregame, 5);
};
```

Pregameに入ったときの処理例
```
StateMachine[EGameState.Pregame].onEnter = prev =>
{
	if (prev == EGameState.Postgame)
	{
		Runner.SetActiveScene("Menu");
		if (!Runner.SessionInfo.IsOpen) Runner.SessionInfo.IsOpen = true;
		UIScreen.activeScreen.BackTo(InterfaceManager.Instance.sessionScreen.screen);
	}
};
```

# GameManagerクラス
重要★★★
シングルトンです。
PlayerRegistryにより常駐します。
重要ではありますが、やっていることはほとんどありません。

* GameState _gameState：GameStateクラスの参照
* int CourseLengthIndex：コースの数
* int MaxTimeIndex：制限時間
* int MaxStrokesIndex：最大ショット数
* bool DoCollisions：他プレイヤーと衝突させるか

### static PlayerDNF(PlayerObject player)
最大ショット回数によりプレイヤーをリタイアさせ、ステージ終了判定を行います。

### static CalculateScores()
全プレイヤーのスコアを計算し、PlayerObjectに格納します。

### Rpc_LoadDone()
PlayerObjectのIsLoadedフラグをオンにします。
PlayerObejctが単純にスポーンされるとこのメソッドを呼びます。
全員がオンになると、GameStateクラスがステートをIntroに切り替えます。

Rpc属性に渡されている[HostMode = RpcHostMode.SourceIsHostPlayer]がポイントで、
これが無いとサーバモードの挙動として扱われ、ホストのPlayerRefが入らずNoneになります。
PUNには無かった概念ですが、明示的に指定する必要があります。

# Levelクラス
重要★★☆
継承：NetworkBehaviour

１レベル(ステージ)の構成です。
地形メッシュの他、Levelプレファブ自体をロードする役割を持ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703909/c1decd51-d82a-e33c-7069-0006a160a35f.png)

* static Current:現在のLevel
* プレイヤーを出現させる高さ

### static Load(Level level)
このクラスそのものが引数として与えられたレベルプレファブをSpawnします。
呼び出しタイミングは以下です。
* ゲームシーンがロードされたとき
* OutroステートからLoadingに遷移したとき

### static Unload()
現在ロードされているLevelを破棄します。
Load()の前に行います。

### Spawned()
Currentに自身を格納します。
GameManagerへロード完了を通知します。

### Vector3 GetSpawnPosition(int index)
プレイヤーの生成位置を計算します。

# PlayerSpawnerPrototypeクラス
重要★☆☆
PlayerObjectを自動生成するユーティリティクラスです。

# NetworkSceneManagerDefaultクラス
重要★☆☆
シーン遷移を行うユーティリティクラスです。
ピアの種類別の処理や、ネットワークオブジェクトのシーン移動をよしなにやってくます。
このサンプルはユーティリティで事足りるのに、シーン１つで完結しているProjectilesの方は独自実装しているのが謎なので、後日詳しく見てみます。