昨今のUnity開発におけるMVP設計思想についてと、それの適用可否の話
こちらも合わせてどうぞ
はじめに
こちらの記事を読んで、最近Unity開発でよく言われるようになった
MVPについてだいぶ浸透(あるいは導入)してきたのではないかと思った。
ただ、すべてのゲームがこの思想でカバーできるかというとそうでもないと思っている。
「そりゃ仕様によって最適解は変わるだろw」と思うかもしれないが
言いたいことはそういうことではなく
MVP設計というのは元々Web業界で浸透した設計であり
WebのフロントはユーザーがUIを操作するものであるということだ。
つまりこの設計思想をそのまま、あるいは少しのアレンジを加え適用できるのはユーザーがUIを操作することが基本となる
モバイル向けソーシャルゲームアプリということである。
それを意識せず、ユーザーがコントローラを持ち、キャラクターを操作するタイプのゲームなど(あくまで、など)で
「MVPっていうのがいいらしい」
「とりあえず導入しよう」
でどこまでがModelでどこまでがViewでPresenterをどこまで定義すれば良いのかが曖昧のまま設計が迷走する例をよく見た。
MVPの具体的な活用法に対しては先ほどの記事を読んでもらえばわかると思うので
今回はModel-View-Presenterに対して、各レイヤーの解釈について特にUnityゲーム開発における持論を書こうと思う。
ある人にとっては間違っているかもしれないが、ある人にとっては正解への道標になるはずだ。
先に結論を書くと、Viewは積極的に拡大解釈しろという話だ。
あとUniRxとMVPの理解は前提で。
レイヤーの明確な責任
まずModel-View-Presenterにおける役割を明確にするのと同時に
ゲームを作るために必要なレイヤーを全て洗い出す。
MasterData
一応書く。
ゲーム中の経験値テーブルや、アイテムのId、価格などのデータ。
Model
ゲームを構成するマスターデータをもとに、ユーザーの現在所持しているアイテム数や、経験値などのいわゆるセーブ対象になるデータを管理しているもの。
ゲームを進行するごとに、書き換えられていく。
役割として
- データを保持する
- データの書き換えロジックを、メソッドとして外部に公開する
- データが書き換えられた時、通知を発行する
この役割さえ守っていれば、中身でどれだけインスタンスを生成しようと、どんなロジックを持とうと構わない。
逆に言うと、これ以外で役割を持ってしまってはいけない。
そしてModelは依存先を持たず、どんな状態でも自身をインスタンス化できるようにしなければならない。
View
プレイヤーが実際に見る画面と勘違いされやすい(名前的に)。
本質的な役割は
- ユーザーが実際に見る画面を構成し、表示する
- 画面を更新するメソッドを外部に公開する
- ユーザーの操作を通知する
最初に書いた通り、UIを操作するだけであるならこのUIが操作されたという通知を飛ばすだけで済む。
その通知の結果、Viewの更新を受け取るだけで済むからだ。
ただ実際にはそうはいかない。
なぜなら、キャラクターを操作できるゲームであるなら、キャラクターの位置が更新され、その結果アイテムを拾えたりする領域に入ったりするなど、複雑であるからだ。
そして愚直にMVPに当てはめるのであれば、コントローラ操作というViewが通知を発行し、キャラクターのモデルが位置の変更を通知で管理しなければいけなくなる。
はっきり言ってそんな設計は冗長すぎてクソだ。
なので後ほどViewの拡大解釈と自由設計について記述する。
ただ、上記3つの役割自体は変わらないので厳守しなければならない。
そして、Modelと同じく、Viewはどんな時でもインスタンス化可能でなければならない。
Presenter
具体的な機能の提供者である。役割として
- Viewからの通知を受け取り、Modelが外部公開しているメソッドを呼ぶ
- Modelのメソッドを呼んだ結果、Modelからの通知を受け取り、Viewが外部公開しているメソッドを呼ぶ
単体では機能しないViewとModelをつなげ、ゲームとして成り立つような機能をロジックとして提供する
となっているが、例として
- アイテムを拾える領域に入った通知がViewから飛んでくるので
- その通知が飛んできた後で他のViewからボタンが押された通知が飛んできたら
- Modelが公開しているアイテムを一個増やすメソッドを呼び
- アイテムが増えたという通知を受け取り
- Viewが公開している「アイテムを拾った演出」を出すメソッドを呼ぶ
という処理を行うといえば、わかりやすいだろうか。
Viewが作り込まれれば、あとはPresenterを量産するだけでゲームを拡張していくことが可能になる。
以上がレイヤーの責任の話。
Viewを積極的に拡大解釈していく
ここまでの話で、何が一体問題なのかというと
ゲームはユーザーの操作がバリエーション豊富すぎてViewの役割がブレやすい
というところだ。
例として、ゲームの操作キャラクターについての話をした。
ここで考えたい
キャラクターとはどこのレイヤーに属するのか?
ゲームの1機能としてキャラクターを見るのであれば
Model-View-Presenterにまたがって処理を書かなければいけない。
でも、ユーザーの操作だし、キャラだって要は画面の1要素なのだからView単体で記述しなければいけない。
どっちだろうとなる。
結論としては、どっちも正しい
なので
Viewとしての役割を厳守できれば、内部の処理はView内で完結されたMVPで記述されても良いのでは
と考えることも可能である。
そしてさらに拡大解釈するなら
Viewとしての役割さえ守れていれば、中身の設計はなんでもいい
だ。
例として、クラス図を書いてみた。
ViewがCharacter全体の各コンポーネントで、ゲームに関連するHpなどをModelに移譲することで見通しが良くなる。
CharacterPresenterはCharacterの各コンポーネントの通知を使って、Modelを更新すれば良くなるが、既存のPresenterとは違う点がここで生まれる。
それは通知を発行したViewは自身で更新を行うということだ。
ダメージを受けた時に、ダメージモーションの遷移などをいちいちPresenterから発行されていては、Character自身が独立して動かなくなってしまう。
なのでPresenterの役割としては
Viewの通知を使い、Modelを更新した結果、必要であれば他のViewに対しての更新を行う
になる、ダメージの結果HPゲージを更新する処理を呼ぶとかね。
最後
蓋を開けてみればなんだそんなことかと思うかもしれないが
責任を明確にしないままMVPを導入するとカオスになるのを身を以て経験している。
うまくレイヤーを定義してやり、そのレイヤーに属するものは何なのかを考えよう。
そして、責任さえ守れていれば、レイヤーの中身は自由に解釈して構わない
そういう結論を今自分では出している。
推敲されておらず、勢いだけで書いた文なのでわかりづらいかもしれないが
許してね^^
何かあればTwitterまでどうぞ。
あとこんなこと書いてる暇あるならゲーム作れって言われそうなのでゲーム作ります・・・。