YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

UnityのMVP、MV(R)Pを調べたけど、どれが正しいんだ?

Unityで設計をしたい

クラス関係が複雑になり、どこかしらに重複したプログラムが量産され、
循環参照が起こってしまっています。
他人のソースコードは読み辛いものになってしまうことが多いです。
設計をしないと駄目だ……。
私はそう思って、Unityの設計について調べ始めました。

今回はUnityのMVPとMV(R)PについてWeb上で載っている記事を見比べ、
「その上でこうしたら良いんじゃないか?」という私独自の考えたMV(R)Pの設計の公開をします。


いくつかの記事


Webの海をあさり、その中で、素晴らしいと思った記事がいくつかありました。
その中で一番いいと思ったのがMVP or MV(R)Pです。
しかし、Web上には様々なMVP or MV(R)Pの実装方法があり、どう実装すべきか悩みました。
そこで私が探した記事を一つ一つ私の個人的見解を交えて語っていこうと思います。

Web出身のUnityエンジニアによる大規模ゲームの基盤設計


developers.cyberagent.co.jp

素晴らしいと思った記事の一つがこの「Web出身のUnityエンジニアによる大規模ゲームの基盤設計」です。 その記事で紹介されているのはUniRxを使ったMVP。
Web系でMVCと呼ばれているものです。
(厳密にはWeb系でいうMVCは誤って伝わっており、MVPが正しい呼び方らしい)

私がUnityで設計をしたいと思った時に一番しっくりきた記事です。
しかしながらもう少しスッキリした書き方が出来そうな気もします。


Unityで学ぶMVPパターン ~ UniRxを使って体力Barを作成する ~

qiita.com

この記事はViewとModel、そしてPresenter全てがMonoBehaviourに依存してしまっています。
MVPはMonobehaviourの依存から解き放たれた設計ができるのに、ModelやPresenterも全てMonobehaviourに依存してしまっているのが勿体無いと思いました。

初めてのUniRx + MVP(Model View Presenter)(Unity 2017.1.0)

techblog.gracetory.co.jp

この記事ではViewがPresenterと一緒になっています。
MV(R)Pと呼ばれるものです。
Viewの入出力検知とPresenterの検知した入力の通知やModelの反映処理がPresenterとして一緒になっています。
そのViewとPresenterが一緒になっているのが、個人的にはちょっとモヤッとしました。

悪くは無いのですが、Modelへの変更要素が複数あった場合、大変なことになりそうです。
Presenterに処理を全て書く場合は肥大化しますし、Presenterを別けた場合には、どこから変更があったのか追いにくいそうです。
しかしながら、あまり一つの画面に詰め込むことは無いのでコレでも十分だとは思います。

UniRx 4.8 - 軽量イベントフックとuGUI連携によるデータバインディング

neue.cc

UniRxの作成者によるMV(R)Pの解説記事。
先ほどのMV(R)Pの詳しい説明が乗ってますサイトは読みづらいですが一見の価値ありです。

【Unity】MVPとMV(R)Pの実装例といろんな実装方法の考察

light11.hatenadiary.com

大体調べていくと、この記事のように3つの選択肢に絞られます。
難しい。

改めて通常のMVPについて考えてみる


これらを踏まえた上で本来のMVPとは何なのか振り返ってみようと思います。

Model

Modelは画面の値などを管理する部分で、計算処理などもModelで行います。
ModelについてはMVPの他記事でもブレている点は少なく簡潔に書けそうです。

View

ViewはUIそのものだったり、UI上の入出力です。
Animatorなどの処理もViewに割り当てられると考えられます。
WebではHTMLもしくはJavaScriptなどのクライアントの処理がViewと定義されています。
ここがWebとUnityで大きく違います。
UnityのPresenterとViewは切り分けが難しいです。

Presenter

PresenterはModelとViewの間の処理です。
基本的に値の変更は行わず、やっても値の加工ぐらいです。
Modelに変更があればViewへ値を反映させ、Viewから入力があればModelに反映させます。

UnityでのMVPは?


WebではPostでクライアント側とサーバーサイドできっぱり別れていますが、Unityの画面設計は言ってしまえば全部Viewです。
Unityの処理全部がViewとはいえ画面構造などはクライアントに持っているため、ViewとModelは切り分けが必要です。
しかし、PresenterとViewを強引に切り分けた場合、Presenterは値の仲介役でしかなく、Presenterのいる意味はほぼ無くなってしまいます。
先ほども軽く触れた通りViewとPresenterを一緒にしてしまった場合、Modelへの変更要素が複数あった場合は大変なことになりそうです。
Presenterに処理を全て書く場合は肥大化します。
それに、Presenterを別けた場合は - 他のオブジェクトからの発火なのか - 画面タッチなのか - コントローラー(ゲームパッド)による操作なのか どのPresenterからの発火なのか分かりにくいです。

私の考えたMV(R)P


はじめに言っておきますが、UniRxとZenjectを使うつもりです。
UniRxやZenjectを使えないプロジェクトの方は別の実装方法で実装することをおすすめします。
無理にUniRxやZenjectを入れる必要は無いと思います。

1画面1Presenter

UnityのMVPの悪いところはViewでボタンの入力の検知を出来るが、その検知自体がPresenterの役目が半分あるからです。
何が言いたいかというと、中途半端ViewとPresenterの境目が曖昧過ぎて設計が難しいってことです。

しかし、Viewでボタンの入力を受け付けてもPresenterはModelへ処理を素通りさせるだけというのも十分にあります。
素通りさせる場合は、「PresenterはViewとModelを切り分けるだけの存在」になってしまっていて1View 1Presenterだと冗長ではないかと私は思います。

Presenterの切り分けるという機能自体は必要だと私は思います。
そこで私が提唱するのは1画面、1Presenterです。
Presenterで複数のViewの処理の受付をしてしまえばコードが集約できます。
どこから呼び出されたかが分からなかったとしても、必ずPresenterを介して処理してしまえば他のViewを意識せずに済みます。
懸念点はPresenterの肥大化です。関数が呼ばれたら何かをするぐらいしかしてないはずなので、多少コード量が多くてもPresenterの処理の中身が意味不明には、ならないのではないかと思います。
複数機能がある画面ではPresenterが1つだと厳しいと思うので数個は使っても良いと思いますが、どこに処理があるか分からなくなると思うので基本的に1つにしてしまうのが良いんじゃないかなって思います。
(もしくは冗長にはなりますが1View、1Presenterで妥協)

多分、1画面1PresenterならPresenterのソースコードを量産しなくて冗長にならないのでGoodなはず……。

Zenjectを使う

1画面Presenterにする解決策としてZenjectを使います。
Zenjectの使い方の詳細は割愛します。

ZenjectでPresenterに対して画面全てのViewとModelをインストールします。
そうすることでModelやPresenterもMonobehaviourに依存せずに済みます。

結果

1画面につき複数のViewと複数のModelと1つのPresenterと1つのZenjectInstallerで構成されるMV(R)Pが出来上がります。

サンプルプロジェクト

github.com

サンプルプロジェクトの解説

orotiyamatano.hatenablog.com

以上


「UnityのMVP、MV(R)Pを調べたけど、どれが正しいんだ?」でした。
私の考えたMV(R)Pは実際に現場で使ったことが無いので、まだまだ考えるべき点は多いと思いますが、なま暖かい目で読んでくれたら幸いです。

その他インゲームでMVPは使える?


結論からいうと使いにくいです。
パフォーマンスも落ちますしオススメしません。

インゲームで使えるか考えてみる

キャラクターを動かす部分をMVPで考えてみます。
Viewは入力やを受け取ったりする入出力部分なのでコントローラーの処理はViewになります。
その上でPresenterがModelに反映させる。
ビジネスロジックに相当するところはModelが持つことになります。
つまり、キャラクターの位置情報の計算はModelでやることになります。
しかし、加速度や実際の移動はMonoBehaviourのRigidBodyがあるViewでやらなければいけないです!
もどかしい!!

だとしたら、
例えばModelに設定された値をRigidBodyに反映させる場合
PresenterがModelに設定された値をViewに伝え、
ViewはRigidBodyに値を伝えることになります。

ここで気が付いた人がいるかもしれない「あれ?Presenter無駄じゃない?」
と、いったことになるのでMVPはインゲームには向いてないです。
画面の遷移やUIの見た目への反映は良いが、MonoBehaviourに依存するパーツが多いインゲーム側には向いてないと感じます。

WebではPresenterがViewに大して一対一になっていることが多いです。
しかし、Unityでは一つもしくは数個のコンポーネントに対してViewが設定されます。
そのViewに対してPresenterが毎回生成されるとなると、ソースコードの数が多くなり過ぎる欠点がある。(Presenterをまとめてもわかり辛い)

いくつかのViewをまとめる必要があると思うが、今度はPresenterに記述する量が多くなる。
他の書き方を考えても難しそう。

落ち着いて再び、インゲームに採用する場合はどのようにすればいいか考えてみる。
今度はModel部分から考えてみます。
データ部分、キャラのステータスや道具袋などはModel化出来そうだ。
キャラクターの状態を表すステート(走り中か、ジャンプ中かなど)もModel化出来そうだ。
衝突判定やノックバックはViewになりそうだ。
それらを考慮するとインゲームについては、これまでと同じようにコンポーネントで管理し、
データの塊や他に制御するコードについてはModelにかけそうだ。

しかし、View部分については難しい。
衝突した場合、ノックバックする処理はMVPで書こうとしたら、
Viewで衝突を受付、その後ぶつかったことをPresenterに通知、
受け取ったPresenterはノックバックをさせる処理をするために(MonoBehaviourで処理するしか無いので)Viewに渡す。

View→Presenter→View
となる。Presenterでやはり発行する意味が本当にあるのか不明です……。
だとしたら、Viewを拡大解釈して、Viewだけで終わらさせるのが良いと感じます。Presenterから何か通知があって動作するものもあって、ちょっと複雑化します。

インゲームにはMVPを選択しないということになります。
少なくともデータ周りについてはMVP化出来るが、インゲーム部分に複雑に絡んでくるところは難しいです。

やはり、MVPという枠にとらわれてインゲームを設計すること自体が間違っているのかもしれないです。