MVPとZenjectを使って疎結合なワークフローを構築し、デザイナーとの作業分担の可能性を探ってみます。
- ワークフローの疎結合
- View用のSceneを作る
- Model用のSceneを作る
- Presenter用のSceneを作る
- 動作させてみる
- 仮のModelでViewをチェックするワークフロー
- 仮のViewでModelをチェックするワークフロー
- 関連
Unity2018.2
Zenject 7.3.1
ワークフローの疎結合
Unite2018でこんな講演がありました。
www.slideshare.net
簡単にまとめると、コードが密結合になってるとデータやワークフローも密結合になっちゃって、
それはつまり色んな人が関わるということなのでイテレーションが回しづらい&同時作業がしづらい。
結果としてクオリティ上がりづらいよね、だからワークフローの疎結合を目指そうね、ということです。
また、当然関係者が多くなる(プロジェクト規模が大きくなる)ほど疎結合を進めるべきです。
このあたりの考え方には全面的に賛成なので、この記事では簡単に実装してワークフローの疎結合を試してみます。
なお上記の講演ではMessage Busという手法を紹介していますが、本記事ではMVP+Zenjectで実現します。
MVPやZenjectの知識が前提となりますのでわからない方は関連の節を参照してください。
View用のSceneを作る
まずViewを作っていきます。
まずGUIとしてはこんな感じのシンプルなテキストボタンを作ります。
次にこれを操作するViewクラスを作ります。
using UnityEngine; using UnityEngine.UI; public class ExampleView : MonoBehaviour { [SerializeField] private Text _buttonText; public void SetButtonText(string text) { _buttonText.text = text; } }
ボタン名を設定するメソッドを持っているだけです。
次にZenjectを設定します。
まずは先ほどのExampleView
をZenjectBinding
でDIできるようにします。
さらにContextが必要なのでこのSceneにSceneContext
を置きます。
これは後ほどPresenter用のSceneとScene Parentingを行うので、Contract Name
をExampleView
としておきます。
Model用のSceneを作る
次にModelを作ります。
Modelは後々のことを考えてInterfaceを定義して、
public interface IExampleModel { string GetName(); }
これを実装したクラスを作ります。
using UnityEngine; public class ExampleModel : MonoBehaviour, IExampleModel { public string GetName() { return "Button Name"; } }
ただボタンの名前を返しているだけです。
MonoBehaviourにする必要もないしModelって名前のModelもどうかと思いますが、わかりやすさ重視です。
これを新しく作ったScene上に置いたGameObjectにアタッチします。
そしてViewのときと同様、ZenjectBinding
でDIします。
ただし今回はインタフェースを定義しているので、Bind Type
をAll Interfaces
にしています。
最後にScene Context
を作ってContract Names
にExampleModel
と入力します。
Presenter用のSceneを作る
次にPresenterを作ります。
using UnityEngine; using Zenject; public class ExamplePresenter : MonoBehaviour { [Inject] private IExampleModel _exampleModel; [Inject, SerializeField] private ExampleView _exampleView; private void Start() { _exampleView.SetButtonText(_exampleModel.GetName()); } }
IExampleModel
から名前を取得してExampleView
にセットしているだけです。
IExampleModel
およびExampleView
はDIによって取得します。
これをまた新しく作ったScene上に置いたGameObjectにアタッチします。
最後にSceneContext
を作ります。
これはModelとViewのSceneContext
の子とするので、
Parent Contract Names
にExampleView
とExampleModel
を入力します。
動作させてみる
それでは動作させてみます。
親のSceneContext
は先にロードしておく必要があるため、次の手順でマルチシーンを構築します。
- View用のSceneを開く
- Model用のSceneを追加
- Presenter用のSceneを追加
ヒエラルキーはこんな感じになります。
この状態で再生すると、
ボタンのテキストが切り替わりました。
仮のModelでViewをチェックするワークフロー
さて今回の目的はワークフローの疎結合化です。
例えばデザイナーがViewを作るときにAPIが未完成だったとしたら、仮のデータを返すModelが欲しいところです。
こんな時にはModelのインタフェースを実装した別のModelを作っておきます。
using UnityEngine; public class ExampleMockModel : MonoBehaviour, IExampleModel { public string GetName() { // 仮のデータを返す return "Mock Button Name"; } }
これを使ったModelのシーンをもう一つ作っておきます。
そして前節のように動作させるときに、Modelのシーンを仮データ用のものにします。
- View用のSceneを開く
- 仮データを返すModel用のSceneを追加
- Presenter用のSceneを追加
ヒエラルキーはこんな感じです。
これを再生すると、
仮のModelから取得した名前でボタンのテキストが更新されました。
仮のViewでModelをチェックするワークフロー
次に、エンジニア側のワークフローを考えてみます。
例えばまだデザイナーがViewを作っていない状態だけど作業を進めなければいけないとします。
こんなときには仮のView用のシーンを作ります。
置いてあるGUIは仮のものですが、アタッチしているExampleView
やZenject周りの設定は元のViewシーンと同様にしておきます。
そして動作させるときに、Viewのシーンを仮のものにします。
- 仮のGUIを置いたView用のSceneを開く
- Model用のSceneを追加
- Presenter用のSceneを追加
ヒエラルキーはこんな感じです。
これを再生すると、
仮のViewで正常に表示されました。