unity-game-enginedependency-injectionzenject

Instantiating random or chosen prefabs with sub-container thru Factory or Pool


I have an array of prefabs and I want to be able to Instantiate randomly picked prefabs thru Zenject Factory and perform their bindings in their sub-containers.

What I want to do is the same as in this code sample from Zenject documentation, but for randomly selected prefabs. https://github.com/modesttree/Zenject/blob/master/Documentation/SubContainers.md#using-game-object-contexts-no-monobehaviours

using UnityEngine;
using Zenject;

public class GameInstaller : MonoInstaller
{
    [SerializeField]
    GameObject ShipPrefab;

    public override void InstallBindings()
    {
        Container.BindInterfacesTo<GameRunner>().AsSingle();

        Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
            .FromSubContainerResolve().ByNewPrefabInstaller<ShipInstaller>(ShipPrefab);
    }
}

I was able to partially make it work with

[SerializeField] private GameObject[] ships;

...

Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
            .FromSubContainerResolve().ByNewGameObjectMethod(SpawnShip);

...

private void SpawnShip(DiContainer container, float speed)
{
    container.Bind<ShipFacade>().AsSingle();
    container.Bind<Transform>().FromComponentOnRoot();
    var shipPrefab = ships[Random.Range(0, ships.Length)];
    var ship = container.InstantiatePrefab(shipPrefab);
    container.Bind<ShipHealthHandler>().FromNewComponentOn(ship).WhenInjectedInto<ShipFacade>();
    container.BindInstance(speed).WhenInjectedInto<ShipInputHandler>();
}

But it's awkward and in this case I guess I'm not using an advantage of sub-container. And also prefabs spawns in an empty GameObject. What I want to achieve is to be able to use ShipInstaller for sub-container binding.


Solution

  • You're right, there wasn't really a very elegant way to choose the sub-container prefab dynamically.

    I took some time to make this better today with this commit. If you use the latest version of Extenject then you can now do things like this:

    public class QuxInstaller : Installer {
        float _speed;
    
        public QuxInstaller(float speed) {
            _speed = speed;
        }
    
        public override void InstallBindings() {
            Container.BindInstance(_speed);
            Container.Bind<QuxFacade>().AsSingle();
        }
    }
    
    public class CubeInstaller : MonoInstaller
    {
        public List<GameObject> QuxPrefabs;
    
        public override void InstallBindings()
        {
            Container.BindFactory<float, QuxFacade, QuxFacade.Factory>()
                .FromSubContainerResolve().ByNewPrefabInstaller<QuxInstaller>(ChooseQuxPrefab);
        }
    
        UnityEngine.Object ChooseQuxPrefab(InjectContext _) {
            return QuxPrefabs[Random.Range(0, QuxPrefabs.Count)];
        }
    }