Unityを触り始めた頃って、つい1つのスクリプトのUpdateに全部書いてしまうことが多いですよね。
「ダメージ処理」「死亡処理」「エフェクト再生」「スコア加算」などを1つのクラスに押し込むと、すぐにGodクラス化してしまいます。

  • どこでHPが減っているのか分からない
  • 敵とプレイヤーで似たようなコードをコピペしてバグの温床になる
  • 死亡演出だけ変えたいのに巨大クラスを編集しないといけない

こういう問題は、「HPの管理」だけを担当する小さなコンポーネントを用意しておくと一気に解決できます。
この記事では、HPを持ち、0になったらdiedシグナル(イベント)を発行し、親を消す or アニメーションを呼ぶだけに責務を絞ったコンポーネント

「HealthManager」

をUnity6 / C#で実装していきます。

【Unity】死亡処理をイベントでスッキリ分離!「HealthManager」コンポーネント

以下に、コピペでそのまま使えるフルコードを載せます。
HP管理・ダメージ処理・死亡イベント通知・自動削除/アニメーション呼び出しまでを、1つのコンポーネントにまとめています。


using System;
using UnityEngine;

namespace Sample
{
    /// <summary>
    /// シンプルなHP管理コンポーネント。
    /// - currentHealth が 0 以下になったら died イベントを発行
    /// - 死亡時にオブジェクトを破棄する or アニメーションを再生する
    /// 
    /// 「HPを持つ存在」が共通で使えるように、処理を1か所に集約します。
    /// </summary>
    public class HealthManager : MonoBehaviour
    {
        // ====== 設定項目(インスペクターから編集)======

        [Header("基本設定")]
        [Tooltip("最大HP。ゲーム開始時はこの値で初期化されます。")]
        [SerializeField] private int maxHealth = 100;

        [Tooltip("現在HP。Startで maxHealth にリセットされます。")]
        [SerializeField] private int currentHealth = 100;

        [Header("死亡時の挙動")]
        [Tooltip("死亡時に自動的に GameObject を Destroy するかどうか。")]
        [SerializeField] private bool destroyOnDeath = true;

        [Tooltip("destroyOnDeath が false の場合、ここで指定した Animator に死亡アニメーションを再生させます。")]
        [SerializeField] private Animator targetAnimator;

        [Tooltip("死亡アニメーションのトリガー名。Animator に同名のTriggerパラメータを用意してください。")]
        [SerializeField] private string deathTriggerName = "Die";

        [Header("デバッグ用")]
        [Tooltip("ダメージテスト用。チェックを入れている間、Dキーでダメージを与えます。")]
        [SerializeField] private bool enableDebugDamage = false;

        [Tooltip("デバッグ用のダメージ量。")]
        [SerializeField] private int debugDamageAmount = 10;

        // ====== 公開イベント ======

        /// <summary>
        /// HPが0になったときに発火するイベント。
        /// 他のコンポーネントはこれを購読して、スコア加算やSE再生などを行えます。
        /// </summary>
        public event Action Died;

        /// <summary>
        /// HPが変化したときに発火するイベント。
        /// UIへの反映などに使えます。
        /// (current, max) を引数として渡します。
        /// </summary>
        public event Action<int, int> HealthChanged;

        // ====== プロパティ(読み取り専用)======

        /// <summary>
        /// 最大HP(読み取り専用)
        /// </summary>
        public int MaxHealth => maxHealth;

        /// <summary>
        /// 現在HP(読み取り専用)
        /// </summary>
        public int CurrentHealth => currentHealth;

        /// <summary>
        /// すでに死亡処理を実行済みかどうか。
        /// 二重に死亡処理が走るのを防ぎます。
        /// </summary>
        public bool IsDead { get; private set; }

        // ====== Unity ライフサイクル ======

        private void Start()
        {
            // ゲーム開始時にHPを最大値で初期化
            currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
            NotifyHealthChanged();
        }

        private void Update()
        {
            // デバッグ用:Dキーでダメージを与えて挙動を確認
            if (!enableDebugDamage) return;

            if (Input.GetKeyDown(KeyCode.D))
            {
                ApplyDamage(debugDamageAmount);
            }
        }

        // ====== 公開メソッド ======

        /// <summary>
        /// ダメージを与えるメソッド。
        /// 正の値を渡してください(負値は無視されます)。
        /// </summary>
        /// <param name="amount">与えるダメージ量(正の整数)</param>
        public void ApplyDamage(int amount)
        {
            if (IsDead) return;          // すでに死亡済みなら何もしない
            if (amount <= 0) return;     // 不正な値は無視

            currentHealth -= amount;
            currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);

            NotifyHealthChanged();

            if (currentHealth <= 0)
            {
                HandleDeath();
            }
        }

        /// <summary>
        /// HPを回復するメソッド。
        /// 正の値を渡してください(負値は無視されます)。
        /// </summary>
        /// <param name="amount">回復量(正の整数)</param>
        public void Heal(int amount)
        {
            if (IsDead) return;          // 死亡後は回復しない
            if (amount <= 0) return;     // 不正な値は無視

            currentHealth += amount;
            currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);

            NotifyHealthChanged();
        }

        /// <summary>
        /// HPを最大値にリセットします。
        /// リスポーン時などに使えます。
        /// </summary>
        public void ResetHealth()
        {
            IsDead = false;
            currentHealth = maxHealth;
            NotifyHealthChanged();
        }

        // ====== 内部処理 ======

        /// <summary>
        /// HPが変化したことをイベントで通知する。
        /// </summary>
        private void NotifyHealthChanged()
        {
            HealthChanged?.Invoke(currentHealth, maxHealth);
        }

        /// <summary>
        /// 死亡処理本体。
        /// - Died イベントの発火
        /// - destroyOnDeath の設定に応じて Destroy or アニメーション再生
        /// </summary>
        private void HandleDeath()
        {
            if (IsDead) return;  // 二重実行防止

            IsDead = true;

            // まず died シグナル(イベント)を発行
            Died?.Invoke();

            // その後、自身の挙動(Destroy or Animation)を実行
            if (destroyOnDeath)
            {
                // 親ごと消したい場合は、親オブジェクトにこのコンポーネントを付けておくのがおすすめです。
                Destroy(gameObject);
            }
            else
            {
                // アニメーション再生モード
                if (targetAnimator != null && !string.IsNullOrEmpty(deathTriggerName))
                {
                    targetAnimator.SetTrigger(deathTriggerName);
                }
                else
                {
                    // Animator が設定されていない場合はログだけ出しておく
                    Debug.LogWarning(
                        $"[HealthManager] destroyOnDeath が false ですが、Animator または deathTriggerName が設定されていません。GameObject: {gameObject.name}"
                    );
                }
            }
        }
    }
}

使い方の手順

ここからは、実際のシーンでの使い方を具体的に見ていきましょう。
プレイヤー・敵・動く床など、さまざまなオブジェクトに使い回せる設計になっています。

手順①:スクリプトをプロジェクトに追加

  1. Unity の Project ウィンドウで、Scripts フォルダなどを右クリック → Create > C# Script を選択。
  2. 名前を HealthManager に変更。
  3. 中身をすべて削除し、上記のコードをコピペして保存。

手順②:HPを持たせたいオブジェクトにアタッチ

  • プレイヤーキャラクターの GameObject(例: Player)を選択。
  • Add Component ボタンから HealthManager を追加。
  • 敵キャラクター(例: EnemyGoblin)にも同様に HealthManager を追加。
  • 動く床や破壊可能オブジェクト(例: BreakableCrate)にも付ければ、「HPが0になったら壊れる床」などが簡単に作れます。

インスペクター上で以下を設定します。

  • Max Health:そのオブジェクトの最大HP
  • Destroy On Death
    • チェック有り:HP0で GameObject を Destroy
    • チェック無し:HP0で Animator のトリガーを叩く
  • Target Animator / Death Trigger Name:死亡アニメーションを使う場合のみ設定
  • Enable Debug Damage:動作確認用。チェックすると Play 中に D キーでダメージを与えられます。

手順③:ダメージを与えるスクリプトから呼び出す

攻撃判定やトラップ判定などのスクリプトから、HealthManager.ApplyDamage を呼ぶだけでOKです。
例として、プレイヤーに触れたら10ダメージを与えるトゲ床のコンポーネントを作ってみます。


using UnityEngine;
using Sample; // HealthManager の namespace を使用

/// <summary>
/// プレイヤーに触れたらダメージを与えるトゲ床。
/// </summary>
[RequireComponent(typeof(Collider))]
public class SpikeTrap : MonoBehaviour
{
    [SerializeField] private int damage = 10;

    private void OnTriggerEnter(Collider other)
    {
        // 触れた相手が HealthManager を持っていればダメージを与える
        var health = other.GetComponent<HealthManager>();
        if (health == null) return;

        health.ApplyDamage(damage);
    }
}

こうしておくと、「ダメージをどう処理するか」はすべて HealthManager 側に集約されるので、
トゲ床や敵の攻撃スクリプトは「ダメージを送るだけ」のシンプルな責務になります。

手順④:死亡イベント(diedシグナル)を別コンポーネントで受け取る

スコア加算やエフェクト再生などは、別コンポーネントに分けて Died イベントを購読すると綺麗に分離できます。

例:敵が死んだときにスコアを加算するコンポーネント


using UnityEngine;
using Sample;

/// <summary>
/// HealthManager の Died イベントを監視して、死亡時にスコアを加算するコンポーネント。
/// </summary>
[RequireComponent(typeof(HealthManager))]
public class EnemyScoreOnDeath : MonoBehaviour
{
    [SerializeField] private int scoreValue = 100;

    private HealthManager _health;

    private void Awake()
    {
        _health = GetComponent<HealthManager>();
    }

    private void OnEnable()
    {
        // Died イベントに購読
        _health.Died += OnDied;
    }

    private void OnDisable()
    {
        // 購読解除(メモリリーク&多重呼び出し防止)
        _health.Died -= OnDied;
    }

    private void OnDied()
    {
        // ここでスコアマネージャーに通知したり、静的メソッドを呼んだりする
        Debug.Log($"+{scoreValue} score! ({gameObject.name} died)");
    }
}

このように、「HP管理」「スコア加算」「エフェクト再生」などの責務をコンポーネント単位で分けることで、
巨大な God クラスを避けつつ、挙動を組み合わせていけるようになります。

メリットと応用

メリット①:プレハブが「HPを持つかどうか」だけで整理できる

HealthManager をプレハブに付けておくだけで、

  • プレイヤー
  • 敵(ザコ・ボス)
  • 破壊可能オブジェクト(木箱・バリケード)
  • タイム制限付きの床(HPをタイマー代わりに使う応用もアリ)

といったオブジェクトを、同じインターフェース(ApplyDamage / Heal / Died)で扱えます。
レベルデザイン時は「HPを持たせたいものにはとりあえず HealthManager を付ける」というルールにしておくと、
あとから共通の処理(例: ダメージエフェクト)を追加しやすくなります。

メリット②:死亡演出の差し替えが簡単

  • 雑魚敵:DestroyOnDeath = true にして、即座に消す
  • ボス:DestroyOnDeath = false + Animator で長めの死亡モーションを再生
  • オブジェクト:死亡時にパーティクルを出してから消す(別コンポーネントで Died を購読)

といったように、同じ HealthManager を使い回しつつ、死亡時の見た目だけを差し替えできます。
ロジックは共通、演出は個別、という理想的な分離ですね。

メリット③:テストがしやすい

デバッグ用に Dキーでダメージ を送れるようにしてあるので、

  • UIのHPバーが正しく減るか
  • 死亡アニメーションが正しく再生されるか
  • スコア加算やSE再生が複数回発火していないか

などを、敵AIやゲーム全体の流れに依存せずにテストできます。
こういった「単体でテストできるコンポーネント」は、長期開発でじわじわ効いてきます。

改造案:一定時間後に自動Destroyする死亡処理

死亡アニメーション再生後に少し待ってから消したい、というケースも多いです。
その場合は HandleDeath 内でコルーチンを呼び出すように改造すると良いでしょう。


private void HandleDeath()
{
    if (IsDead) return;

    IsDead = true;
    Died?.Invoke();

    if (destroyOnDeath)
    {
        // すぐに消すのではなく、2秒待ってから Destroy
        StartCoroutine(DestroyAfterDelay(2f));
    }
    else
    {
        if (targetAnimator != null && !string.IsNullOrEmpty(deathTriggerName))
        {
            targetAnimator.SetTrigger(deathTriggerName);
            // アニメーション長に合わせて遅延Destroyするのもアリ
            StartCoroutine(DestroyAfterDelay(3f));
        }
    }
}

private System.Collections.IEnumerator DestroyAfterDelay(float delay)
{
    yield return new WaitForSeconds(delay);
    Destroy(gameObject);
}

このように、小さな責務のコンポーネントにしておくと、
「死亡後に一定時間だけ残す」「リスポーン用に非アクティブにする」などのアレンジがやりやすくなります。

HP管理を HealthManager に任せてしまえば、他のコンポーネントは
「ダメージを送る」「死亡イベントを受け取る」という2つのインターフェースだけを意識すればよくなります。
ぜひプロジェクトの標準コンポーネントとして育ててみてください。