Zenject
UnityでDIのパッケージとして流行ってるそうなので少し使って見ました。
オブジェクト(特にGameObject)を生成するFactoryに注目して使っていきたいと思います。
Factory基本
生成するオブジェとしてBaseCubeを用意します。
BaseCube.cs
デモに倣ってFactoryを作っておきます。
public class BaseCube : MonoBehaviour {
public class Factory : Factory<BaseCube>
{
}
}
CubeSpawner.cs
次は毎フレームCubeをスポーンするインスタンスを用意します。
毎フレーム行いたい処理がある場合は、ITickable
を実装しましょう。
public class CubeSpawner : ITickable
{
private BaseCube.Factory _factory;
public CubeSpawner(BaseCube.Factory factory)
{
_factory = factory;
}
public void Tick()
{
_factory.Create();
}
}
GameInstaller.cs
Installerを用意します。
public class GameInstaller : MonoInstaller
{
[SerializeField] private GameObject _cube;
public override void InstallBindings()
{
Container.BindFactory<BaseCube, BaseCube.Factory>()
.FromComponentInNewPrefab(_cube)
.WithGameObjectName("Cube")
.UnderTransformGroup("Cubes");
Container.Bind<ITickable>().To<CubeSpawner>().AsSingle();
}
}
Container.BindFactory<BaseCube, BaseCube.Factory>()
によって生成されるオブジェとFactoryを紐づけることができます。
SceneContextにGameInstallerを紐づけて完成です。
これで、毎フレームキューブが生成されます。
GameObjectFactoryを使う
GameObjectFactoryというのが用意されており、これは初期化時に引数として、Prefabを渡しておくことで、Create()によってそのPrefabeをInstantiateすることができます。
GameObjectFactory.cs
public class GameObjectFactory : IFactory<GameObject>
{
DiContainer _container;
UnityEngine.Object _prefab;
[Inject]
public void Construct(
UnityEngine.Object prefab,
DiContainer container)
{
_container = container;
_prefab = prefab;
}
public GameObject Create()
{
return _container.InstantiatePrefab(_prefab);
}
}
CubeFactory.cs
CubeFactoryはそれを継承するだけで中身は空っぽでOK.
public class CubeFactory : GameObjectFactory
{
}
GameInstaller.cs
あとは初期化時にPrefabを渡しておきましょう。
public class GameInstaller : MonoInstaller
{
[SerializeField] private GameObject _cube;
public override void InstallBindings()
{
Container.Bind<ITickable>().To<CubeSpawner>().AsSingle();
Container.Bind<CubeFactory>().AsSingle().WithArguments(_cube);
}
}
プレファブを渡すあたりがこちらの方が直感的に理解しやすいかもしれないですね。
Prefabを複数登録しておく
このままだと一つのFactoryに対して一つのPrefabしか生成できないので、Create(3)とすると、3番目のPrefabが生成できるようにしてみました。
GameObjectsFactory.cs
public class GameObjectsFactory : IFactory<int, GameObject>
{
private DiContainer _container;
private GameObject[] _gameObjects;
[Inject]
public void Construct(GameObject[] gameObjects, DiContainer container)
{
_container = container;
_gameObjects = gameObjects;
}
public GameObject Create(int i)
{
return _container.InstantiatePrefab(_gameObjects[i]);
}
}
GameObjectFactoryのパクリです。ポイントは、IFactory<int, GameObject>
を実装することで、Create(int)を使うことができる点ですね。
CubesFactory.cs
例によって継承するだけ。
public class CubesFactory : GameObjectsFactory
{
}
CubeSpawner.cs
Create時に引数を渡しましょう。
public class CubeSpawner : ITickable
{
private CubesFactory _factory;
public CubeSpawner(CubesFactory factory)
{
_factory = factory;
}
public void Tick()
{
_factory.Create(0);
_factory.Create(1);
}
}
GameInstaller.cs
初期化時にGameObjectの配列を渡して完了!
public class GameInstaller : MonoInstaller
{
[SerializeField] private GameObject[] _cubes;
public override void InstallBindings()
{
Container.Bind<ITickable>().To<CubeSpawner>().AsSingle();
Container.Bind<CubesFactory>().AsSingle().WithArguments(_cubes);
}
}
これで2種類のキューブが交互に生成されれば成功!
所感
これだと、GameObjectの配列の順番をプロジェクト内でしっかり決めておかないと混乱の元になるだけですね。
でも敵を生成するにしても様々な敵を生成したい時ってあると思うんだけどな。そういう場合は一つのクラス(敵)ごとに一つのFactoryを作っておくのがいいのですかね?