ZenjectのScene Decorator Contextの使い方

最近、UnityのプロジェクトではZenjectを使っており、 1年くらい実プロジェクトで運用してようやくコツがわかってきた気がする。

しかしながら、 Scene Decorator Contextがどんなものかをあまり理解しておらず調査したのでメモ。

そもそもZenejectのContextと親子関係

ZenjectのContextには以下の4つのタイプがある

  • Project Context
  • Scene Context
  • Scene Decorator Context
  • GameObject Context

この中でProjectContextはゲーム起動時に必ず生成される。 そして全てのContextの親である。 そのためどんな環境でも利用すべき神的な何をBindするには良い。 ただし、テストのことも考慮するとテスト環境でも利用するマジでゴッドなものに限る。

そしてScene Contextはシーンをロードすることで、子となる新しいContextを生み出すことができる。 GameObject ContextもScene Contextの子になることができるし、Factoryを使うことで動的に子となるContextを生み出すことができる。

f:id:miki05:20180724005934j:plain

こんなカンジでContextの親と子を設定したり、 ある意味のある塊や粒度ごとにContextを切り分けることで、 カプセル化やContextの差し替え、動的な生成・破棄などが可能になってくる。

ただし、親子関係が存在すると言うことは、 Context間に依存関係が生じるということで慎重に用いる必要がある。

Scene Decorator Context

Scene Decorator Contextは親子関係を作らずにContextの分離と差し替えを可能にする仕組みだ。 もしくは、Decoratorパターンのように、対象Contextに何の変更もなく機能を付与する仕組みと言える。

たとえばある1つのゲームが起動している状態を3つのScene Contextに分ける場合を考える。

  • Stage Scene Context
  • UI Scene Context
  • Main Scene Context

ちなみに、Contextを分割するメリットは、 StageやUIをデバッグ用や別のセットに差し替えたり、個別にテスト等を行えるようにすることが主だと思う。

で、この場合、Main Scene ContextがEntryとなってゲームが駆動する場合、 Scene Contextの親子関係を作るとしたら、いくつかパターンがあるが、例えば以下のようなものが考えられる。

f:id:miki05:20180726221652j:plain

Zenejectは途中のバージョンからParentを2つ以上もつことができるようになったので もしくはこう f:id:miki05:20180726221704j:plain

反面、Scene Decorator Contextのイメージは以下のようなカンジだ。

f:id:miki05:20180726222015j:plain

つまり、それぞれのScene Contextが親子というより、なんとなく同列なカンジになる。

ここで、親子関係を作る方法と比較して、 Decorateする方法の違いを挙げると、 Decorateする方法は相互にContext内のContainerに参照ができる点である。 親子関係を作る方法に置いて、上記の例のパターンではStage SceneやUI SceneからはMain Sceneを参照できないが、 Decorateする方法はStage SceneやUI Scene、Main Scene内のContextをあたかも同一かのように扱うことができる。

なお、実際Editor上の設定方法は以下みたいなInspectorとシーンの状態になる。 注意点としてはシーンをロードする順番(Hierarchyの順番)はDecorator Scene => Decorated Sceneにする必要はある。

f:id:miki05:20180726212835p:plain

Scene Decorator Contextのメリットは?

Scene Decorator Contextのメリットは 対象のContextや親子関係を変更なく、機能の一部の差し替え、もしくは機能の追加ができる点だ。 たとえば、 特定のケースのときだけDecoreted Sceneを読み込んで機能を付与(対象コンテキストを装飾)することができる。

これは、複雑な親子関係の設定を、ある程度シンプルなものにしてくれる。 Scene Parentingな方法だと、親子の関係を慎重に設計し、 階層構造を意識しながらParent Context Contract Nameとシーンのロードをしなければならない。

Scene Decorator Contextを導入することで、 アプリケーション本筋である大枠の文脈(Context)の依存関係をざっくりと意識するだけでよくなる。

どんなときにDecorator Contextが使える?

未検証だが多分以下のようなUseCaseが考えられるおもう

  • デバック用のコマンドやUIの機能を特定のScene Contextに付与する
  • Parentingとして意識したくないScene Context(大筋外の文脈)の分割
  • チュートリアルフローなど?

そのScene Contextは親子?

正直いうと、Scene Decorator Contextの機能は、 親子関係を工夫することである程度まかなえる気がする。

しかしながら、かねがねZenjectをしばらく利用してきたわけだが、 本当に概念上の親子関係なのか疑問に思うパターンもいくつかあった。 例えば、上記の例だとStage ContextとMain Contextってどっちが親なん?というか親子関係なん?といった具合である。

さらに、Scene Contextの親子関係の依存周りがネックになりやすいという印象を持った。 なにせZenjectはContext同士の依存関係を解決してくれるが、 Sceneの依存関係は自動で解決してくれない。 Zenjectのためにシーンのロードのお膳立てをする必要あり、 そのあたりが複雑になると(何重にもScene Parentingを行うと)わけわからんくなる。 さらに言うとHierarchyビューで親子関係の視覚化が行われないので、とっつきにくいのもある。

そもそも、親を複数持てるということも混乱を招きやすいように思う。 親ではなくDependancyみたいな命名だったらもうちょいわかりやすいが。 で、クラス間の依存解決をサポートするためにDI Containerがあるはずなのに、 Context間の依存に苦しむのは本末転倒感がある。

そのため、親子関係の設定は極力シンプルにしておきたい気がする

まとめ

以上、Decorator Scene Contextについての機能と自分の見解でした。 最後ほうDecorator Scene Context推しみたいな主張になってしまいましたが、 時と場合によって使い分けるべきなんでしょうね。

自分の中では、 以外と使いどころはあるのかなと思っているので、導入を検討してみます。