目次
はじめに
Unityでゲームを作っていると、HP(体力)管理はほぼ確実に必要になります。
しかし、
- ダメージ処理
- UI(HPバー・数値表示)
- 当たり判定との連携
これらを1つのスクリプトにまとめてしまうと、後々の修正や拡張が大変になりがちです。
そこで今回は、
Model / View / Presenter(MVP)構成 + R3(Reactive Extensions) を使って実装した
HP管理のサンプルを、
- HPModel.cs:HPのデータとロジック
- HPView.cs:HPゲージ・テキストの表示
- HPPresenter.cs:ModelとViewの仲介、イベント処理
の3ファイルに分けて紹介します。
HPModel.cs:HPの状態とロジックを管理
まずはコード全文です。
using R3;
using UnityEngine;
public class HPModel : MonoBehaviour
{
/// <summary>
/// 最大HP
/// </summary>
public readonly int maxHP = 100;
/// <summary>
/// ダメージ量
/// </summary>
[SerializeField]
private uint _damege;
/// <summary>
/// 残っているHP
/// </summary>
public ReadOnlyReactiveProperty<int> HP => _hp;
private readonly ReactiveProperty<int> _hp = new ReactiveProperty<int>();
private void Start()
{
_hp.Value = maxHP;
}
/// <summary>
/// ダメージを受けたときの処理
/// </summary>
public void GetDamage()
{
var hp = _hp.Value -= (int)_damege;
_hp.Value = Mathf.Clamp(hp, 0, maxHP);
Debug.Log("Damage");
}
private void OnDestroy()
{
_hp.Dispose();
}
}
HPModel.csは、HPに関する純粋なデータと処理を担当します。
主な役割
- 最大HPの定義
- 現在HPの保持
- ダメージを受けたときの処理
- HP変更をReactiveに通知
ポイント解説
- ReactiveProperty<int> を使ってHPを管理
- 外部からは ReadOnlyReactiveProperty<int> として公開
- GetDamage() でHPを減らし、Clamp で0〜最大値を保証
public ReadOnlyReactiveProperty HP => _hp;この設計により、
- 「HPが変わったらUIを更新する」
- 「HPが0になったらゲームオーバー」
といった処理を、イベント感覚で追加できるのが大きなメリットです。
HPView.cs:HPの見た目を担当
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class HPView : MonoBehaviour
{
/// <summary>
/// HPゲージ
/// </summary>
private Slider _hpGuage;
[SerializeField]
private TextMeshProUGUI _hpText;
private void Start()
{
_hpGuage = this.GetComponent<Slider>();
//_hpText = this.GetComponent<TextMeshProUGUI>();
}
/// <summary>
/// HPゲージの設定
/// </summary>
/// <param name="maxHP">最大HP</param>
/// <param name="hp">残っているHP</param>
public void SetGuage(int maxHP, float hp)
{
_hpGuage.value = hp / maxHP;
}
public void SetText(int maxHP, int hp)
{
_hpText.text = hp.ToString() + '/' + maxHP.ToString();
}
}
HPViewは、UI表示だけに責任を持つクラスです。
主な役割
- HPスライダー(ゲージ)の更新
- HPテキスト(例:80 / 100)の更新
ポイント解説
- Slider を使ってHP割合を表示
- TextMeshProで数値を表示
- ロジックは一切持たず、表示専用
public void SetGuage(int maxHP, float hp)
{
_hpGuage.value = hp / maxHP;
}このようにすることでUIデザインを変更してもModelに影響しない、表示方法を差し替えやすいという利点があります。
HPPresenter.cs:ModelとViewをつなぐ司令塔
using R3;
using R3.Triggers;
using System;
using UnityEngine;
public class HPPresenter : MonoBehaviour
{
[SerializeField] private GameObject _player;
private HPModel _hPModel;
private HPView _hpView;
//private IGetHitEventProvider _getHitEventProvider;
void Start()
{
_hPModel = this.GetComponent<HPModel>();
_hpView = this.GetComponent<HPView>();
// HPテキスト初期化
_hpView.SetText(_hPModel.maxHP, _hPModel.HP.CurrentValue);
// 敵に当たった時の処理
_player.OnTriggerEnter2DAsObservable()
.Select(hit => hit.gameObject.tag)
.Where(tag => tag == "Enemy")
.Subscribe(_ =>
{
if (_player.GetComponent<SpriteAlphaBlinker>()._isInc == false)
{
// ダメージ処理
_hPModel.GetDamage();
// 点滅&無敵時間を設定
_player.GetComponent<SpriteAlphaBlinker>().BlinkOnHit();
}
}).AddTo(this);
// HPModel内のHPが減ったことをViewへ知らせる
_hPModel.HP
.Subscribe(hp =>
{
_hpView.SetGuage(_hPModel.maxHP, hp);
_hpView.SetText(_hPModel.maxHP, _hPModel.HP.CurrentValue);
}).AddTo(this);
}
void Update()
{
//_hPModel.UpdateInvincibleTime();
//if(_hPModel.InvincibleTime.CurrentValue <= 0f)
//{
// //_player.GetComponent<SpriteAlphaBlinker>().BlinkOnHitEnd();
//}
}
}
HPPresenterは、全体の流れを制御する中心的な存在です。
主な役割
- ModelとViewの初期化
- プレイヤーの当たり判定監視
- HP変更をViewへ反映 敵に当たったときの処理
_player.OnTriggerEnter2DAsObservable()
.Where(tag => tag == "Enemy")
.Subscribe(_ =>
{
_hPModel.GetDamage();
});・OnTriggerEnter2DをObservable 化
・Enemyタグに当たったときだけ反応
・ダメージ処理はModelに委譲
HP変更をViewに反映
_hPModel.HP
.Subscribe(hp =>
{
_hpView.SetGuage(_hPModel.maxHP, hp);
_hpView.SetText(_hPModel.maxHP, hp);
});ここがReactive設計の一番重要な部分で、HPが変わったら自動的にUIが更新されるという非常に分かりやすい流れになります。
まとめ
今回紹介した構成のポイントをまとめると、
・HPModel
HPの状態とロジックだけを管理
・HPView
見た目(ゲージ・数値)だけを担当
・HPPresenter
当たり判定やイベントを受け取り、ModelとViewを接続
という、責務が明確に分かれた設計になっています。
Unityで「UI関連のコードがごちゃごちゃしてきた…」と感じている方はMVP + Reactive設計を試してみてください。

