VContainer の導入と基本的な使い方 ver 0.9.0 ( 備忘録 )

自分用の備忘録記事

最近新しくリリースされたシンプルなDIコンテナライブラリのVContainerの導入方法をメモ


## 環境
Unity 2018.4.14.f1
VContainer ver 0.9.0



## 導入方法
GitHubから直接UnityPackageをDLしてプロジェクトにインストールする方法とUnityPackageManagerでインストールする方法がある、今回はUnityPackageを直接インストールする方法をとる

[GitHub](https://github.com/hadashiA/VContainer)のリリースページから0.9.0のUnityPackageをDLし、プロジェクトにインポート
この時、manifest.jsonに ```nuget.mono-cecil": "0.1.6-preview``` の指定を追加

これで特にエラーが無ければまずは導入成功

## 簡単なサンプル
次にVContainerを利用した簡単なサンプルを作成します。
VContainerには疑似的にMonoBehaviourのStartやUpdateイベントと似た様な処理を行うことが出来る仕組みがあるので( MonoBehaviourを利用しないクラスでMonoBehaviourのUpdate同様に毎フレーム何かの処理をする等を行えるように )そのサンプルとMonoBehaviourを継承した通常のGameObjectのコンポーネントから利用する方法の二通りを作成します。


### HelloWorldServiceの作成
先ずはゲームを通じて利用するHelloWorldServiceクラスを作成します、呼び出し元確認用にstringで引数をとりいつ呼ばれたかをコンソールに表示します、 このクラスはMonoBehaviourを継承しないクラスとします。

```csharp
using UnityEngine;

public class HelloWorldService
{

    public void HelloWorld(
        string text)
    {
        Debug.Log($"{text} : HelloWorld");
    }

}

```


### GameLifetimeScopeの作成
Zenjectで言うところのSceneContextを作成します
ProjectWindowで右クリック Create > C# Script でファイル名に *LifetimeScopeと記述すると自動でテンプレートスクリプトを作成します。

```csharp
using VContainer;
using VContainer.Unity;

public class GameLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
    }
}

```

この段階ではまだテンプレートのままです、いったん先に進みます。

### GamePresenterの作成
MonoBehaviourを継承していないクラスでMonoBehaviourのStartやUpdateのイベント同様な処理を行えるクラスを作成します。

[LifeCycleのイメージ図](https://github.com/hadashiA/VContainer/blob/master/docs/lifecycle_diagram.png)

```csharp
using VContainer.Unity;

public class GamePresenter :
    IInitializable,
    IPostInitializable,
    IFixedTickable,
    IPostFixedTickable,
    ITickable,
    IPostTickable,
    ILateTickable,
    IPostLateTickable
{
    private readonly HelloWorldService hellowWorldService;


    public GamePresenter(
        HelloWorldService _helloWOrldService)
    {
        hellowWorldService = _helloWOrldService;
    }


    // Start()直前に呼ばれる.
    public void Initialize()
    {
        hellowWorldService.HelloWorld("Initialize");
    }


    // Start()直後に呼ばれる.
    public void PostInitialize()
    {
        hellowWorldService.HelloWorld("Postinitialize");
    }


    // FixedUpdate()直前に呼ばれる.
    public void FixedTick()
    {
        hellowWorldService.HelloWorld("FixedTick");
    }


    // FixedUpdate()直後に呼ばれる.
    public void PostFixedTick()
    {
        hellowWorldService.HelloWorld("PostFixedTick");
    }


    // Update()直前に呼ばれる.
    public void Tick()
    {
        hellowWorldService.HelloWorld("Tick");
    }


    // Update()直後に呼ばれる.
    public void PostTick()
    {
        hellowWorldService.HelloWorld("PostTick");
    }


    // LateUpdate()直前に呼ばれる.
    public void LateTick()
    {
        hellowWorldService.HelloWorld("LateTick");
    }


    // LateUpdate()直後に呼ばれる.
    public void PostLateTick()
    {
        hellowWorldService.HelloWorld("PostLateTick");
    }

}


```

実装しているインターフェースは其々コメントに書かれある通りのタイミングに呼ばれます。


### TestObjectの作成
最後にMonoBehaviourを継承したTestObjectクラスを作成します

```csharp
using UnityEngine;
using VContainer;

public class TestObject : MonoBehaviour
{
    private HelloWorldService helloWorldService;

    
    // コンテナから取得.
    [Inject]
    public void Construct(HelloWorldService _helloWorldService)
    {
        Debug.Log("Inject");
        helloWorldService = _helloWorldService;
    }


    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            helloWorldService.HelloWorld("TestObject");
        }
    }
}

```

Update時にスペースキーを押下したらHelloWorldSericeのHelloWorld()を呼び出すようにします。


## Sceneの作成

次にシーンを作成します
先ずコンテナを実装するためにGameLifetimeScopeオブジェクトを作成します、Hierarchy上で右クリック Create Empty で GameObjectを生成、GameLifetimeScope.cs をアタッチします。

![VContainer_0.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212999/0b554087-892f-a2d6-8c0e-372a7a4e0df2.jpeg)

このまま実行しても何も起きないので GameLifetimeScope に手を加えます

```csharp
using VContainer;
using VContainer.Unity;

public class GameLifetimeScope : LifetimeScope
{

    protected override void Configure(IContainerBuilder builder)
    {
        builder.Register<HelloWorldService>(Lifetime.Scoped);
        builder.RegisterEntryPoint<GamePresenter>(Lifetime.Singleton);
    }

}


```

利用される側のHelloWorldServiceクラスをbuilderに登録、その次に利用する側のGamePresenterをbuilderに登録します。

そして実行
コンソールが以下の様になれば正常に動作しています、Hierarchy上にはMainCamera、DirectionalLight、GameLifetimeScopeオブジェクトの3つだけでありこのログを読んだオブジェクトがシーン上に配置されていないことがわかります。

![VContainer_1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212999/2441ce03-2e21-ee23-bd66-944ad2529a2a.jpeg)


次にGameObjectから利用する場合です。
GameLifetimeScopeに以下のように手を加えます。

```csharp
using UnityEngine;
using VContainer;
using VContainer.Unity;

public class GameLifetimeScope : LifetimeScope
{

    [SerializeField] TestObject testObject;


    protected override void Configure(IContainerBuilder builder)
    {
        builder.Register<HelloWorldService>(Lifetime.Scoped);
        builder.RegisterEntryPoint<GamePresenter>(Lifetime.Singleton);

        // TODO : GameObjectとの紐付け( これ以外の方法があるかはまだわからない ).
        builder.RegisterComponent(testObject);
    }

}
```

次にシーン上にTestObjectを配置します。
この時GameLifetimeScopeのInspectorからTestObjectをセットします。
![VContainer_2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212999/c7866d61-c110-3f55-7b6e-f4abbceaeda6.jpeg)

これで実行

以下のようにコンソールが表示されれば成功です、スペースキーを押下したときに ```TestObject : HelloWorld``` が表示されてるのが確認できるはずです。
![VContainer_3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212999/fa956238-9149-caf9-6fe4-d06440ec9b93.jpeg)

以上でとりあえずVContainerを導入及び簡単なサンプルの作成までの手順です

[ここ](https://github.com/D-RyusukeMatsumoto/VContainerTest)に一応サンプルプロジェクトを配置しています~~見る必要はないと思いますが~~

より詳細の情報やZenjectのAPIとの対応表を確認したい人は[開発者のGitHubページ](https://github.com/hadashiA/VContainer)にて確認できます

# あとがき

MonoBehaviourのコンポーネント側からコンテナに登録したクラスをInjectする時の方法がいまいちよくわかってない、ソースコードにもある通りLifetimeScopeに[SerializeField]でInjectを要求してるコンポーネントを紐付けないとできない?
詳しい方いたらご教示おねがいします