🦔

VContainerでFactoryを利用して、複数のComponentの依存を解決したPrefabを作る

2024/03/26に公開

はじめに

VContainerはめちゃめちゃ便利なDIコンテナですが、色々と依存性を解決するのが難しい場面もあります。

今回は[Inject]アトリビュートの付いた複数のComponentに対して依存を解決する書き方の一例を提示します。

状況

例えばPlayerCoreクラスとPlayerMovementクラスがあるとします。これらは同じPlayerというGameObjectに紐づいたPrefabとして利用されるという想定です。

これらのComponentには、それぞれ依存先があります。仮にFoo,Barとします。

PlayerCore
using UnityEngine;

public class PlayerCore : MonoBehaviour
{
    [Inject] private readonly Foo _foo;
}
PlayerMovement
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    [Inject] private readonly Bar _bar;
}

失敗例

以下のようにして、Prefabをアタッチしても上手くいきません。

失敗例
public sealed class GameConfigure : LifetimeScope
{
    [SerializeField] private PlayerCore _playerCore;

    protected override void Configure(IContainerBuilder builder)
    {
        builder.Register<Foo>(Lifetime.Singleton);
        builder.Register<Bar>(Lifetime.Singleton);
        builder.RegisterComponent(_playerCore);
        builder.RegisterComponentInNewPrefab(_playerCore, Lifetime.Singleton);
    }
}

解決法:Factoryの利用

これらを解決するにはFactoryを登録し、そのメソッドを呼んでやります。

public sealed class GameConfigure : LifetimeScope
{
    /* ... */

    protected override void Configure(IContainerBuilder builder)
    {
        builder.Register<Foo>(Lifetime.Singleton);
        builder.Register<Bar>(Lifetime.Singleton);

        builder.RegisterFactory<PlayerInfo, PlayerCore>(container => (info) =>
        {
            var player = container.Instantiate(_playerCore); // playerを生成
            var playerMovement = player.GetComponent<PlayerMovement>();
            container.Inject(playerMovement); // playerMovementの依存を解決
            player.Init(info); // playerの初期化処理をしたかったらこんな感じで
            return player;
        },
        Lifetime.Singleton);

        builder.RegisterComponent(_playerSpawner);
    }
}
参照側
public sealed class PlayerSpawner : MonoBehaviour
{
    [Inject] private readonly Func<PlayerInfo, PlayerCore> _playerFactory; // Factoryのメソッドがinjectされる

    private void Start()
    {
        var playerInfo = /*...*/
        var player = _playerFactory(playerInfo); // playerのPrefabが生成される
    }
}

Factoryとは

そもそもFactoryとは何でしょうか?

色々な使い方やパターンがあり、私も正直自信がないのですが…

基本的には、newの処理をFactoryクラスに逃がすことで抽象的なメソッドに逃がしたり、複雑な生成系をまとめたりするのに使うのかなーーという印象です。

https://qiita.com/toshi0607/items/d95bdd31da363e0f04a9
https://unity.com/how-to/how-use-factory-pattern-object-creation-runtime

今回は、複雑なクラスの依存関係等を解決したオブジェクトを生成するために使います

コードの意味

コードの意味について見ていきます。

1.builder.RegisterFactory<>();

FactoryメソッドをRegisterするよ~という意味です。<>内の引数は一番最後が生成するクラスで、それ以外が生成に使う引数になります。たとえば、

builder.RegisterFactory<int, Vector3, Player>(/*...*/);

と書けば、第一引数にint,第二引数にVector3を持つ、Playerを返すFactoryメソッドが登録されます!

ちなみにこれは以下の構文と一緒です。

builder.Register<Func<int, Vector3, Player>>(/*...*/);

まあ上のような構文が用意されているので、上を使いましょう。

2.(container => (x) => ...

containerはDIのコンテナ、(x)は指定した引数が入ります。

builder.RegisterFactory<int, Vector3, Player>のように引数を複数指定した場合は、その数だけ引数名がないとコンパイルエラーになります。

つまり、例えば以下のように書く必要があります。

builder.RegisterFactory<int, Vector3, Player>(container => (number, position) => /*...*/ );

3.カッコ内の処理

あとはメソッドの中身の実装です。container.Inject()を利用すれば依存を解決することが出来るので、これを使いながらFactoryを生成しましょう。

感想

Factoryメソッドをそのまま登録できるのはVContainerの超便利機能だと感じました。Componentの依存を解決したPrefabを作るという用途のほかにも色々な場面でFactoryは重宝するので、活用場面は結構多いのではないでしょうか…!

参考

https://vcontainer.hadashikick.jp/registering/register-factory
https://github.com/hadashiA/VContainer/discussions/498

Discussion