はじめに
本記事では、Unityを例にとりながら、
MVPパターンとは何かを、解説していきたいと思います。
そもそも「〇〇パターン」とは何でしょうか?
これは簡単にいうと、
「プログラムをこう書くと便利ですよ」という作り方のコツのことです。
つまり、MVPパターンという方法に従って書くことで、
コードが分かりやすくなり、後から修正しやすくなったり、
チームで開発するときにも共有しやすくなるといったメリットがあります。
MVPパターンとは
MVPパターンは、UIや2Dゲームなどを実装するときによく使われる考え方です。
- 見た目(画面に表示する部分)
- 仕組み(ゲームのルールや処理の流れ)
この2つをきれいに分けて作りましょう!というのがMVPパターンの基本です。
では、なぜこのような作り方が推奨されるのでしょうか?
もし分けなかった場合、次のようなデメリットが考えられます。
- コードが長くなって読みにくくなる
- 修正や機能追加のときに、思わぬところまで壊れてしまう
- デザイナーやプログラマーが同じコードを触ることになり、作業がやりづらい
こうした問題を避けるために、MVPパターンでは「見た目」と「仕組み」を分けるのです。
MVPパターン「ではない」例
まずは、MVPパターンを取り入れていない例、
つまり、見た目と仕組みが混在しているクラスを見てみましょう。
以下は、シンプルなタイマーのクラスで、10秒からのカウントダウンを表示するものです。
using UnityEngine;
using UnityEngine.UI;
public class SimpleTimer : MonoBehaviour
{
[SerializeField] private Text timerText; // 表示用のテキスト
private float time = 10f; // 残り時間(秒)
private void Update()
{
// --- カウントダウンの処理(仕組み)---
time -= Time.deltaTime;
if (time < 0) time = 0;
// --- テキストの更新(見た目) ---
timerText.text = "Time: " + Mathf.CeilToInt(time).ToString();
}
}
テキストの更新(見た目)と、カウントダウンの処理(仕組み)が同じクラスの中に書かれていますね。
もちろん、このような書き方でも正しく動作しますし、間違いというわけではありません。
ただし、MVPパターンの考え方では、この「見た目」と「仕組み」を分けて作ることが推奨されます。
今回の例は処理が数行で済むシンプルな内容ですが、もし機能が増えて複雑になると、
コードが読みにくくなったり修正がしづらくなったりする可能性があります。
MVPパターンの例
今度は、MVPパターンの考えにのっとって、
テキストの更新(見た目)とカウントダウンの処理(仕組み)を分けてみましょう!
+-------------+ +-------------+
| (見た目) | 分離 | (仕組み) |
+-------------+ +-------------+
// 見た目 (View)
using UnityEngine;
using UnityEngine.UI;
public class TimerView : MonoBehaviour
{
[SerializeField] private Text timerText;
// ViewはPresenterを一切持たない
// Presenterから呼ばれるだけの受け口メソッドを提供する
public void SetTime(float time)
{
timerText.text = "Time: " + Mathf.CeilToInt(time);
}
}
// 仕組み (Model)
public class TimerModel
{
public float Time { get; private set; }
public TimerModel(float startTime)
{
Time = startTime;
}
public void Tick(float deltaTime)
{
Time -= deltaTime;
if (Time < 0) Time = 0;
}
}
しかし、ここで1つ問題が出てきます。
Viewは「画面に数字を表示するだけ」、Modelは「数字を計算するだけ」です。
このままでは、お互いに直接やり取りができません。
本来は「Modelで計算した結果」を「Viewに渡して画面に表示する」必要があります。
そこで登場するのが、第3のクラス Presenter(プレゼンター) です。
Presenterは、ModelとViewの橋渡しをする専用のクラスです。
+-------------+ +-------------+ +-------------+
| View | <----> | Presenter | <----> | Model |
| (見た目) | | (橋渡し役) | | (仕組み) |
+-------------+ +-------------+ +-------------+
// 橋渡し役 (Presenter)
public class TimerPresenter
{
private TimerModel model;
private TimerView view;
public TimerPresenter(TimerModel model, TimerView view)
{
this.model = model;
this.view = view;
}
public void Tick(float deltaTime)
{
model.Tick(deltaTime);
view.SetTime(model.Time);
}
}
Presenterの役割はあくまで橋渡しのみ。
表示や計算の機能は極力持たないようにしましょう。
■備考
ここで気づいた方もいるかもしれませんが、
MonoBehaviour を継承しているのは View(表示用クラス)だけ です。
なぜなら、ViewはUnityの GameObject と紐づいていて、
シーン上でUIやテキストを更新するために MonoBehaviour が必要だからです。
一方で、ModelやPresenterは「計算」や「橋渡し」といった処理だけを担当しており、
Unityのコンポーネント機能に依存する必要はありません。
そのため、通常のC#クラスとしてシンプルに定義されています。
初期化用のクラスについて
最後の実装です。
Model、View、Presenterはそれぞれ役割が分かれていますが、
「この3つを最初にどうつなぐか?」を決める必要があります。
そこで登場するのが 初期化用のクラス です。
ここでは例として GameManager を用意し、
ゲーム開始時に Model と View を作り、Presenterで結びつけます。
つまり、GameManagerは「最初の組み立て役」です。
using UnityEngine;
public class GameManager : MonoBehaviour
{
[SerializeField] private TimerView timerView;
private TimerPresenter presenter;
private void Start()
{
var model = new TimerModel(10f);
presenter = new TimerPresenter(model, timerView);
}
private void Update()
{
presenter.Tick(Time.deltaTime);
}
}
まとめ
ここまで、MVPパターンをUnityの例で紹介してきました。
改めて、採用したときのメリットと、採用しなかったときのデメリットを整理しておきます。
✅ MVPパターンを採用するメリット
- 役割がはっきり分かれるので、コードが読みやすくなる
- 修正や機能追加をしても、影響範囲が小さく済む
- テストがしやすくなる(Model単体で動作確認できる)
- チーム開発で分担しやすくなる(デザイナーはView、プログラマーはModelやPresenterなど)
❌ MVPパターンを採用しない場合のデメリット
- 1つのクラスに色々な処理が混ざってしまい、コードが長く読みにくくなる
- 修正や追加のときに、思わぬ部分まで壊してしまうリスクがある
- 表示と仕組みが密結合になるので、再利用しにくい
- チームで作業するときに、同じコードを複数人が触ることになり、作業がぶつかりやすい
MVPパターンの本質は 「見た目」「仕組み」「橋渡し」を分けて整理すること です。
プロジェクトの規模が大きくなるほど、この考え方のありがたみを実感できるはずです。
- Model … ゲームの仕組み(計算やデータ処理)を担当
- View … 見た目(画面表示)を担当
- Presenter … ModelとViewの橋渡し役
- GameManager … 最初に3つを組み立てる係