MRが楽しい

MRやVRについて学習したことを書き残す

VContainerのHelloWorldを試す その1(DIコンテナへの登録)

本日は VContainer の小ネタ枠です。
VContainer の HelloWorld を試したので記事に残します。

VContainerのHelloWorld

以下のドキュメントページで手順がまとめられています。
本記事ではこちらの手順をキャプチャを交えて記事に残します。
vcontainer.hadashikick.jp

VContainer のインストール手順で作成した以下のプロジェクトを利用します。
bluebirdofoz.hatenablog.com

HelloWorld

本記事では以下の VContainer の基本的な使い方を試します。
・1つのコンテナとなる LifetimeScope を継承したコンポーネントを作り、シーンに置きます
・LifetimeScope のメソッドをオーバライドして、依存関係を管理したいオブジェクトを登録します
・LifetimeScope を使って Player Loop System のスケジュールの動作を試します

1. 他オブジェクトへ機能を委譲するクラスをつくる

一例として「Hello World」のログを出力するだけのクラスを作成します。
これがロジックを保持する純粋な C# のクラスになります。

namespace MyGame
{
    public class HelloWorldService
    {
        public void Hello()
        {
            UnityEngine.Debug.Log("Hello world");
        }
    }
}


2. DIの設定を施す(Composition Root)

先ほどのクラスをDIコンテナに登録して参照関係を作ります。
コンテナをつくるため、LifetimeScope を継承した以下のコンポーネントを作成します。

using VContainer;
using VContainer.Unity;

namespace MyGame
{
    public class GameLifetimeScope : LifetimeScope
    {
        protected override void Configure(IContainerBuilder builder)
        {
            // HelloWorldServiceをDIコンテナに登録する
            builder.Register<HelloWorldService>(Lifetime.Singleton);
        }
    }
}


3. 作成したLifetimeScopeをGameObjectにアタッチ

LifetimeScope を継承したコンポーネントをシーンに配置します。
Hierarchy に Create Empty でGameObjectを作ります。

適当な名前を設定しコンポーネントをアタッチします。

4. HelloWorldService を呼び出す

次に以下のように、HelloWorldService へ仕事を委譲するクラスを作成します。

using VContainer;
using VContainer.Unity;

namespace MyGame
{
    public class GamePresenter
    {
        readonly HelloWorldService helloWorldService;

        public GamePresenter(HelloWorldService helloWorldService)
        {
            this.helloWorldService = helloWorldService;
        }
    }
}

LifetimeScope で登録したクラスたちは自動的に参照関係が構築されます。
上記のクラスはコンストラクタで HelloWorldService を引数にとっています。
DIコンテナはこのような定義をみつけて、自動的に必要なオブジェクトをコンストラクタに渡してくれます。

5. 独自のC#クラスをUnityのPlayerLoopSystem上で走らせる

このままでは仕事を委譲するクラスで処理を実行するタイミングがないため、処理の実行タイミングを実装してみます。

Unity では Unity のライフサイクルイベント上でコードを実行するのが一般的です。
通常は MonoBehaviour の Start / Update などを使用します。
このような処理の起点のことを、VContainerでは 「EntryPoint」と呼びます。

VContainer では特別な Interface を実装したクラスを登録することで MonoBehaviour を使用せずに EntryPoint を作ることができます。
以下の通り、仕事を委譲するクラスを更新します。

using VContainer.Unity;

namespace MyGame
{
    // UpdateのEntryPointを作るため、ITickableのインタフェースを実装する
    public class GamePresenter : ITickable
    {
        readonly HelloWorldService helloWorldService;

        public GamePresenter(HelloWorldService helloWorldService)
        {
            this.helloWorldService = helloWorldService;
        }

        // Updateのタイミングで呼ばれる
        public void Tick()
        {
            helloWorldService.Hello();
        }
    }
}

実際に Unity のライフサイクルイベントで動作させるには、RegisterEntryPoint メソッドで登録しておく必要があります。
LifetimeScope を継承したクラスを以下の通り更新してDIコンテナへの登録を追加します。

using VContainer;
using VContainer.Unity;

namespace MyGame
{
    public class GameLifetimeScope : LifetimeScope
    {
        protected override void Configure(IContainerBuilder builder)
        {
            // HelloWorldServiceをDIコンテナに登録する
            builder.Register<HelloWorldService>(Lifetime.Singleton);
            // GamePresenterをVContainerのEntryPointに登録する
            builder.RegisterEntryPoint<GamePresenter>();
        }
    }
}

実際にシーンを再生して動作を確認します。
Update のタイミングで処理が呼び出されていることが確認できます。

この VContainer の EntryPoint はドメインロジックとプレゼンテーションロジックを分離するために役に立ちます。
記事が長くなったので分けます。次は View からユーザの入力を受けて処理を実行するケースについてです。
bluebirdofoz.hatenablog.com