はじめに
その1では、ModelとViewだけでのUnity設計は非常に難しいことが分かりました。
今回は、それを解決するために現れたControllerやPresenter、ViewModelという存在について書いていこうと思います。
MVXの大体の把握
非常に詳しい記事があるので、大まかな解説はそちらにお任せしたいと思います。今回は以下の記事のMVXの定義に従って解説を進めていきます。
厳密な話は以下で扱っていくので、詳しい理解と言うより、大まかなイメージを図で把握することが大事です。
Webアプリケーション開発者から見た、MVCとMVP、そしてMVVMの違い
UnityにおけるController
残念ながら、今回に定義に従う限りはUnityにおいて絶対にMVCの実装はできません。
なぜなら、原初のMVCもModel2MVCも、「ユーザーからの入力を直接受け取る」のがControllerだからです。
Unityでこれを実現すると、ControllerはUnityの仕組みに乗っからないといけません。しかし、Unityにおいては「Unityの仕組みに乗っかることが不可避なものこそがView」という立場に立っているので、ControllerはViewと同一の存在にならざるを得ないのです。
UnityにおけるPresenter
Unityの機構に則ったうえでMVPを実装することが可能です。実装方法には以下の2つが存在します。
それぞれの利点と欠点については第3章で考察していきます。
Passive View
- 入力:PresenterがViewから低レベルの入力を受け取り、いい感じに処理してModelのメソッドを呼ぶ。
- 表示:PresenterがModelの情報を取得しつつViewに情報を与える。
監視View
- 入力:PresenterがViewから低レベルの入力を受け取り、いい感じに処理してModelのメソッドを呼ぶ。
- 表示:ViewがModelを監視して、Modelの状況から情報を得て組み立てる
UnityにおけるViewModel
ViewModelにおいては、双方向データバインディングという手法が取られるのが最大の特徴です。
しかし、Unityでは残念ながら、双方向データバインディングの仕組みがありません。
これがなければ、Passive View型のMVPとあまり変わりませんね。
ただし、UniRxを用いれば単方向でのバインディングっぽいことは可能なので、バインディングがUnity開発においてどの程度有効であるか、適切な実装方法はどのようなものかについての考察を第3章で行います。
ここまでで、実質的にはUnityにおいてはMVPしか選択肢がないということが分かりました。実際、Unityの設計で検索してもMVPが多くヒットするのはこれが理由になります。したがって、MVPについての考察を進めていきましょう。
MVPにおけるユーザーからの入力
ModelをViewに反映させる方法は、MVC,MVP,MVVMで大きく異なっています。しかし、ユーザーからの入力の扱いについては一貫している思想があります。
それは、**「ユーザーからの入力は直接Modelに行くのではなく、何らかの存在を経由してModelを呼ばせよう」**という部分です。
これが、前回挙げた問題のうち、ユーザーの入力の問題を解決することにつながるからです。
入力におけるPresenterの特徴
Viewから低レベルな画面依存の入力を受け取り、画面依存のデータを管理して、Modelが純粋性を失わない高レベルなメソッドの呼び出しにつなげることです。
また、PresenterはUnityに依存ないです。
Model,Viewと違って「それ以外」として扱われ、「値をつなぐだけであんまり操作しない受け渡し役」などと思われてしまい「こいつは何のためにいるんだよ!直接渡せよ!」と思われてしまいがちなPresenterですが、この特徴を把握すれば、適切な責務を果たすPresenterを作成することが比較的容易になります。
Presenterは、Viewが持つにも、Modelが持つにも辛い、「ダブルクリックのための一次変数」「マウスの速度」「UIでロックがかかっているときはデータを変更できない、という仕様の時の、ロックしているか否か」「3回コマンド入力したらロジックにデータを渡す、という仕様のときのコマンド入力回数」などのデータをすべて持ちます。
「画面に依存するデータかつUnityに依存しないデータ」というのが判断基準です。
そして、Presenterが持つことが出来るロジックは、Modelに依頼するかどうかの判断に終始すべきです。
Modelの代わりとなるようなロジックをPresenterに持たることは許されません。
Modelとの違いの判断基準は、「画面が必要なデータならPresenter、純粋にゲームロジックとして必要ならばModel」です。
Presenterを導入することの利点
まず導入した目的から簡単にこの2つは理解できると思います。これが出来るだけでも十二分にPresenterの存在意義があります。
- Viewが入力をPresenterに伝えるだけの存在になり、非常にシンプルに保てる
- Modelの純粋性も保てるので、再利用性が失われない
加えてPresenterを内部に仕込むことによって新しいメリットも得られます。
1. Presenterは純粋なC#なので、Unityを起動せずとも書こうと思えばテストが書ける
この「純粋」はModelのように、画面に依存しないゲームロジックであるという意味ではなく、単にUnityを気にしないでいいという意味です。「書こうと思えば」という表現なのは、マウスポインタの座標などを受け取るPresenterのテストは結構苦行になることも多いという意味です。しかし、ボタンぐらいだったら手軽にテストできます。
したがって、この点は「発生したら大きいメリットだし、発生しなくてもModel・Viewの2人体制と同じレベル」であるといえますから、総合的に見てメリットです。
2. 入力処理を一本化できる
もしもViewとModelを直接つなげてしまえば、UnityのButtonコンポーネントのOnClickイベントで直接Modelを参照してしまったりします。これは曲者で、シーン上のゲームオブジェクトを全部見ないとどこが呼ばれるのか、どのタイミングで呼ばれるのかが把握できないというViewの複雑性の問題が発生してしまいます。
しかし、「すべてのViewは単純にPresenterに入力を通知する」という原則を徹底すれば、すべての入力はPresenterのメソッドの形で把握できます。一覧性が高いことや、純粋なC#コードのメソッドであることから、Visual Studioの補完・検索などの機能の恩恵を受けられる点でも、Unity Editor上にすべてが存在するよりも圧倒的に楽です。
3. メソッドが呼ばれない不具合はPresenterが原因
「適切な時に適切なメソッドを呼べば、正しいデータが設定される」はModelで保証します。
これが保証しやすいようにModelは画面のことを一切気にしないでいいという環境を整えています。
また、テストを書いて保証することも楽なように設計しています。
「画面でボタンを押したら通知する」はViewで保証します。
これの保証は非常に簡単です。なぜなら、Viewは低レベルの情報をそのまま渡すだけの構造だからです。
以上のことから、「ボタンを押したのにうまく機能しない」は、たいていの場合Presenterに原因を押し付けられます。
これはViewとModelを分離したときに、「レイアウトが変えたときに出るバグは全部Viewが悪い!」と確定させることで保守性を上げることが出来たことと、本質的に同じ事です。
Presenterを容易に実装できるようになるライブラリ「UniRx」
Presenterを実装する際に、他の記事ではUniRxというライブラリと一緒に紹介していることも多いです。
これは、Presenterを実装する際に非常に便利なイベント・通知の仕組みを持っているからです。
しかし、MVP+UniRxという組み合わせの記事が多すぎて、初めて学ぶときには「UniRx=MVP」かのように勘違いしそうです。
もちろん、UniRx無しにMVPの設計をすることは十分可能であり、また、UniRxを使えばMVPになるというものではありませんので注意しましょう。
UniRx自体の解説はほかに譲りますが、UniRxを使えばPresenterが簡潔になる、という例を実践編で紹介します。
おわりに
第2章ではMVXの必要性と、Presenterの入力における有用性について書きました。
経験的には、役割を理解されないPresenterは「Modelを飲み込んで巨大化する」か「極限まで薄くなって値をちょっと加工する程度の存在になる」のどちらかの運命をたどりがちな気がします。Presenterには明らかな責務が存在するので、その点を忘れないように設計することが重要です。
次回はPresenterの出力における役割について、最適な方法を考察していきます。