ZenjectSceneLoaderでシーン間のパラメータ渡しをシンプルに実現する


## はじめに

この記事ではゲームエンジン Unityにおいて、シーンを遷移させる時にパラメータを渡す方法として、 [ZenjectSceneLoader](https://github.com/svermeulen/Zenject/blob/master/UnityProject/Assets/Plugins/Zenject/Source/Util/ZenjectSceneLoader.cs) を利用した方法を紹介していきます。

本記事ではZenject ver 7.3.0をターゲットバージョンとしています。（6.x 以下での動作はわかりません）

### この記事を３行にまとめると…

* ZenjectSceneLoaderを使うと現在のシーン上から遷移後のSceneContextに任意のパラメータをBindできる
* 遷移後のシーンで稼働するBehaviour内でパラメータをInjectして利用する
* ProjectContextやDecoratorContextを使わなくてもシーン遷移時パラメータ渡しが出来る！

## ZenjectSceneLoaderとは

ZenjectSceneLoaderの主な機能（シーン読み込み時の追加的なBindやSceneContextの親子関係の制御）については
[ZenjectSceneLoader](https://github.com/svermeulen/Zenject/blob/master/UnityProject/Assets/Plugins/Zenject/Source/Util/ZenjectSceneLoader.cs)のLoadSceneのオーバーロードメソッドの**引数に注目して**読むことで見えてくるかと思います。

```csharp

public AsyncOperation LoadSceneAsync(string sceneName)

public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadMode)

public AsyncOperation LoadSceneAsync(
    string sceneName, LoadSceneMode loadMode, Action<DiContainer> extraBindings)

public AsyncOperation LoadSceneAsync(
    string sceneName,
    LoadSceneMode loadMode,
    Action<DiContainer> extraBindings,
    LoadSceneRelationship containerMode)

public AsyncOperation LoadSceneAsync(
    string sceneName,
    LoadSceneMode loadMode,
    Action<DiContainer> extraBindings,
    LoadSceneRelationship containerMode,
    Action<DiContainer> extraBindingsLate)
```

UnityスタンダードのSceneLoader.LoadSceneには見られない引数として…

* extraBindings
* containerMode
* extraBindingsLate

の３つがあります。

１つ目と３つ目に出てきた extraBindingsとextraBindingsLateは遷移後のシーンに置かれたSceneContextのInstallメソッド内で呼び出される、追加のBindingを設定できるActionです。

extraBindingsのAction内からDiContainer.BindInstance等でパラメータを設定するようにします。例えばゲームリザルトの情報などをここで設定すれば、遷移先シーンのライフタイムに限定した状態でパラメータを渡す形が作れるわけです。

ただし、**遷移後のシーンにSceneContextが存在しない場合は何も起きません。**注意しましょう。

> SceneContextがextraBindingsを解決するまでの内部的な挙動について

> まずLoadSceneが呼び出された直後のシーン遷移前にSceneContextのstatic変数として定義されたExtraBindingsInstallMethod へとextraBindingsを配置する下準備が行なわれます。
> 次にシーン遷移後に移って、遷移後のシーンに配置されたSceneContextがAwakeされるタイミングでInstallメソッドが呼ばれます。
> そのInstallメソッド内でExtraBindingsInstallMethodが遷移後のシーンのSceneContext上で呼び出される、という流れになっています。

> さらに詳しくは [ZenjectSceneLoader](https://github.com/svermeulen/Zenject/blob/master/UnityProject/Assets/Plugins/Zenject/Source/Util/ZenjectSceneLoader.cs) やZenjectリポジトリ内からExtraBindingsInstallMethodで検索してみてください。


また、LoadSceneで見慣れない引数として LoadSceneRelationship もありますね。こちらはそのソースに書かれたコメントがそのまま説明になるかと思います。

**LoadSceneRelationship.None**を指定した場合はProjectContextを親コンテナに配置します。（default値です）

**LoadSceneRelationship.Child** を指定した場合には現在のシーンのSceneContextの子として配置できます。（ただ、この場合は LoadSceneMode.Additive を指定する必要がある？ ）

**LoadSceneRelationship.Sibling**は遷移前のシーンの親を次のシーンの親とする設定です。多くの場合はNoneとほぼ同義、とのことです。


最後に、extraBindingsLateについてです。LateとあるようにSceneContextのインスペクター経由で設定できるInstallers等を実行した後に実行されます。

実用例が思い浮かばないですが、インスペクター経由でSceneContextに設定できるInstallersなどが解決された後に実行したいBindがあればextraBindingsLateを利用するとよさそうです。

> BindやInstallerの解決順については [SceneContext](https://github.com/svermeulen/Zenject/blob/0ea9690c9ec8ec8edeff7a65a202f7220ace839d/UnityProject/Assets/Plugins/Zenject/Source/Install/Contexts/SceneContext.cs) のInstallBindings メソッドを確認してみてください。

さて、次に具体的なコードで説明してみたいと思います。
以下は、ZenjectSceneLoader.LoadSceneAsync の extraBindings引数 を使ってパラメータを一個だけに限定してDiContainerにBindを仕掛けるコードの例です。


```csharp

public class MySceneManager : IMySceneManager
{
    [Inject]
    ZenjectSceneLoader sceneLoader;

    public void TransitionScene<T>(string targetSceneName, T sceneParameter)
    {
        Debug.Log("Load scene: " + targetSceneName);
        
        sceneLoader.LoadScene("Scenes/" + targetSceneName, LoadSceneMode.Single, 
            (diContainer) => 
            {
                if (sceneParameter != null)
                {
                    diContainer.BindInstance(sceneParameter).AsTransient();
                }
            });
        
        Debug.Log("Load scene done");
    }

```

TransitionSceneメソッドのsceneParameterで指定した一つの値をextraBindingsのActionからDiContainerにBindしています。containerModeは指定していないのでNoneが設定される、ということになりますね。

上記のシーン間パラメータとして宣言しているテンプレート型 T は、非同期で使うことも考えるとイベントで扱うのと同様、構造体を使うなどハードコピーされるようにしたほうがいい…？

ちなみにMySceneManager といったアプリ全体で利用するものはProjectContextで扱うとよさそうですね。

## 参考

https://github.com/svermeulen/Zenject