R3を使った体力ゲージの作り方

R3を使った体力ゲージの作り方_アイキャッチ
目次

はじめに

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設計を試してみてください。

よかったらシェアしてね!
目次