はじめに
VContainer の入門記事は結構あるのですが、動的に GameObject を生成する方法がイマイチよく分からなかったので、サンプルを作成しました。
ソースコード
以下に記載しているプログラムは GitHub にも公開しています。
ファクトリメソッドを使用する方法を記載していましたが、もっと簡単に書ける方法がありましたので、修正しました。
具体的には IObjectResolver をインジェクトすることで、ファクトリメソッドを介さずに、直接依存注入する形式です。
GameLifetimeScope
using UnityEngine;
using VContainer;
using VContainer.Unity;
public class GameLifetimeScope : LifetimeScope
{
    [SerializeField]
    EnemySpawner EnemySpawner;
    protected override void Configure(IContainerBuilder builder)
    {
        // Enemy に依存注入する Timer クラス
        builder.RegisterComponentOnNewGameObject<Timer>(Lifetime.Singleton);
        // 実際に敵を生成するクラス
        builder.RegisterComponentInNewPrefab(EnemySpawner, Lifetime.Singleton);
        builder.RegisterComponentInHierarchy<EnemySpawnTrigger>();
        // ヒエラルキーに配置済みの Enemy クラスに依存注入
        foreach (var enemy in FindObjectsByType<Enemy>(FindObjectsSortMode.None)) {
            builder.RegisterBuildCallback(resolver => {
                resolver.Inject(enemy);
            });
        }
    }
}
EnemySpawner
using UnityEngine;
using VContainer;
public class EnemySpawner : MonoBehaviour
{
    [Inject]
    IObjectResolver Resolver;
    [SerializeField]
    Enemy EnemyPrefab;
    public Enemy Generate(string ID)
    {
        Debug.Log($"Generate ID: {ID}");
        // 今回は Instantiate を呼び出しているが、
        // ObjectPool などで、インスタンスを生成して返すことも可能。
        // 例) var enemy = ObjectPool.Get(ID).GetCompnent<Enemy>();
        var enemy = Instantiate(EnemyPrefab);
        // 動的生成されたインスタンスに対して依存注入
        Resolver.Inject(enemy);
        return enemy;
    }
}
Enemy
using UnityEngine;
using VContainer;
public class Enemy : MonoBehaviour
{
    [Inject]
    Timer timer;
    [SerializeField]
    float Duration = 10f;
    float lifetime;
    void Update()
    {
        if ((lifetime += Time.deltaTime) >= Duration) {
            Destroy(gameObject);
        }
        if (timer.Timeout) {
            Destroy(gameObject);
        }
    }
}
EnemySpawnTrigger
using System;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
using Random = UnityEngine.Random;
public class EnemySpawnTrigger : MonoBehaviour
{
    [SerializeField]
    Button EnemySpawnButton;
    [Inject]
    EnemySpawner EnemySpawner;
    [Inject]
    Timer timer;
    void Start()
        => EnemySpawnButton.onClick.AddListener(OnClicked);
    void Update()
    {
        if (timer.Timeout) {
            EnemySpawnButton.onClick.RemoveListener(OnClicked);
        }
    }
    void OnClicked()
    {
        var ID = Guid.NewGuid().ToString();
        var enemy = EnemySpawner.Generate(ID);
        enemy.transform.position = new(Random.Range(0f, 320f), Random.Range(0f, 180f), 0f);
    }
}
Timer
using UnityEngine;
public class Timer : MonoBehaviour
{
    float TimeoutSeconds = 10f;
    public bool Timeout => !enabled;
    void Update()
    {
        if ((TimeoutSeconds -= Time.deltaTime) <= 0) {
            Debug.Log("Timeout");
            enabled = false;
        }
    }
}
古いコード
### GameLifetimeScopeusing UnityEngine;
using VContainer;
using VContainer.Unity;
public class GameLifetimeScope : LifetimeScope
{
    [SerializeField]
    EnemyGenerator EnemyGenerator;
    protected override void Configure(IContainerBuilder builder)
    {
        builder.RegisterComponentOnNewGameObject<Timer>(Lifetime.Singleton);
        builder.RegisterComponentInNewPrefab(EnemyGenerator, Lifetime.Singleton);
        builder.RegisterComponentInHierarchy<EnemySpawner>();
        builder.RegisterFactory<string, Enemy>(container => (ID) => {
            return container.Resolve<EnemyGenerator>().Generate(container, ID);
        }, Lifetime.Scoped);
    }
}
Timer
using UnityEngine;
public class Timer : MonoBehaviour
{
    float TimeoutSeconds = 10f;
    public bool Timeout => !enabled;
    void Update()
    {
        if ((TimeoutSeconds -= Time.deltaTime) <= 0) {
            Debug.Log("Timeout");
            enabled = false;
        }
    }
}
Enemy
using UnityEngine;
using VContainer;
public class Enemy : MonoBehaviour
{
    [Inject]
    Timer timer;
    [SerializeField]
    float Duration = 10f;
    float lifetime;
    void Update()
    {
        if ((lifetime += Time.deltaTime) >= Duration) {
            Destroy(gameObject);
        }
        if (timer.Timeout) {
            Destroy(gameObject);
        }
    }
}
EnemyGenerator
using UnityEngine;
using VContainer;
public class EnemyGenerator : MonoBehaviour
{
    [SerializeField]
    GameObject EnemyPrefab;
    public Enemy Generate(IObjectResolver resolver, string ID)
    {
        Debug.Log($"Generate ID: {ID}");
        // 今回は Instantiate を呼び出しているが、
        // ObjectPool などで、インスタンスを生成して返すことも可能。
        // 例) var enemy = ObjectPool.Get(ID).GetCompnent<Enemy>();
        var enemy = Instantiate(EnemyPrefab).GetComponent<Enemy>();
        // 動的生成されたインスタンスに対して DI 注入
        resolver.Inject(enemy);
        return enemy;
    }
}
EnemySpawnTrigger
using System;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
using Random = UnityEngine.Random;
public class EnemySpawnTrigger : MonoBehaviour
{
    [SerializeField]
    Button EnemySpawnButton;
    /// <summary>
    /// RegisterFactory で登録されたファクトリメソッドを依存注入する
    /// </summary>
    [Inject]
    Func<string, Enemy> GenerateEnemy;
    [Inject]
    Timer timer;
    void Start()
        => EnemySpawnButton.onClick.AddListener(OnClicked);
    void Update()
    {
        if (timer.Timeout) {
            EnemySpawnButton.onClick.RemoveListener(OnClicked);
        }
    }
    void OnClicked()
    {
        var ID = Guid.NewGuid().ToString();
        var enemy = GenerateEnemy(ID);
        enemy.transform.position = new(Random.Range(0f, 320f), Random.Range(0f, 180f), 0f);
    }
}
