📦

UnityのDIフレームワークをZenjectからVContainerへ移行する際に試したこと

に公開

VContainerは、Unityで動作する高速なDIフレームワークです。
https://vcontainer.hadashikick.jp/ja/

最近、開発中の弾幕STGで使用しているDIフレームワークを、ZenjectからVContainerへ移行したので、その際に試したことを記録しておきます。

VContainerのインストール

https://vcontainer.hadashikick.jp/ja/getting-started/installation

Packages/manifest.json へ、以下の行を追加して Reimport All します。

manifest.json
"jp.hadashikick.vcontainer": "https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.16.8"

Zenjectのアンインストール

Assets/Plugins/Zenject を削除します。

using Zenject を置換

using Zenject; を全検索して置換します。

- using Zenject;
+ using VContainer;

IInitializable を置換

IInitializable を全検索して置換します。

- IInitializable
+ VConatiner.Unity.IInitializable

差分ファイル数が多くなく手動で修正できる数であれば、該当のファイルで

+ using VContainer.Unity;

してあげるだけでもよいかもしれません。

MonoInstaller を置換

MonoInstaller を全検索して置換します。

- MonoInstaller
+ VContainer.Unity.LifetimeScope

こちらも、手動で修正できる数であれば、

+ using VContainer.Unity;

をした上で、以下で全置換してもよいでしょう。

- MonoInstaller
+ LifetimeScope

InstallBindings を置換

public override void InstallBindings() を全検索して置換します。

- public override void InstallBindings()
+ protected override void Configure(IContainerBuilder builder)

Bind を置換

正規表現でいい感じに全検索して置換します。
ここではすべてのパターンを網羅しきれないので、以下の例は参考程度に。

- Container\.Bind(\<.*\>)\(\)\.AsSingle\(\);
+ builder.Register$1(Lifetime.Singleton);
- Container\.BindInterfacesTo(\<.*\>)\(\)\.AsSingle\(\);
+ builder.Register$1(Lifetime.Singleton).AsImplementedInterfaces();
- Container\.BindInterfacesAndSelfTo(\<.*\>)\(\)\.AsSingle\(\);
+ builder.Register$1(Lifetime.Singleton).AsImplementedInterfaces().AsSelf();
- Container\.BindInstance\((.*)\);
+ builder.RegisterInstance($1);
- Container\.BindInstance\((.*)\).AsSingle\(\);
+ builder.RegisterInstance($1);
- Container\.BindInterfacesAndSelfTo\<.*\>\(\)\.FromInstance\((.*)\).AsSingle\(\);
+ builder.RegisterInstance($1).AsImplementedInterfaces().AsSelf();

LifetimeScope の親子関係を整理

Inspector上から LifetimeScope を継承したコンポーネント(元 MonoInstaller )を探し、元のContextに応じた Parent を指定します。

例えば、この例では GameObjectContext に刺さっていた元 MonoInstallerParent として、 SceneContext に刺さっていた元 MonoInstaller のオブジェクトを設定しています。


このとき、Inspector上でかつて GameObjectContext だったScriptがMissingになっているはずなので、Remove Componentします。
すると、Gitで以下のような差分が出てくるので、

- m_Script: {fileID: 11500000, guid: 08eca9f7688a0a24685b89133b020c8e, type: 3}

SceneやPrefabなども対象にGUIDを全検索し、ヒットした候補に対して同様に Missing Script を RemoveComponent します。

また、ほかのContextも同様にGUIDで全検索して、Missing Scriptを Remove Component しておきましょう。

MonoBehaviour 継承クラスから [Inject] Attributeメソッドを探す

https://vcontainer.hadashikick.jp/ja/resolving/gameobject-injection
VContainerでは、 [Inject] Attributeメソッドがある MonoBehaviour 継承クラスに対して、自動でインジェクションは行いません。

public void Construct など、 MonoBehaviour へのインジェクションを行っているメソッド名で検索し、該当ファイルがアタッチされたGameObjectを探して、 LifetimeScope 継承クラスの Auto Inject Game Objects へアタッチします。

対応漏れがないかチェック

最後に、一通りゲームを動作させてみて、実行時エラーが発生していないかチェックします。
例えば、MonoBehaviour 継承クラスの [Inject] Attributeメソッドは、必ずしも public void Construct という名称とは限らないため、検索に引っかからず対応漏れしているケースがありそうです。
また、 LifetimeScope の親子関係に関しても、見落としがちなポイントに思えます。

以上です。
筆者の場合、DIフレームワーク自体開発終盤で導入したため、Zenjectは単純な依存性の注入や一部の拡張メソッド程度にしか使用しておらず、本記事に記した手順だけでも概ね移行することが出来ました。
一方で、Zenject独自の便利機能などにガッツリ依存していると、移行難易度や工数も数段階上がってしまいそうです。その場合は無理に移行せず、開発規模や実行時に求められる最適化の度合いなどを天秤にかけたうえで移行可否を検討するとよいでしょう。

Discussion