📦

【もぉん流】Unity × VContainer : 手っ取り早く使おう

に公開

この記事では、手っ取り早く下記の目的を達成するための手順を紹介します。

  • VContainerに触れてみたい
  • シーン間でデータを引き継ぎたい
  • 手動で設定する参照関係を最小限したい

補足

丁寧な使い方については、公式サイトや他の記事などを参照してください!
https://vcontainer.hadashikick.jp/ja/
https://qiita.com/sakano/items/b91e01f7fc0a946090ac
https://zenn.dev/qemel/articles/14d247b9945527#はじめに

1. インストール

公式サイトからどうぞ!
Git URLが手軽でおすすめです

2. OnePlaySaveData.csを実装

シーンを跨いで引き継ぐデータを実装します。

public class OnePlaySaveData
{
    public int Score;
}

3. RootLifetimeScope.csを実装

RootLifetimeScopeは、どのシーンから再生しても最初にただ1つ生成される、すべての親となるLifetimeScopeです。(LifetimeScopeは、参照関係を管理/解決する機構のこと)

using VContainer;
using VContainer.Unity;

public sealed class RootLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // OnePlaySaveDataを登録
        builder.Register<OnePlaySaveData>(Lifetime.Singleton);

        // 参照関係が構築された後に、
        // 子に存在する全てのオブジェクトにInject(注入)する
        builder.RegisterBuildCallback(resolver =>
        {
            foreach (Transform child in transform)
            {
                resolver.InjectGameObject(child.gameObject);
            }
        });
    }
}

4. RootLifetimeScopeを設定する

  1. 適当なPrefabを作成し、RootLifetimeScope.csをアタッチする
  2. Projectウィンドウで右クリックし、Create/VContainer/VContainer Settingsで設定ファイルを作成
  3. 作成したVContainerSettings.assetRootLifetimeScopeに、作成したPrefabをアタッチする

5. CommonLifetimeScope.csを実装

各シーンに配置するLifetimeScopeです。各シーン読み込み時に、そのシーン内のオブジェクトの依存関係を解決する役割です。

using VContainer;
using VContainer.Unity;

public sealed class CommonLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // 参照関係が構築された後に、
        // シーンに存在する全てのオブジェクトにInject(注入)する
        builder.RegisterBuildCallback(resolver =>
        {
            foreach (var rootGameObject in gameObject.scene.GetRootGameObjects())
            {
                resolver.InjectGameObject(rootGameObject);
            }
        });
    }
}

6. Test.csを実装

インスタンスがInjectされ、値が引き継がれていることを確認する為のテストクラスです。

using UnityEngine;
using UnityEngine.SceneManagement;
using VContainer;

public class Test : MonoBehaviour
{
    // 注入して欲しい!と宣言するAttribute
    [Inject] private OnePlaySaveData _onePlaySaveData;

    private void Update()
    {
        _onePlaySaveData.Score++;
        Debug.Log($"Score: {_onePlaySaveData.Score}");

        if (Input.GetKeyDown(KeyCode.R))
        {
            // シーンを跨いでも値が引き継がれることを確認する
            SceneManager.LoadScene(gameObject.scene.name);
        }
    }
}

7. 動作確認する

  1. 適当なシーンを作成する
  2. CommonLifetimeScope.csをアタッチする
  3. Test.csをアタッチする

8. (番外編)PureC# 以外のインスタンスを注入したい

例ではPureC#OnePlaySaveDataのインスタンスを注入しましたが、MonoBehaviourや生成済みのインスタンスも注入することができます。

// 画面を制御する仮クラス
public class DisplayFader : MonoBehaviour
{
    [SerializeField] private CanvasGroup _canvasGroup;

    public void Fill()
    {
        _canvasGroup.alpha = 1f;
    }
    
    public void Clear()
    {
        _canvasGroup.alpha = 0f;
    }
}
~ 省略 ~
public sealed class RootLifetimeScope : LifetimeScope
{
    // ここへインスタンスを手動で設定
    [SerializeField] private DisplayFader _displayFader;
    protected override void Configure(IContainerBuilder builder)
    {
        // DisplayFaderを、RegisterInstance関数で登録
        builder.RegisterInstance(_displayFader);

        // PureC#について、自分で作成したインスタンスを登録することも可能です
        // builder.Register<OnePlaySaveData>(Lifetime.Singleton);
        var onePlaySaveData = new OnePlaySaveData();
        builder.RegisterInstance(onePlaySaveData);

        ~ 省略 ~
    }
}

RootLifetimeScopeプレハブの子オブジェクトにDisplayFaderを用意し、SerializeFieldに手動で設定しています

9. TIPS

動的に生成したオブジェクトの[Inject]が注入されない!

通常のInstantiate関数ではなく、IObjectResolverInstantiate関数を使って生成しましょう

[Inject] private IObjectResolver _resolver;
[Inject] private GameObject _prefab;

private void Generate()
{
    // var instance = Instantiate(_prefab); // 従来
    _resolver.Instantiate(_prefab); // 置き換え
}

登録するInstanceのTypeを限定したい!

RegisterInstance<T>関数を使いましょう


public interface IHoge{
}

public class Hoge : IHoge{
}

protected override void Configure(IContainerBuilder builder)
{
    var hoge = new Hoge();
    // IHogeとして登録
    builder.RegisterInstance<IHoge>(hoge);
}

// 注入される側
[Inject] Hoge hoge; // 注入されない
[Inject] IHoge hoge; // 注入される

シーン別にInjectするクラスを変えたい

今回はCommonLifetimeScopeで代用しましたが、シーン別に〜LifetimeScopeを作成し、そこで必要最低限の依存関係を解決する運用をオススメします

Discussion