Unity 開発における MVC 基礎
-
2026年2月27日
こんにちは。
Cocone Engineering でクライアントエンジニアをしている今福です。
今回は、Unity 開発において、MVC アーキテクチャをどのように適用するかについて、解説します。
はじめに
今回解説する内容は、こうしなければならない、というような堅苦しいものではなく、こういうやり方もある、という方法論の一つです。
これをご覧になっている Unity 開発経験者の方は、すでに実感されている方もいらっしゃるかと思いますが、Unity 開発、こと、ゲームアプリなどの開発においては、一般的なアーキテクチャをそのまま適用しようとすると、かえって歪な構成になってしまうこともしばしばあります。
そんなときに、考えや構成を整理する一助になれば、幸いです。
MVC とは?
機能をデータやビジネスロジックを扱う Model 、 表示物を扱う View 、Model、View 間の処理を行う Controller の3つに分けて開発する手法であり、非常に長い歴史を持つアーキテクチャとなります。

特に、 ビジネスロジックと表示の分離 という観点は、現在のソフトウェアの設計やアーキテクチャにおいても広く用いられるものであり、実際のこのアーキテクチャを用いて、設計、開発を行った経験のある方も多いかと思います。
Unity 開発での MVC
Unity で開発を行う場合、コンポーネントとして開発するかどうかを考える必要があります。
Unity で MVC の各モジュールを作成する場合、基本的に Model 以外は、コンポーネントとして作成するのがベターでしょう。
| モジュール | MonoBehaviour を継承するか | 説明 |
|---|---|---|
| Model | ✗ | データやビジネスロジックのみで、Unity のコンポーネントには依存しない |
| View | ◯ | 表示するオブジェクトなどのコンポーネントをシリアライズするため、コンポーネント化が必要 |
| Controller | ◯ | View をシリアライズするため、コンポーネント化が必要 |
各モジュールをコンポーネント化しないで実装することもできないわけではありませんが、Unity 上でオブジェクトを扱う際に、コンポーネントを使わない場合、逆に煩雑化しやすいので、メリットよりもデメリットが大きくなりやすいです。
MVC での依存関係
MVC における依存関係は、やや定義があいまいな部分もありますが、以下のようになります。
| モジュール | 依存するモジュール |
|---|---|
| Model | なし |
| View | Model ※ |
| Controller | Model、View |
View から Model への参照については、MVC においては制限されていませんが、この部分についても、Controller が Model からデータを取得して View に渡すような形式にした方が、個人的にはよいと考えています。
これは、MVP に近い考え方になりますが、View が Model への参照を保持していると、View からも Model のメソッドを呼び出すことができるため、意図せぬ実装によって、データが書き換わってしまうなどのバグのリスクが上がるためです。
MVC の各モジュールについて
Model
Model は、主にデータとビジネスロジックを扱うモジュールとなります。
また、通信や DB へのアクセスなど、インフラストラクチャ層とのやり取りも Model の役割となります。
- View、Controller に必要なデータ
- ただし、以下のような実体のあるオブジェクトやリソースは保持しない
- GameObject
- Component
- Texture
- ただし、以下のような実体のあるオブジェクトやリソースは保持しない
- ビジネスロジック
- 通信
- DB へのアクセス
Model を実装する際は、できるだけ単体で成立するようにすることを意識するとよいです。
これは、SOLID 原則に基づいた観点のみだけでなく、テストを行うことを考えた場合にも非常に重要になってきます。
Unity のクライアント開発では、まだまだテストを行う文化はあまり浸透していないと感じますが、Model 内のビジネスロジックに対して、きちんとテストを書いておくことで、実装の安定性を高めることができます。
テストについて、細かい解説をすると終わらなくなってしまうので詳細については割愛しますが、昨今の AI の台頭による開発環境の変化においても、テストの重要性は上がってきていると感じています。
また、Unity での実装においては、Model がテクスチャなどのリソースのパスを保持することになるため、リソースの読み込みや保持も合わせて実装したくなってしまいがちですが、これは、Model の責務から外れたものとなってしまい、Model が何でもありな状態になるため、実装しないようにしましょう。
View
View は、主に表示に関するものを扱うモジュールとなります。
Unity の場合、表示するオブジェクトへの参照をシリアライズして保持するため、必然的に View は、コンポーネントとして実装することになります。
- 表示するオブジェクトのシリアライズ
- Image
- Button
- 表示物に限定したロジック
- ビジネスロジックであれば、Model、Model と View 間のロジックは Controller の責務なので、Unity 開発ではあまり View にロジックを実装する機会は少ない
- ユーザー入力の受け取り
- 受け取った入力を処理するのは Controller
View を実装する際は、受動的なモジュールとして実装することを意識するとよいです。
また、上述していますが、View が直接 Model を参照することがないように実装した方が安全性が高いです。
ユーザーからの入力に関しては、Button コンポーネントからの入力自体を受け取るところまでは、View が行い、Controller へコールバックメソッドなどを用いて通知するような形式にするのがよいと考えています。
ただ、実際のところ、入力を受け取ってもそのままコールバックするだけ、という状態になることも少なくないため、場合によっては、Controller 側で直接 Button コンポーネントへの入力コールバックを設定してもよいと思います。
Controller
Controller は、View と Model 間の処理を行ったり、他のモジュールとのやり取りを行います。
View をコンポーネントとして実装するため、View への参照をシリアライズする必要があるので、Controller もコンポーネントとなります。
- Model のデータを View に設定
- 外部のモジュールとのやり取り
- Model や View を外部のモジュールが直接参照するようなことがないようにするため
- ユーザー入力後の処理
- オブジェクトやリソースの読み込み
Controller は、Model と View 間の処理を実装したり、外部のモジュールとのやり取りを行う都合上、一定度ロジックを実装する必要があります。
このとき、実装するロジックがビジネスロジックとして、Model に実装すべきものではないかということを常に考えながら実装するとよいと思います。
例題:ポップアップ
簡単な実装例として、ポップアップでの MVC の実装例を記載します。
例として、いくつかの機能を持たせているため、やや実際のものとは離れている部分もありますが、参考としてご覧ください。
要件
- タイトルを表示する
- メッセージを表示する
- メッセージの形式は、アイテム名+テキスト
- {item} を獲得しました
- {item} を受け取りますか?
- メッセージの形式は、アイテム名+テキスト
- ボタンを表示する
- ボタンの表示方法は、2 種類
- OK のみ
- Yes / No の 2 つを表示
- ボタンの表示方法は、2 種類
Model
public class PopupModel
{
public string Title { get; }
private string itemId;
private PopupType type;
public bool IsNotice { get { return type == PopupType.Notice; } }
public PopupModel(string title, string itemId, PopupType type)
{
Title = title;
this.itemId= itemId;
this.type = type;
}
private string GetItemName()
{
return Database.Master.GetItemName(itemId);
}
public string GetMessage()
{
string itemName = GetItemName();
return IsNotice ? $"{itemName}を獲得しました" : $"{itemName}を受け取りますか?";
}
public async UniTask<Result> ReceiveItem()
{
// サーバーへと通信を行い、結果を返す
}
}public enum PopupType
{
Notice,
Confirm
}View
public class PopupView : MonoBehaviour
{
[SerializeField] private Text title;
[SerializeField] private Text message;
[SerializeField] private Button okButton;
[SerializeField] private Button yesButton;
[SerializeField] private Button noButton;
private Action onClickOk;
private Action onClickYes;
private Action onClickNo;
public void Init()
{
okButton.onClick.AddListener(OnClickOk);
yesButton.onClick.AddListener(OnClickYes);
noButton.onClick.AddListener(OnClickNo);
}
public void SetButtonCallback(Action onClickOk, Action onClickYes, Action onClickNo)
{
this.onClickOk = onClickOk;
this.onClickYes = onClickYes;
this.onClickNo = onClickNo;
}
public void SetTitle(string title)
{
this.title.text = title;
}
public void SetMessage(string message)
{
this.message.text = message;
}
public void UpdateButtonVisible(bool isNotice)
{
okButton.SetActive(isNotice);
yesButton.SetActive(!isNotice);
noButton.SetActive(!isNotice);
}
private void OnClickOk() => onClickOk?.Invoke();
private void OnClickYes() => onClickYes?.Invoke();
private void OnClickNo() => onClickNo?.Invoke();
}Controller
public class PopupController : MonoBehaviour
{
private PopupModel model;
[SerializeField] private PopupView view;
public void Init(string title, string itemId, PopupType type)
{
model = new PopupModel(title, itemId, type);
view.SetButtonCallback(OnClickOk, OnClickYes, OnClickNo);
view.SetTitle(model.Title);
view.SetMessage(model.GetMessage());
view.UpdateButtonVisible(model.IsNotice);
}
private void OnClickOk()
{
Close();
}
private async void OnClickYes()
{
Result result = await model.ReceiveItem();
Close();
}
private void OnClickNo()
{
Close();
}
private void Close()
{
Object.Destroy(gameObject);
}
}まとめ
Unity 開発における MVC について、解説しましたがいかがでしたでしょうか。
Unity 開発においては、MVC に限らず、様々なアーキテクチャがそのまま適用できないことが多く、正解のない中で各々が試行錯誤しながら、開発を行われていると思いますが、この記事がそんな皆様の一助となれば幸いです。
Unityを主軸としたクライアント開発に携わるエンジニアや様々なポジションを募集しています。
今回の記事のように、アーキテクチャや設計について主体的に考えながら開発したい方、
チームでより良い実装を模索したい方など大歓迎です!
カジュアル面談からのエントリーも可能ですので、少しでも興味を持っていただけた方は、お気軽にご応募ください。
🔗採用サイトはこちら