【Unity】MVPとZenjectでデザイナーときっちり作業分担する方法

MVPとZenjectを使って疎結合なワークフローを構築し、デザイナーとの作業分担の可能性を探ってみます。

Unity2018.2
Zenject 7.3.1

ワークフローの疎結合

Unite2018でこんな講演がありました。

www.slideshare.net

簡単にまとめると、コードが密結合になってるとデータやワークフローも密結合になっちゃって、
それはつまり色んな人が関わるということなのでイテレーションが回しづらい&同時作業がしづらい。
結果としてクオリティ上がりづらいよね、だからワークフローの疎結合を目指そうね、ということです。

また、当然関係者が多くなる(プロジェクト規模が大きくなる)ほど疎結合を進めるべきです。

f:id:halya_11:20190104151157p:plain
【Unite 2018 Tokyo】Unityにおける疎結合設計 ~UIへの適用事例から学ぶ、テクニックとメリット~

このあたりの考え方には全面的に賛成なので、この記事では簡単に実装してワークフローの疎結合を試してみます。
なお上記の講演ではMessage Busという手法を紹介していますが、本記事ではMVP+Zenjectで実現します。
MVPやZenjectの知識が前提となりますのでわからない方は関連の節を参照してください。

View用のSceneを作る

まずViewを作っていきます。
まずGUIとしてはこんな感じのシンプルなテキストボタンを作ります。

f:id:halya_11:20190104151734p:plain

次にこれを操作するViewクラスを作ります。

using UnityEngine;
using UnityEngine.UI;

public class ExampleView : MonoBehaviour {

    [SerializeField]
    private Text _buttonText;

    public void SetButtonText(string text)
    {
        _buttonText.text = text;
    }
}

ボタン名を設定するメソッドを持っているだけです。

次にZenjectを設定します。
まずは先ほどのExampleViewZenjectBindingでDIできるようにします。

f:id:halya_11:20190104152140p:plain

さらにContextが必要なのでこのSceneにSceneContextを置きます。
これは後ほどPresenter用のSceneとScene Parentingを行うので、Contract NameExampleViewとしておきます。

f:id:halya_11:20190104152314p:plain

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します。

f:id:halya_11:20190104153402p:plain

ただし今回はインタフェースを定義しているので、Bind TypeAll Interfacesにしています。

最後にScene Contextを作ってContract NamesExampleModelと入力します。

f:id:halya_11:20190104153523p:plain

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 NamesExampleViewExampleModelを入力します。

f:id:halya_11:20190104153918p:plain

動作させてみる

それでは動作させてみます。
親のSceneContextは先にロードしておく必要があるため、次の手順でマルチシーンを構築します。

  1. View用のSceneを開く
  2. Model用のSceneを追加
  3. Presenter用のSceneを追加

ヒエラルキーはこんな感じになります。

f:id:halya_11:20190104154305p:plain

この状態で再生すると、

f:id:halya_11:20190104154331p:plain

ボタンのテキストが切り替わりました。

仮のModelでViewをチェックするワークフロー

さて今回の目的はワークフローの疎結合化です。
例えばデザイナーがViewを作るときにAPIが未完成だったとしたら、仮のデータを返すModelが欲しいところです。
こんな時にはModelのインタフェースを実装した別のModelを作っておきます。

using UnityEngine;

public class ExampleMockModel : MonoBehaviour, IExampleModel {

    public string GetName()
    {
        // 仮のデータを返す
        return "Mock Button Name";
    }
}

これを使ったModelのシーンをもう一つ作っておきます。
そして前節のように動作させるときに、Modelのシーンを仮データ用のものにします。

  1. View用のSceneを開く
  2. 仮データを返すModel用のSceneを追加
  3. Presenter用のSceneを追加

ヒエラルキーはこんな感じです。

f:id:halya_11:20190104155010p:plain

これを再生すると、

f:id:halya_11:20190104155050p:plain

仮のModelから取得した名前でボタンのテキストが更新されました。

仮のViewでModelをチェックするワークフロー

次に、エンジニア側のワークフローを考えてみます。
例えばまだデザイナーがViewを作っていない状態だけど作業を進めなければいけないとします。
こんなときには仮のView用のシーンを作ります。

f:id:halya_11:20190104155256p:plain

置いてあるGUIは仮のものですが、アタッチしているExampleViewやZenject周りの設定は元のViewシーンと同様にしておきます。
そして動作させるときに、Viewのシーンを仮のものにします。

  1. 仮のGUIを置いたView用のSceneを開く
  2. Model用のSceneを追加
  3. Presenter用のSceneを追加

ヒエラルキーはこんな感じです。

f:id:halya_11:20190104155536p:plain

これを再生すると、

f:id:halya_11:20190104155624p:plain

仮のViewで正常に表示されました。

関連

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com