VContainerで動的に生成したGameObjectにDIする方法についてまとめました。
- はじめに
- パターン1: Enemyを生成するクラス(Spawner)にDIする場合
- パターン2: Spawnerを介しつつEnemyにDIしたい場合
- パターン3: ファクトリメソッドをDIする場合
- 参考
VContainer 1.15.3
はじめに
本記事では VContainer を使って動的に生成したGameObjectにDIする方法についてまとめます。
例として、ゲーム中に動的に多数の敵が生成され、それぞれの敵は自身が行動したイベントを他に伝えるためのイベントバスを持つものとします。
まずイベントバス以下のように定義します(説明用なのでログを出力するだけ)。
using UnityEngine; public sealed class EventBus { public void Publish<T>(T message) { Debug.Log($"Publish: {message}"); } }
敵のクラスは以下の通りです。
このMonoBehaviourをアタッチしたPrefabを動的に生成し、IEventBusをDIするケースを考えます。
using UnityEngine; public sealed class Enemy : MonoBehaviour { private EventBus _eventBus; public void Initialize(EventBus eventBus) { _eventBus = eventBus; Debug.Log($"Enemy.Initialize: {eventBus}"); } }
なおVContainerの基本的な使い方については以下の記事にまとめていますので、必要に応じて参照してください。
パターン1: Enemyを生成するクラス(Spawner)にDIする場合
上述のように多数の敵を生成するケースでは、敵の生成を管理するクラスが普通はありそうなので、まずこのケースについて考えます。
以下のように敵の生成を管理するクラスを作成します。
using UnityEngine; using UnityEngine.Assertions; using UnityEngine.UI; using VContainer; public sealed class EnemySpawner : MonoBehaviour { [SerializeField] private Enemy prefab; [SerializeField] private Button spawnButton; private EventBus _eventBus; // EventBusをVContainerにより注入する [Inject] public void Construct(EventBus eventBus) { _eventBus = eventBus; } private void Start() { // ボタンを押したら敵がスポーンされる spawnButton.onClick.AddListener(() => { // イベントバスが注入されてなかったらエラー Assert.IsNotNull(_eventBus); var enemy = Instantiate(prefab); // イベントバスを渡す enemy.Initialize(_eventBus); }); } }
コメントに書いた通り、VContainerにはこのMonoBehavior
を登録してEventBus
をDIします。
このクラスが生成したEnemy
にはそれを受け渡すだけです。
DIは以下のように行います。
using VContainer; using VContainer.Unity; public sealed class CompositionRoot : LifetimeScope { protected override void Configure(IContainerBuilder builder) { builder.Register<EventBus>(Lifetime.Singleton); builder.RegisterComponentInHierarchy<EnemySpawner>(); } }
EnemySpawner
とCompositionRoot
をシーン上の適当なGameObject
にアタッチして、EnemySpawner
にEnemy
のPrefabとButtonを持たせて実行すれば動作確認できます。
パターン2: Spawnerを介しつつEnemyにDIしたい場合
前節のようにEnemySpawnerにDIするのではなく、EnemyにDIしたいんだ、というケースもあると思います。
using UnityEngine; using VContainer; public sealed class Enemy : MonoBehaviour { private EventBus _eventBus; // Enemyに対してDIする [Inject] public void Initialize(EventBus eventBus) { _eventBus = eventBus; Debug.Log($"Enemy.Initialize: {eventBus}"); } }
あまりDIするポイントを細かくしすぎるとコードがカオスになっていきますし(よくある)、今回のケースでは必要ありませんが、あくまで例としてこういうケースがあるとします。
このような場合にはEnemySpawner
にDIコンテナ自体をDIしてIObjectResolver.Instantiate
を使ってEnemy
をインスタンス化することで依存性を注入できます。
using UnityEngine; using UnityEngine.Assertions; using UnityEngine.UI; using VContainer; using VContainer.Unity; public sealed class EnemySpawner : MonoBehaviour { [SerializeField] private Enemy prefab; [SerializeField] private Button spawnButton; private IObjectResolver _diContainer; // DIコンテナを注入 [Inject] public void Initialize(IObjectResolver container) { _diContainer = container; } private void Start() { spawnButton.onClick.AddListener(() => { Assert.IsNotNull(_diContainer); // IObjectResolver.Instantiateでインスタンスを生成 _diContainer.Instantiate(prefab); }); } }
パターン3: ファクトリメソッドをDIする場合
ちなみに以下のようにファクトリメソッドをDIすることもできます。
using System; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.UI; using VContainer; public sealed class EnemySpawner : MonoBehaviour { [SerializeField] private Button spawnButton; // Enemyのファクトリメソッド private Func<Enemy> _enemyFactory; // ファクトリメソッドをDIする [Inject] public void Initialize(Func<Enemy> enemyFactory) { _enemyFactory = enemyFactory; } private void Start() { spawnButton.onClick.AddListener(() => { Assert.IsNotNull(_enemyFactory); // ファクトリメソッドを呼び出してEnemyを生成 _enemyFactory(); }); } }
CompositionRoot は以下の通りです。
(あくまで例で、設計としてはかなりダメなので悪しからず)
using System; using UnityEngine; using VContainer; using VContainer.Unity; public sealed class CompositionRoot : LifetimeScope { [SerializeField] private Enemy enemyPrefab; protected override void Configure(IContainerBuilder builder) { builder.Register<EventBus>(Lifetime.Singleton); builder.RegisterComponentInHierarchy<EnemySpawner>(); // ファクトリを登録する builder.RegisterFactory(container => { Func<Enemy> factory = () => container.Instantiate(enemyPrefab); return factory; }, Lifetime.Singleton); } }