YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

【備忘録】怠惰な人(私)が書いたZenject(Extenject)の使い方の説明

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
今回はZenject(Extenject)の使い方についてです。
自分が使い方をよく忘れるので備忘録、まとめ用です。

N番煎じですので、参考にはならないと思います。

目次


Zenject(Extenject)とは


一応解説です。

DIフレームワークです。
DIとは依存性の注入(Dependency injection)です。
誤解を生みそうなくらい簡単にいうと、いい感じにクラスとかの初期化とかをやることをDIっていいます。
依存性っていうかオブジェクトの注入だよね。外部からオブジェクトを注入して初期化することをDIっていう。
(依存性って直訳するからわかりにくいだけで、切っては切り離せないものの注入って感じで訳した方が伝わりそう?)
初期化するときにDIコンテナというのを使っていい感じに自動で初期化してくれるのををDIフレームワークと言います。

厳密に詳しいこと知りたい?うるせぇ!コレは備忘録だ!説明書いてるだけありがたく思いやがれ!他の記事に行ってくれ。

他にも私が使ったことあるやつでDI使われてるフレームワークはSpringFrameworkとかZend Frameworkだな!
Webのやつだ!ただこの2つはDIフレームワークではなくて、DIも含まれるフレームワーク


私がZenjectなのになんでタイトルで(Extenject)ってついてるかって?
Zenject≒Extenjectだから。
Extenjectについては色々あるんだけど、元々のZenjectのメインメインコントリビューターのSteve Vermeulen氏がZenjectの権利関係で揉めたからとかなんとかで
Steve Vermeulen氏がメインで開発してるのにSteve Vermeulen氏がいてた会社がZenject権利を主張して色々揉めたって感じなのかな?詳しいことは知らん。
ま、それでSteve Vermeulen氏が別途つくったやつがExtenjectってわけ。なのでこの記事ではZenjectって私は呼んでるが、実質私がつかってるのはExtenjectの方。
Zenjectは更新されてないのでExtenject皆使おうね。

github.com

DI使う利点って何よ!


DIすると密結合の設計が疎結合になるって思ってる人がいるけど、それは違くて、
DIを使えれば疎結合に設計されたものを疎結合に保ち続けやすいって話。
だから、「イカしてるからDIフレームワーク導入しようぜ!」ってプロジェクトの途中で導入はやめたほうがいい。
止めはせんが、覚悟はしたほうが良い。

後単純にDIするだけでなくDIフレームワークを利用すると初期化の手間が省けてのが副産物的にうれしいって感じ。
メインは疎結合にするものだから、(後で軽く解説するけど)いろいろな初期化の手間を省くためだけに利用するのだけはやめたほうがいい。


あと、もう少し詳しく説明する。

グローバル変数で定義するのは良くないっていう理由で
シングルトンっていうデザインパターンがあるけど、
それもキレイじゃないって言われていて
サービスロケーターパターンってのを使い始められてるけど、
それすらもキレイじゃないって最近は言われてるのよね。

更にキレイに使えるようにしようってなって出てくるのがDIってわけ。

blog.a-way-out.net

場合によってはサービスロケーターやシングルトンも使うけど、基本DIでOK。

で、使い方


DIフレームワークっていうけどUnityで色々使えちゃうのよね。
逆に色々使えちゃうがために相応しくない使い方をしてしまって阿鼻叫喚する人もいる。
いくらいい感じに初期化してくれるからって何でもかんでも初期化対象にしたらゲームが始まるときとかに、初期化処理が走りまくって、とても遅くなるし、最悪の場合、ゲームが止まる。
まぁ、最初はグローバルで定義してるシングルトンやサービスロケーターを消すだけのために導入はありなんじゃないかな。

初期化処理で阿鼻叫喚した話は別の人がスライドとかで発表してた人がいたと思うので「Unity Zenject完全に理解した」とかで検索してくれ。
多分そこで言ってたと思う。記憶が正しければ。
ただ、ちょっと古い発表会なので、そのままコードは使えないこともあるので概念の理解だけに留めておいたほうがいいかも?
しらんけど。

さて、使い方を説明していく。

Zenject Binding


コンポーネントを自動でアタッチしてくれるやつ!
色んなオブジェクトにアタッチしないといけないときに、毎回[SerializeField]って定義したスクリプトに手作業でアタッチしていくのって面倒くさくない?
え、面倒くさくない?だったら手動でやってください。DI使うより初期化の動きの負荷がないのでそっちが良いと思います。この部分は終わり。

面倒くさい場合はDIで自動でアタッチされるようにしましょう。

1.アタッチするGameObjectをつくる
2. Zenject Bindingというスクリプトコンポーネントをプロジェクト上のどっかのオブジェクトにアタッチする(まぁ、普通に1のオブジェクトのほうが良いと思う)
3.Zenject BindingコンポーネントのComponentsにアタッチしたいオブジェクトを設定する。
4.[SerializeField]って書いてるところを替わりに[Inject]って定義する
5.終わり!

え、アタッチしたいオブジェクトが複数あって違うオブジェクトをそれぞれアタッチしたい?非MonoBehaviourとかのクラスを注入したい?シーン上に置かれてないやつをアタッチしたい?

うるせぇ!知るか!どういう設定でインストールするかっていうのはスクリプト書けば設定できると思うけど、そこまでは知らん!
↓でも読んどけ!

light11.hatenadiary.com

~~~~Context


シングルトンの消し方についてだ!
私はキー入力まわりをZenjectでいい感じにすることが多い。
人によってはシーン制御の共通系スクリプトとか、ネット通信周りとか、サウンドマネージャー的なやつをZenjectに任せるんじゃないか?しらんけど。

で~~~~Contextだけど、SceneContextとかProjectContextとかGameObjectContextとかあるけど、
DI対象を決めるために種類が分かれてるんだよね。

SceneContextならScene上にあるやつ全部、ProjectContextならプロジェクト上全部、GameObjectContextならアタッチしたゲームオブジェクトだな。

ぶっちゃけ面倒くさい人はProjectContextでも良いとは思うけど依存性を考えると一番はGameObjectContextよな。
しかし、私は面倒くさい!ので間をとってSceneContextを使う!一般的にもSceneContextが普通じゃないですかね?しらんけど。
シーンまたいで手軽に設定したい人はProjectContextを使うと良いんじゃないですかね。

肝心のSceneContextの出し方?他の記事をググれ。
で、実際に~~~~ContextでBindする方法だけど、それは、スクリプトを書かないといけない。

そのBind設定のスクリプト書くときに、このBindするスクリプトはプロジェクト上で一つだけ!とか複数個欲しいだとか生成するタイミングだとかは定義出来たはず。

備忘録用に私が使うキー入力のスクリプトだけ書いておく。

using xxxx.Util.Input;
using Zenject;

namespace Util.ZenjectInstaller {
    public class InputInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<IInputEventProvider>().To<InputEditor>()
                .AsCached();
        }
    }
}

何やってるかというと、[inject] IInputEventProvider hoge; って書いてるところにInputEditorをシングルトン的な感じでBindしてねってこと。
なんでIInputEventProvider とインターフェースを定義してるかっていうとInputEditorのところが差し替えやすいから。
TestInputとかInputSmartphoneとか別途作って定義おけばビルドごとに差し替えが出来たり楽よね。
NPCとかで擬似的にキー入力させたりとか?モンキーテストしたりとか、媒体によって変えたりとか?疎結合だからキーコンフィグ周りのコードもスッキリしそう。
こういうところをこんな感じでインターフェースを使って疎結合化することを依存性逆転の原則に則った設計っていうけど今回の筋からは外れるので解説は割愛。

「書いたスクリプトをどこにアタッチさせて利用できるようにするのか?」とか詳しい設定とかは他の人の記事を読んでくれ。説明面倒くさい。

qiita.com

マルチシーンで使いたいけどProjectContextは使いたくないとき


使いたいことがあったので書いておく。
それぞれのSceneでSceneContextを定義して親になるSceneContextを用意。

親のSceneContextに名前を設定する。

f:id:OrotiYamatano:20200808034224p:plain
親の名前

子供のSceneContextに親のSceneContextの名前を指定する。

f:id:OrotiYamatano:20200808034343p:plain
子供の方に親の名前を指定

後は、先に親のシーンから読み込まれるようにしておけばマルチシーン構成でもいい感じに親子関係なくBind出来るはず。

まとめ


雑にかきましたが、自分用です。
特に見る人も居ないだろうし、適当でいいよね?
他の人の参考になったら儲けもの程度に考えておきます。
この解説じゃ全然物足りないよーって人はimoさんの本がとても良いので買うと良いと思います。

booth.pm

では、アデュー。