Photon Fusion for Unityでマルチシーン開発する時のTips

# はじめに

ミニゲームやミニマムなチームでの開発であればシングルシーンでも完成させる事ができると思います。しかしゲームやチームの規模が大きくなるにつれ、読み込み負荷の増加や作業の分担の観点から避けて通れなくなってくるのが、マルチシーン開発でしょう。

シーンの読み込みも例に漏れず、Photon接続時はオンラインマルチゲーム特有の同期や読み込みの問題が出てきます。

[前回の記事](https://qiita.com/nimushiki/items/8bc2c2df7fc055d56cc0)ではシーン読み込み管理の基礎をお伝えしました。

本記事でもサンプルを元に、そこから一歩踏み込んだTipsについて紹介します。

### 前提記事

Photon Fusionの基本的な解説は別記事で行っています。また、シーン管理の基礎について詳しく知りたい方は以下のリンクからご参照下さい。

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

[Photon Fusion for Unityのシーン読み込み管理の基礎](https://qiita.com/nimushiki/items/8bc2c2df7fc055d56cc0)

### 動作確認環境

Windows 10 Home 21H2
Unity 2020.3.27f1
Fusion SDK 1.1.3 F Build 586

# Photon Fusionにおけるマルチシーン開発

## マルチシーンとは

Unity5.3から追加された機能で、複数のシーンを読み込み・編集する機能です。主にUIやフィールドを分割し、読み込み負荷の軽減や作業分担のために活用されます。

## 必須要素

マルチシーンのオンライン対応に必須な要素は一つだけで、それはホスト・サーバーが必要なシーンを事前にキャッシュしておくことです。

あとは必要な時にSceneManager.LoadSceneAsyncなどを実行すればロードすることができます。

しかしロードやアンロードを任意のタイミングで問題なく行うには、様々な実装が必要になります。各々のマシンでの状態に差異が出ないように、シーンの必要性や更新のチェックをする、クライアント・ホストで動作を切り分ける、などです。

公式が公開しているサンプルでは、こういった基本となる実装がなされています。

# サンプルについて

公式のサンプルプロジェクトは以下のページからダウンロードすることができます。

[Fusion シーンの読み込み | Photon Engine ダウンロード](https://doc.photonengine.com/ja-jp/fusion/current/samples/technical-samples/fusion-scene-loading#_jb0qd6m1ewila7l)

使用されているSDKのバージョンは1.1.1ですが、最新版（1.1.3、2022/10/01現在）にアップデートしても問題ありません。

一点、現行のサンプル（ver1.1.1）はマルチピアモードに対応していない点には注意が必要です。

# マルチシーン開発時のTips

## シーン間でデータを持ち越す手法

プレイヤー名やログイン情報など主に個人で取り回したい情報の管理は、通常Unityで行う方法で問題ありません。いくつか紹介します。

なお、どの方法でもそのままでは同期できないため、[Networked]をつけた別の変数を経由する必要があります。

### static変数で保持する

サンプルでも利用されている最も手軽な方法です。しかし影響範囲が大きくなってしまい後々の変更が困難になりがちなので、捨てる前提のコードや極一部のデータなどに使い所を限定するほうがいいでしょう。

```Menu.cs
public class Menu : MonoBehaviour
{
    public static string SessionName;

	//inputFieldの値を受け取る
    public void SetSessionName(string name)
    {
        SessionName = name;
    }
}
```

### 保存用オブジェクトをDontDestroyOnLoadにする

シングルシーンしか扱えなかった時代に長い間定番だった手法です。こちらも分かりやすくお手軽ですが、Unityとしては非推奨で次項の方法を推奨しています。
DontDestroyOnLoadを実行すると対象のゲームオブジェクトが元のシーンから外れ、元シーンをアンロードしても消えなくなります。

```PlayerData.cs
public class PlayerData : MonoBehaviour
{
	[SerializeField] private string playerName;

	void Start()
    {
        DontDestroyOnLoad(gameObject);
    }

    public void SetPlayerName(string name)
    {
        playerName = name;
    }
}
```

### 別シーンに保存用オブジェクトを用意する

こちらがUnity推奨の方法です。画面遷移で破棄されない管理用のシーンを用意、データ保持用のオブジェクト（コンポーネント）を配置し、必要なタイミングでそのデータを更新・取得します。

ゲーム自体は別のシーンを追加で読み込んで動作させます。

- Mainシーン
    - PlayerName用のテキストボックスを用意
    - Menu.csに以下のコードを追加
- PlayerDataシーン
    - PlayerData.csをアタッチしたゲームオブジェクトを用意

遷移先のシーンでplayerNameを使いたい場合は、Menu.csに追加したのと同様の方法でPlayerDataを探し出して取得します。

![サンプル改変後の状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/81cfcaab-4317-a90b-97d3-39cfab8fa4a6.png)

```PlayerData.cs
public class PlayerData : MonoBehaviour
{
	[SerializeField] private string playerName;

    public void SetPlayerName(string name)
    {
        playerName = name;
    }
}
```

```Menu.cs
private PlayerData data;

private void Start()
{
	//PlayerDataシーンからPlayerData.csを取得する処理
    Scene scene = SceneManager.GetSceneByName("PlayerData");
    foreach (var rootGameObject in scene.GetRootGameObjects())
    {
        data = rootGameObject.GetComponent<PlayerData>();
        if (data != null) break;
    }
}

public void SetPlayerName(string playerName)
{
	//inputFieldの変更を反映
    data.SetPlayerName(playerName);
}

```

## 共有モードへの対応

そのまま共有モードにして使うということは出来ません。

共有モードは権限の管理方法がクライアントホスト型・サーバー型とは異なるため、シーンの管理についても考え方が大きく異なります。

- クライアントホスト型・サーバー型
    - クライアントは必要なシーンを要求すればよい
    - ホスト、サーバーはクライアントが読み込むすべてのシーンを読み込む必要がある
- 共有モード
    - 自分が必要なシーンだけ読み込めば良い
    - 自身が読み込んだシーンを相手に知らせる（さらに同期する）必要がある

サンプルを共有モードで動かす方法は以下の通り。エラーはなくなりますがシーンの同期が出来る状態ではないため、さらなる修正が必要です（調査中）

- StartGameArgsのGameModeをSharedにする
- 以下の部分のコードを追記する

```CustomNetworkSceneManager.cs
public override void Spawned() {
    if (Runner.SceneManager()) {
      Runner.Despawn(Object);
      return;
    }
    DontDestroyOnLoad(this);
    if (Object.HasStateAuthority == false) return; //追記
    Runner.SetSceneManager(this);
}
```

## マルチピアモードへの対応

開発元に問い合わせたところ、現段階（2022/10/01）では、サンプルをマルチピアモードで動かすのは難しいようです。Fusion SDKのver1.2以降、より使いやすくアップデートしていく予定とのことでした。

マルチピアモードはプロダクトに組み込むのではなく、検証やテスト、部分的な開発用のモードとして考えたほうが良さそうです。