はじめに
今回は、MVPの実現方法として、ViewがModelを監視して、毎フレーム自分を書き換えていくという監視型Viewの考え方について、Unityでの有効性を検討していきます。
監視型Viewとは
ViewはModelを読み出す権限を持っています。そして毎フレームにModelの状況を監視します。
そして、Modelに変更が生まれた時点でViewを書き換えます。
監視型Viewの実装
Modelが内部状況をpublicかつ読み取り専用な変数として提供します。ViewはModelへの参照を持ちます。Viewは直前のModelの値を常にキャッシュしておき、Update()
で変更を検知します。
監視型Viewが不適切だといえる理由
ModelにもViewにも属さないデータの行き先が用意されていない
これは、第2章その1で挙げた問題点です。詳しくはそちらの記事をご覧ください。
監視Viewはデータ出力における演出関係の変数の行き場がないという問題を完全に無視しています。
それだけでも監視型Viewがゲームでは使いづらい概念であることが分かると思います。
ゲームではModelの変化以外の要因でも見た目が変わることがある
監視型Viewは根本的に「ViewはModelを映す存在である」という発想に立っています。
しかし、ゲームではこの発想が常に正しいとは限りません。例をいくつか挙げてみましょう。
- キャラの着せ替え画面で閲覧しているときに、時間経過で勝手にキャラが回転する。Modelで保持しているキャラの服装は変化しない
- レースゲームにおいて、離脱した後に、元の場所に復帰するときにアニメーション演出が発生するが、Modelで保持しているゲーム上での車の位置は変わらない(マリ〇カートで崖から落下した後、カートがジュゲムに戻してもらえるなどを想像してください)
などと言った場合、Modelとは無関係に見た目が変わります。
しかし、監視型Viewはこれらを感知する手段がありません。
なぜなら、プレイヤーの入力は全部Presenterに投げていて、ViewからPresenterに問い合わせをすることは禁止だからです。
監視型ViewはModelだけを見る存在です。
これを解決するためには「ModelにView変更用のフラグを用意する」などModelの純粋性を破壊するようなことをせざるを得なくなります。監視型ViewはModelが変更されないのにViewが変更されるような処理に弱いのです。
毎フレーム監視するコードはパフォーマンス・可読性ともに良くない
毎フレーム監視している場合
- 直前フレームで値が何だったかをViewに覚えさせる(Viewのコードが単純に複雑化する)
- 毎フレームModelの情報に従ってViewを更新をする(パフォーマンス的に劣る)
のうちどちらかをしないといけません。
また、UIの仕様がややこしくなっていくにつれて、2フレーム前のキャッシュだったり、3秒前のキャッシュだったりを管理する必要が生まれてきます。(典型的にはキーの長押しなど)この手のデータは、「単純なView」という原則を著しく破壊します。なぜなら、フレームという低レベルの仕組みを強く意識したコードになるからです。
なお、どうしても監視型Viewを使いたい場合には、UIの規模が大規模にならないうちはパフォーマンスへの影響は微々たるものなので、どちらかと言うと毎フレーム機械的に再生成してViewを薄く保つことを勧めます。
Viewの検証にModelを使いたくなるので、独立したテストが難しい
監視型Viewをテストするには、毎フレーム値を提供しなければなりません。
こういうプログラムをModelとは別にテスト用で提供するのはそれなりに手間がかかってしまいます。
結果、ModelとViewの独立性が下がってしまうことを許容してでも、動作確認にModelを使ってしまい、保守性を下げてしまいます。
ModelがViewを意識してしまう
Viewを小さくしようとすると、どうしてもModelはViewで加工の手間が小さくなるようにView寄りのデータを提供したくなります。結果的に、Modelとは関係ない「カードの表示アイコンの色(RGB)」とか「設定画面のチェックボックスの成否」とかをModelに定義していしまうことが起こりやすいです。
Modelの純粋性が下がることのデメリットはここまで強調してきた通り「Viewを意識した項目のテストはViewを起動しないとできない」という点で現れます。
ViewがModelを監視するロジックが複雑化する
Modelの変数を監視する、とひとくちにいっても、かなり複雑化します。ゲームならなおさらです。
例えば、ボードゲームを作る状況を考えてみましょう。この手のゲームでは、Modelは「各マスに存在する駒」を二次元配列として提供していたと考えるのが自然だと思います。
ここで「駒が1マス進む」というイベントが発生したとします。すると、ViewはModelの変更を検知するわけですが、「二次元配列のどのマスのインスタンスがどのマスに移動したか」を調べるのは、結構ややこしいことになります。二重forや大量のキャッシュなど、容易に複雑化を生みます。そして、Viewにややこしいロジックが存在すること自体のデメリットもあるので、非常に悪い設計を生みやすいです。
どういう場合に監視型Viewが適しているのか
「絶対に演出などを目的とした変数・条件分岐を追加しないと決めているシーン」に利用する分には全く問題は発生しません。
メリットは以下の2つです。
- Presenterはデータの流れが1方向になり、理解が容易になります。
- Presenterのデータ加工コストをViewに持たせることができるのでPresenterが楽になるでしょう。(なおこの加工が複雑だった場合、Presenterで書いた方がマシですので、監視型Viewによる実装はやめましょう)
具体的に役に役に立つ場面としては
- 3D機能などを目当てに業務用ツールをUnityで作成する場合
- プロトタイプとして演出以外を先に作ってしまって、そのあと改めて演出を含むシーンを新規作成する場合
では有用な実装方法になる可能性があります。
特に3D機能を持つ業務用アプリに関してはOpenGLやDirectXを触って作成するよりUnityの方が開発コストが低くなることも多いように思います。
おわりに
Unityにおける監視型Viewは不要に巨大なViewを招きやすいようです。
やはりゲーム開発は本質的に複雑であることを痛感させられました。