UnityにおけるMVPパターンについて

はじめに

こんにちは!クライアントチームの♂Natsuki♂です。

以前は野生のUnityプログラマーとして我流でコードを書いていましたが、今は昔、
最近では設計・実装の知識も身についてスーパーインテリジェントプログラマーになりましたので、そのあたりの話をしようかと思います。

真面目な話をすると、今回はUnityにおけるMVPパターンの話をしたいと思います。
MVPパターンを用いることで、クラスの責務や依存関係が整理されて「いい感じ」のコードが書けるということをお伝えできればと思います。

対象読者

この記事は以下のような方を対象としています。

  • 我流でコードを書いてる人
  • 何らかのパターンを使って設計・実装してみたい人
  • クラスの責務や依存関係を整理したい人

MVPパターンとは

MVPパターンとは、ざっくり言うとModel(データ)、View(表示)、Presenter(両者の仲介役)という役割に分割して処理を行おうという考え方のことです。
これは特に何かしらのデータをユーザーに表示する場面で役に立ちます。

MVPパターンを用いることで以下のようなメリットが得られます。

  1. クラスの責務が明確になる
    • 改修が簡単になる
  2. クラス同士(ModelとView)が疎結合になる
    • クラスの差し替えやテストが簡単になる

さて、MVPパターンという言葉をWeb系のフレームワーク等で聞いたことがある方も居るかもしれません。
それを(割と何でもアリな)Unityの世界に部分的に持ち込むことで、一定の秩序をもたらすことがでるというお話になります。

文章で書いても分かりづらいかと思うので、次の章から具体例を載せつつ説明していきます。

具体例

この章ではMVPパターンを用いた設計・実装の例として、仮の音楽プレイヤーを題材に説明します。
以下のようなアプリケーションをMVPパターンを用いて作ってみましょう。

仮の音楽プレイヤー(※UIだけなので音は出ません)

仕様は以下としました。

  • Play/Stopボタンで再生/一時停止ができる
  • Loopのオンオフができる
    • 曲を最後まで再生した場合
      • Loopがオンの場合: 最初から再生
      • Loopがオフの場合: 最後で停止

また、ループ再生が分かりやすいように曲の長さは3秒としました。

設計

早速ですが、MVPパターンを用いて設計してみましょう。
Model、View、Presenterの解釈の仕方は色々あるかと思いますが、今回は

  • Model: 音楽プレイヤーの実態
  • View: UI(入出力含む)
  • Presenter: ModelとViewを仲介するだけ

としました。
(今回は分かりやすくUIをViewとして解釈しましたが、場合によっては3DモデルやキャラクターがViewになることもあるかと思います)

さて、MVPパターンを意識してクラス図を書くと以下のようになりました。
(今回伝えたいことをここに詰め込みました)

クラス図

まず、見て分かる通りクラスが3つ(Model, View, Presenterに相当)に分割されています。
MVPパターンを意識して設計することで、各クラスの責務が明確になります。

例えば、「UIを変更したい」という場合にはMusicPlayerViewクラスのみを修正すれば良いといった具合です。
MusicPlayerModelMusicPlayerPresenterはUIのことを知らないので、この2つのクラスが影響を受けることはありません。

これが前章のメリット1です。

また、クラス図の矢印の向きから分かるようにPresenterはModelとViewに依存していますが、ModelとViewは互いに依存していません(疎結合)
疎結合なのでクラスの差し替えが簡単です。

例えば、「MusicPlayerModelをテストしたい」という場合に、「テスト用のPresenterクラスに差し替えてテストする」というようなことが簡単にできます。

これが前章のメリット2です。

実装

この節では、前節の設計を実装したコードを載せます。
やはり実装は気になるところだと思うので、省略せずに全部載せちゃいます。

まずはコードをざっと見て「あ、綺麗/簡潔だな」「責務が分かれてるな」と思って貰えれば嬉しいです。
ただやはり、ここで一番見て欲しいのは、各クラスは責務が明確であり、ModelとViewは互いに依存していないというところです。

例えば、前節で言ったような以下の内容はコードからも読み取れるかと思います。

  • Modelは音楽を再生し内部状態を発行するが、UIのことは知らない
  • ViewはUIでの入出力を司るが、音楽がどう再生されるかは知らない

もう少し具体的なところで言うと、以下の実装はMVPパターンの考え方として分かりやすいかもしれません。

  • ViewのSetPlaybackTimeメソッドではplaybackTimeが渡されるが、その値をシークバーにセットしたり、文字列としてフォーマットしてテキストにセットしたり、という処理はView内で完結している
  • Modelはあくまで内部状態発行の一環としてplaybackTimeを発行しているが、上記のようなUI表示用の文字列を発行したりはしない

余談ですが、自分が昔に書いていたコードでは「ModelにOnPlay的な命名のメソッドを定義して、ViewのOnPlayButtonClicked時に呼ぶ」みたいなことをしてたんですが、
OnPlayという命名だとModelとしての動作を表せていないのでMVPパターン的にも良くないんだろうなと思いました。

↓以下コード

MusicPlayerModel.cs

MusicPlayerView.cs

MusicPlayerPresenter.cs

UnityEvents.cs
(引数を取るUnityEventは継承しないと使えないためこのファイルで定義)

※記事の本質から外れないようにイベントまわりはUnityの標準機能(UnityEvent)で実装しましたが、実践的にはUniRxを使うと更に簡潔に便利に書けます。

まとめ

  • MVPパターンと、Unityにおける解釈の例について説明した
  • MVPパターンを用いると以下のメリットがある
    1. クラスの責務が明確になる
      • 改修が簡単になる
    2. クラス同士(ModelとView)が疎結合になる
      • クラスの差し替えやテストが簡単になる
  • MVPパターンはいいぞ(時と場所によります)