うどんゲームメモ

ゲームの技術メモをまったりと

UnityにおけるDIパターンとZenject

DI、プログラミングにおける大事な概念の一つです、最近ちょこちょこ言葉を聞く機会が多くなったなと感じています。(PHPなどを触る方はご存知の方が多いでしょう)

自分も最近仕事の都合で、Unity+DIといった形態の開発をしています、まだ広まりが浅い気はしますが。

今回はその仕事の上で得た知見をまとめていこうかなと思います

 

DI(Dependency Injection)

そもそもDIってなんやねん、と

DIとはDependency Injection」の略で、直訳すると「依存性注入」という意味になります。

ゲーム界隈ではあまり聞きなれませんが、思想自体は結構前からあるものです

最近だとポケモンGOでDI思想によるアーキテクチャが採用されていたといった事例があり、Unity界隈では今ホットになってきたのかな?って感じがあります

 

実はこれ直訳するのはよろしくなく、あくまでもDependencyを注入するといった方が正しいのですが・・・まぁそれはさておき

じゃあDIってのはどういうことなの?と、いくつかのサイトでも紹介されていますが簡単に言うと「オブジェクトの提供」です(提供って書くと厳密には違う気がしなくもないですけど)

ある機能があったとして、それがあるオブジェクトのメソッドなりなんなりを使って機能している、これが一番簡単な「Dependency」です

hogeクラスがhugaクラスを持っていて、ある関数をhugaクラスの関数や変数に依存して使ったり、そんな感じ

 

 

今回はどちらかというとDIパターンとはというよりDIパターンをUnityで応用してみた、という試みのメモ書きなので、分からない人は調べてみてください

 

DIとUnityとZenject

じゃあDIをUnityでどう使うの?と

もちろん明示的に仕組みを組んで注入することはできますが、できればスマートに、労力なくやりたい

ってことで既存のUnity用DIフレームワークを使用します

今回使用したのは「Zenject」という無料のフレームワークです

 

github.com

 

ぶっちゃけC#用のDIフレームワークってちらほらあるんですけど(NinjectとかRx制作者のやつとか)Unityをターゲットにするとなるとあんまりないんですね

その中でもZenjectはかなりいい感じに使えるかなと思ったのでまとめていこうかなと

とはいっても紹介するのは触りだけです 

 

 まずヒエラルキーにSceneContextを用意しましょう、これはInstallerを管理したりするもの、と考えてもらえば大丈夫です

ヒエラルキーにオブジェクトを追加する感じでZenject→SceneContextというメニューがあると思うので、その手順でSceneContextを用意します

SceneContextはこんな感じ

f:id:myudon:20170916151649p:plain

 

 

次にInstallerを用意します、これは所謂注入係です

適当なスクリプトにMonoInstallerを継承しておけばよいです(ここについては後で少し捕捉します)

今回は何も書きませんが、ここでInstallBindingをoverrideし、注入についての設定などをしたりします

f:id:myudon:20170916152035p:plain

これを適当なオブジェクトにアタッチしておいてください、今回はInstallerというオブジェクトを用意しました

アタッチしたら、このInstallerを先ほどのSceneContextのInstallersの部分に入れていきます

f:id:myudon:20170916152805p:plain

 

では次に流し込むものを用意していきましょう、今回は適当な変数を持ったモデルを用意しました

f:id:myudon:20170916152143p:plain

 

これを適当なオブジェクトにアタッチして、ZenjectBindingというコンポーネントをアタッチしていきます

ZenjectBindingはBindingとついているように、流し込むコンポーネントをBindするコンポーネントです、Componentsの部分に今回流し込みたいGameModelを入れることでZenject側が自動で流し込む設定を行ってくれます

 

 

f:id:myudon:20170916152415p:plain

 

そしてこのGameModelを提供してもらうクラスを用意します(今回はReaderとしました)

受け取りたいクラスを変数として用意して、アトリビュートで[Inject]とつけてあげます

こうすることで、Zenject側が自動でこの_modelに、Bindingで指定したGameModelを流し込んでくれるようになります

f:id:myudon:20170916153059p:plain

 

SceneContext,Installer,GameModel,Readerの4つのオブジェクトがそろったところででは再生をしてみます、すると!

f:id:myudon:20170916153406p:plain

流し込んだ値を取得することができました!

このようにして、Injection周りを自分で書くことなく、オブジェクトを取得できるのです

 

Bindするためにシーンに置いとかないといけないじゃん、動的にやるにはどうするの?という疑問がわくかと思いますが、そもそもInstallerを用いた注入はあくまでもラップされた方法であり、動的にBindして注入することも全然可能です

その際コンテナにBindしてそれを流し込む(Resolve)といった一連の流れをケースに合うように書かないといけませんが・・・、まぁそれもそんなに手間じゃないので正直シングルトン参照とかよりよっぽど散らからないと思います

 

また、先ほどスルーしましたがSceneContextにあるPrefabInstallerならPrefab,ScriptableObjectInstallerならScriptableObjectをBindして流し込めちゃいます

Zenject様様ですね

 

Unity設計におけるDI思想

ここまでDIをUnityで!みたいなことを書いてきたわけなんですが実際どう有効なのか

 

例えばUnityで様々なゲームモデルのパラメータを扱うとき、よくシングルトンのマネージャクラスなどで置き、そこから取得するといった方法がよく使われると思います。

確かにシングルトンは便利なんですが、実質グローバルであり、かなりの密結合だといえます

依存関係なども分かりづらくなり、オブジェクト指向的な観点からも、おや?となる部分が多くなってしまいます

そういった点で、DI思想というものはグローバルにアクセスするのではなく、あくまでモジュール単位に切り離してオブジェクトを渡せば良いため、密になる部分を減らすことができます。

 

より良い設計を、より柔軟な設計を、と追い求めていくならば、Unity設計において知っておいて損はないと思います(かといってなんでもかんでも注入すればいいじゃんとなってコンポーネント思想がぐらつくのはあれですが)

 

正直自分も学びたての設計なので、突っ込みどころがあるかと思いますが、その時はコメントでガンガン突っ込んでください、勉強になります

 

後、DIといえばパフォーマンス関係も気になるところで、その点においてRx制作者の方が作っていた「MicroResolver」も試してみたいところですね

github.com

 

 

長ったらしくなりましたが、ようはZenject使うと捗るよってことです

意見やコメントお待ちしております

 

参考にしたリンク

DIとは?DIコンテナとは?試してみた(前編)[PHP][DI] - あざらし備忘録。

 

DI・DIコンテナ、ちゃんと理解出来てる・・? - Qiita

 

neue cc - MicroResolver - C#最速のDIコンテナライブラリと、最速を支えるメタプログラミングテクニック