概要
Zenject は言わずと知れた有名なDIフレームワークです。Zenjectの ProjectContext を使ってタイトルに書いてあることを実現したので、そのときのメモです。
GitHub - modesttree/Zenject: Dependency Injection Framework for Unity3D
ProjectContext とは
Zenjectでは、シーンごとに適した依存関係を SceneContext として定義するのが普通です。一方で、すべてのシーンに共通して、永続的な依存関係を定義したい場合があります。それらの依存関係は ProjectContext として定義します。
SceneContextの存在するシーンが読み込まれた場合に、ProjectContextのInstallerが先に実行され、依存関係がバインディングされます。そのため、ProjectContextを作るだけではダメで、SceneContextが存在しないと依存関係はバインディングされません。
そして、ProjectContextのゲームオブジェクトは、DontDestroyOnLoad なゲームオブジェクトとしてシーンに存在することになります。これも重要な点です。
ProjectContextは、ProjectContextのPrefabをResourcesフォルダ直下に配置しておくことで自動的に読み込まれます。
Installer とは
Installerとは、ある依存関係のまとまりを定義したクラスのことです。Installerとして定義しておくことで、依存関係をグルーピングでき、再利用もしやすくなりします。Installerには以下のような種類があります。ScriptableObjectInstallerというものもありますが、ここでは割愛します。
種類 | 内容 |
---|---|
MonoInstaller | MonoBehaviourとしてのInstaller GameObjectにアタッチするので、inspectorで値を設定できる |
Installer | MonoBehaivourとしての振る舞いが必要ない場合に用いるInstaller |
Installer、もしくは、MonoInstallerを継承したクラスを作成し、InstallBindings() をオーバーライドすることで実装できます。
public class TestInstaller : MonoInstaller { public override void InstallBindings() { // Binding } }
実装したInstallerは、Contextの対応するフィールドにアタッチすることで InstallBindings が自動的に実行され、バインディングされます。
ZenjectのProjectContextでDI
ProjectContextに必要なものをまとめると、以下になります。
- ProjectContext をPrefabとして作成する
- Edit → Zenject → Create Project Context でResources直下に生成されます。
- Installer を実装し、ProjectContextに設定する
- 対象のSceneに、SceneContext を配置しておく
- ヒエラルキー上で右クリック、Zenject → Scene Context で作成できます
どう使ったのか
よくある「シングルトン、かつ、シーン切り替え時にも破棄されないゲームオブジェクト」を生成するために Zenject を使用しました。
前者は、Injectされるインスタンスをシングルトンとして扱う AsSingle() で、後者は、ProjectContextとしてバインディングすることで実現できます。以下に具体例を示します。
具体例
ゴール
Scene遷移関連の操作をする「SceneManager」をすべてのSceneで使えるようにすることがゴールです。Zenject導入前は、別のエントリ に記載されているように、Singleton かつ DontDestroyOnLoad になるように自身で「SceneManager」を実装していました。Zenjectを使えば、それらを自身で実装せずに同じことを実現できます。
InstallerとCustomSceneManagerを実装する
using UnityEngine; using Zenject; public class CommonManagerInstaller : MonoInstaller { // あらかじめPrefabを作成しておいてアタッチする [SerializeField] private CustomSceneManager customSceneManagerPrefab; public override void InstallBindings() { // FromComponentInNewPrefab() で、GameObjectを生成する // AsSingle() として、同じインスタンスが再利用されるようにする Container.Bind<CustomSceneManager>().FromComponentInNewPrefab(customSceneManagerPrefab).AsSingle(); } }
// フェードイン・フェードアウトを伴うシーン遷移を実現するクラス // MonoBehaviourを継承して、通常のGameObjectとして作成するだけで良い public class CustomSceneManager : MonoBehaviour { // 詳細な処理は別エントリを参照 }
Installer を ProjectContext に設定する
作成した CommonManagerInstaller を Prefabにしておきます。また、ProjectContextの「Prefab Installers」の項目にCommonManagerInstallerのPrefabをアタッチしておきます。
最終的に以下のような形になっていれば、シーン読み込み時に CommonManagerInstallers の依存関係がバインディングされます。
デバッグ実行してヒエラルキーを確認すると、以下のように DontDestoryOnLoad なGameObjectが生成されていることが確認できると思います。
Injectする
あとは必要に応じてInjectするだけです。
[Inject]
private CustomSceneManager customSceneManager
まとめ
以上、ProjectContextを使って、Singleton かつ DontDestroyOnLoad な GameObject を生成したときのメモでした。
ほとんどのシーンで使うことが確実、かつ、GameObjectが破棄されると困る場合であれば、今回の例のようにProjectContextを使うと良いと思います。CommonManagerInstallerに、CustomSceneManagerと同じようにバインディングすることで、任意のGameObjectをDIできます。一方で、特定のシーンでしか使わないようなものであれば、SceneContextにしておいたほうがよいでしょう。不必要なバインディングは混乱の元となってしまうと思います。