UnityのDIフレームワークをZenjectからVContainerへ移行する際に試したこと
VContainerは、Unityで動作する高速なDIフレームワークです。
最近、開発中の弾幕STGで使用しているDIフレームワークを、ZenjectからVContainerへ移行したので、その際に試したことを記録しておきます。
VContainerのインストール
Packages/manifest.json
へ、以下の行を追加して Reimport All
します。
"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
に刺さっていた元 MonoInstaller
の Parent
として、 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メソッドを探す
[Inject]
Attributeメソッドがある MonoBehaviour
継承クラスに対して、自動でインジェクションは行いません。
public void Construct
など、 MonoBehaviour
へのインジェクションを行っているメソッド名で検索し、該当ファイルがアタッチされたGameObjectを探して、 LifetimeScope
継承クラスの Auto Inject Game Objects
へアタッチします。
対応漏れがないかチェック
最後に、一通りゲームを動作させてみて、実行時エラーが発生していないかチェックします。
例えば、MonoBehaviour
継承クラスの [Inject]
Attributeメソッドは、必ずしも public void Construct
という名称とは限らないため、検索に引っかからず対応漏れしているケースがありそうです。
また、 LifetimeScope
の親子関係に関しても、見落としがちなポイントに思えます。
以上です。
筆者の場合、DIフレームワーク自体開発終盤で導入したため、Zenjectは単純な依存性の注入や一部の拡張メソッド程度にしか使用しておらず、本記事に記した手順だけでも概ね移行することが出来ました。
一方で、Zenject独自の便利機能などにガッツリ依存していると、移行難易度や工数も数段階上がってしまいそうです。その場合は無理に移行せず、開発規模や実行時に求められる最適化の度合いなどを天秤にかけたうえで移行可否を検討するとよいでしょう。
Discussion