Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
18
Help us understand the problem. What is going on with this article?
@__poosuke

Unityの新しいDIライブラリ:VContainer (0.0.2対応修正 7/4)

VContainer is 何?

 タイトルの通りです。現在Unityで使えるDIライブラリにはZenject/Extenjectがありますが、それとは別の選択肢としてVContainerが作られています。なんでもZenject/Extenjectは多機能で便利なのですが、もっとシンプルな薄いDIライブラリを目指されているのだとか。作者はハダシA氏です。
https://github.com/hadashiA/VContainer
 現在、開発中なのですが、0.0.1がリリースされたので早速触って見ました。
0.0.2が出ました(7/4)。InjectとIInitialize関係が更新されました。

まずはZenject/Extenjectで作る

IPerson.cs
namespace ZenjectSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace ZenjectSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace ZenjectSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}
SayHelloInstaller.cs
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<IPerson>().To<Parent>().AsSingle();
            Container.Bind<SayHello>().AsSingle();
        }
    }
}
SayHelloMonoBehaviour.cs
using UnityEngine;
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject] private SayHello _sayHello = default;
    }
}

 SayHelloMonoBehaviourは適当なGameObjectにアタッチします。SayHelloがResolveされるときに、コンストラクタのIPersonにParentが放り込まれ、
スクリーンショット 2020-07-02 20.26.20.png
と出ます。こいつをVContainerで書いてみます。

VContainerで書く

インストール

 まずインストール方法ですが、上記Githubリンクのリリースページにunitypackageがありますので、それをダウンロード、インストールするだけでOKです。

インストーラーを作る

 最初から手で書いてもいいのですが、テンプレートを自動生成してくれます。Projectの右クリックからCreate > C# Script。で、ファイル名をXXXInstallerとすれば、Installerのテンプレートを作ってくれます。

LifetimeScopeを作る

 Zenject/ExtenjectのSceneContext等にあたるものがLifetimeScopeになります。作り方はHierarchyの右クリックでVContainer > LifetimeScope。で、InspectorのMonoInstallerの+ボタンをクリックすると、さっき作ったSayHellowInstallerが出てきます。指定したInstallerはLifetimeScopeGameObjectにアタッチされます。
スクリーンショット 2020-07-02 21.04.50.png

コード書く

コードを書いていきます。

IPerson.cs
namespace VContainerSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace VContainerSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace VContainerSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}

ここまではさっきと同じです。

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }
    }
}

 MonoBehaviourです。エディター上で適当なGameObjectを作って、それにアタッチします。サンプルではメソッドインジェクションがありましたので、そちらにしました。

SayHelloInstaller.cs

using UnityEngine;
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        [SerializeField] private SayHelloMonoBehaviour sayHello = default;

        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterComponent(sayHello);
        }
    }
}

 キモとなる部分です。書き加えたのはInstallメソッド内の3行とsayHelloフィールド。

builder.Register<IPerson, Parent>(Lifetime.Singleton);
builder.Register<SayHello>(Lifetime.Singleton);

 この2行は、ParentとSayHelloをZenject/ExtenjectでいうところのBindします。

[SerializeField] private SayHelloMonoBehaviour sayHello = default;

builder.RegisterComponent(sayHello);

 その次の行とsayHelloのインスタンスです。この解説の前に、まずInjectの挙動について解説します。

[Inject]の挙動

 Zenject/ExtenjectだとMonoBehaviourにInjectを書いておけば、どこでもInjectしてくれました。VContainerはエディター上でGameObjectを作ってHierarchyに存在してるだけではInjectを発動してくれません。
 VContainerでは、Injectは原則的にResolveするときに発火します。例えばなんらかのクラスのコンストラクタ引数になっててそこに放り込まれる時とか、IInitializableが実装されててそれが走る時とか。
 DIはコンストラクタでのインジェクションが原則だと言われます。コンストラクタインジェクションはResolveされる時以外はなんらInjectされないので、それとタイミングを合わせていると考えれば納得です。

[Inject]を発火させる方法

 ですんで、IInitializableをSayHelloMonoBehaviourに実装してAsを使って発火するか、適当なコンストラクタにぶち込むか、なんですが(7/4追記に転記)、もっと簡単なやり方があります。それが上述の

builder.RegisterComponent(sayHello);

です。これでsayHelloインスタンスをInjectしてくれます。

※0.0.2で実装されました。0.0.1でのやり方は下の方に残しておきます。

動作

スクリーンショット 2020-07-03 22.15.25.png
動きました。

Instantiateとかはどうなんの?

 試しにPrefabをInstantiateしてみましたが、Injectは発火しません。PlaceholderFactoryもありません(少なくとも現在は)。DiContainer.Injectのようなものもないようです。つまり自前でFactoryを作って、必要な依存をそこで解決しましょう。

所感

 [Inject]の使い方にちょっと混乱しましたが、使えそうです。この辺りのZenject/Extenjectとの違いは軽量化のためでしょうか。
 一つ興味深いのはZenject/ExtenjectにあるFactory関係の実装予定はないそうで。まぁ確かにあれはPlaceholderFactory作ってそれをSpawnerで包んで、なんか手間だなぁ、と感じたり。いまいち使いこなせてない感がありました。ですんで、多機能よりも軽量化に注力するのは大歓迎です。今後に期待です。

追記(7/2)

 すみません。同じProjectにあったZenjectと競合してました。名前空間を貫通してくるとは思いませんでした。IInitialize関係がこちらの追記にあったのですが、上の方にスクリプト修正の上で転記しました。

追記(7/3)

 ハダシAさんに質問した所、Zenject/Extenjectのような一方的なInjectは想定しておらず、Resolveが必要とのご回答をいただきました。Zenject/Extenjectのように使っていたInjectを上述のとおり修正いたしました。

追記(7/4)

 0.0.2でRegisterComponentが実装されました。この記事をみて速攻で実装してくれました。熱い&感謝です。
 IInitializableを使ってResolveする方法をこちらに残しておきます。以下のようなやり方は現在不要です。ただ、基本的には以下のようにしてInjectを発火させるんだ、と思っておいた方がVContainerを扱いやすくなると思います。
 ちなみにAsとやっていますが、0.0.2で

builder.RegisterEntryPoint<Foo>(..);

が実装され、IInitializableとかITickableをまとめてやってくれるようになってます。

SayHelloMonoBehaviourにIInitializable等を実装

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour, IInitializable
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }

        public void Initialize()
        {
        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterComponentInHierarchy<SayHelloMonoBehaviour>().As<IInitializable>();
            //HierarchyにあるSayHelloMonoBehaviourのInitializeを発火。Resolveされる。
        }
    }
}

IInitializableをMonoBehaviourに実装してAsで発火します。

何かのコンストラクタでResolveさせる

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }
    }
}
SayHelloMonoBehaviourInjector.cs
using VContainer.Unity;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviourInjector : IInitializable
    {
        public SayHelloMonoBehaviourInjector(SayHelloMonoBehaviour sayHelloMonoBehaviour)
        {
        }

        public void Initialize()
        {
        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        [SerializeField] private SayHelloMonoBehaviour sayHello = default;

        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterInstance(sayHello);
            builder.Register<SayHelloMonoBehaviourInjector>(Lifetime.Singleton).As<IInitializable>();
            //SayHelloMonoBehaviourInjectorがInitializeの発火でResolveされる。
            //その際、コンストラクタ引数にSayHelloMonoBehaviourがあるのでsayHelloインスタンスがResolveされる。
            //builder.RegisterEntryPoint<SayHelloMonoBehaviourInjector>(Lifetime.Singleton);
            //で代用可能。
        }
    }
}

 RegisterInstanceで該当のMonoBehaviourをRegisterして、適当なコンストラクタにぶち込みます。そのときResolveされます。
 IInitializableをRegisterEntryPointやAsすることで、Zenject/ExtenjectのNonLazy的に扱えます。

18
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
__poosuke
ゲーム作ってます。本格派鉄道経営SLG「鉄道事業戦略」、武器商人経営SLG「世界の半分を商人にやろう」、クエスト受注ローグライク「自営業勇者」を頒布中。京都周辺でUnityのプログラミング講師、C++等のお仕事やってます。
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
18
Help us understand the problem. What is going on with this article?