Unityを触り始めた頃は、ダメージ処理も無敵時間も点滅演出も、つい Update() の中に全部書いてしまいがちですよね。
「HPの管理」「ダメージ判定」「無敵時間」「見た目の点滅」が1つの巨大スクリプトにまとまると、次のような問題が出てきます。

  • 処理の追加・修正をすると、別の機能が壊れやすい
  • 敵・プレイヤー・トラップなど、全部に同じようなコードをコピペすることになる
  • テストしたい機能だけを個別に確認しづらい

そこでこの記事では、「ダメージを受けたあと一定時間だけ無敵になり、その間は Hurtbox の監視を止めつつ、見た目を点滅させる」専用のコンポーネントを用意して、役割をしっかり分けていきます。
無敵時間だけをきれいにコンポーネント化しておけば、プレイヤーにも敵にもボスにも、同じ仕組みをそのまま再利用できて便利ですね。

【Unity】ダメージ後の無敵&点滅をまるっと担当!「Invincibility」コンポーネント

ここでは、以下のような前提で設計します。

  • Hurtbox:ダメージ判定を行うコンポーネント(この記事では「ダメージを受ける側」の当たり判定担当と想定)
  • Invincibility:ダメージを受けたタイミングで呼び出され、一定時間 Hurtbox を無効化し、見た目を点滅させる

ダメージを受ける側のスクリプト(例:Health コンポーネント)から、InvincibilityStartInvincibility() を呼ぶだけで動くようにしておきます。


フルコード:Invincibility.cs


using System.Collections;
using UnityEngine;

/// <summary>
/// ダメージ後の無敵時間と点滅演出をまとめて担当するコンポーネント。
/// ・Hurtbox を一時的に無効化(監視オフ)
/// ・SpriteRenderer または Renderer を点滅させる
/// ・コルーチンで時間管理
/// 
/// 想定:ダメージ処理側(Health 等)から StartInvincibility() を呼び出す。
/// </summary>
[DisallowMultipleComponent]
public class Invincibility : MonoBehaviour
{
    [Header("無敵時間の設定")]
    [SerializeField, Tooltip("1回の無敵時間(秒)")]
    private float invincibleDuration = 1.5f;

    [SerializeField, Tooltip("点滅の速さ(1秒あたりの点滅回数)")]
    private float blinkFrequency = 10f;

    [Header("Hurtbox の参照")]
    [SerializeField, Tooltip("ダメージ判定を受け付けるコンポーネント(有効/無効を切り替える)")]
    private MonoBehaviour hurtbox; 
    // ここではインターフェースではなく MonoBehaviour を直接受けて、
    // enabled フラグで ON/OFF するシンプルな実装にしています。

    [Header("見た目の点滅対象")]
    [SerializeField, Tooltip("点滅させたいルートオブジェクト(省略時はこの GameObject 自身)")]
    private GameObject blinkRoot;

    [SerializeField, Tooltip("点滅に SpriteRenderer を使うか(2D向け)。false の場合は Renderer を使用(3D向け)。")]
    private bool useSpriteRenderer = true;

    [Header("オプション")]
    [SerializeField, Tooltip("無敵中に再度 StartInvincibility() が呼ばれた場合、時間を延長するか")]
    private bool extendIfAlreadyInvincible = true;

    // 内部状態
    private bool isInvincible;
    private float invincibleEndTime;
    private Coroutine invincibleCoroutine;

    // キャッシュ
    private SpriteRenderer[] spriteRenderers;
    private Renderer[] renderers;

    private void Awake()
    {
        // blinkRoot が未設定なら自分自身を対象にする
        if (blinkRoot == null)
        {
            blinkRoot = this.gameObject;
        }

        // 対象のレンダラーをキャッシュ
        if (useSpriteRenderer)
        {
            spriteRenderers = blinkRoot.GetComponentsInChildren<SpriteRenderer>();
        }
        else
        {
            renderers = blinkRoot.GetComponentsInChildren<Renderer>();
        }

        // Hurtbox が未設定なら、同じ GameObject から適当な MonoBehaviour を探す(任意)
        if (hurtbox == null)
        {
            // ここでは「Hurtbox」という名前のコンポーネントが付いていれば自動取得する例
            var found = GetComponent("Hurtbox") as MonoBehaviour;
            if (found != null)
            {
                hurtbox = found;
            }
        }
    }

    /// <summary>
    /// 無敵時間を開始する。ダメージ処理側から呼び出す想定。
    /// 例:GetComponent<Invincibility>().StartInvincibility();
    /// </summary>
    public void StartInvincibility()
    {
        // 無敵時間の終了予定時刻を更新
        float newEndTime = Time.time + invincibleDuration;

        if (isInvincible)
        {
            // すでに無敵中の場合の挙動
            if (extendIfAlreadyInvincible && newEndTime > invincibleEndTime)
            {
                invincibleEndTime = newEndTime;
            }
            // 延長しない設定なら何もしない
            return;
        }

        // 無敵状態に入る
        isInvincible = true;
        invincibleEndTime = newEndTime;

        // Hurtbox を無効化(監視を止める)
        if (hurtbox != null)
        {
            hurtbox.enabled = false;
        }

        // コルーチン開始(すでに動いていたら止めてから開始)
        if (invincibleCoroutine != null)
        {
            StopCoroutine(invincibleCoroutine);
        }
        invincibleCoroutine = StartCoroutine(InvincibilityRoutine());
    }

    /// <summary>
    /// 外部から強制的に無敵状態を終了させたい場合に呼ぶ。
    /// </summary>
    public void ForceStopInvincibility()
    {
        if (!isInvincible) return;

        isInvincible = false;

        if (invincibleCoroutine != null)
        {
            StopCoroutine(invincibleCoroutine);
            invincibleCoroutine = null;
        }

        // Hurtbox を再有効化
        if (hurtbox != null)
        {
            hurtbox.enabled = true;
        }

        // 点滅を解除(元の表示に戻す)
        SetVisible(true);
    }

    /// <summary>
    /// 現在無敵中かどうかを返す。UI 表示や他コンポーネントの条件分岐に利用可能。
    /// </summary>
    public bool IsInvincible => isInvincible;

    /// <summary>
    /// 無敵時間の本体(コルーチン)。時間経過と点滅処理を行う。
    /// </summary>
    private IEnumerator InvincibilityRoutine()
    {
        // 無敵中はループし続ける
        while (Time.time < invincibleEndTime)
        {
            // 点滅用の ON/OFF 判定
            // 例:blinkFrequency = 10 の場合、1秒間に10回 ON/OFF を切り替える
            float t = Time.time * blinkFrequency;
            bool visible = Mathf.FloorToInt(t) % 2 == 0;

            SetVisible(visible);

            yield return null; // 1フレーム待つ
        }

        // 無敵時間終了
        isInvincible = false;

        // Hurtbox を再有効化
        if (hurtbox != null)
        {
            hurtbox.enabled = true;
        }

        // 点滅解除(常に表示)
        SetVisible(true);

        invincibleCoroutine = null;
    }

    /// <summary>
    /// レンダラーの表示/非表示をまとめて切り替える。
    /// SpriteRenderer または Renderer を対象にする。
    /// </summary>
    /// <param name="visible">true なら表示、false なら非表示</param>
    private void SetVisible(bool visible)
    {
        if (useSpriteRenderer)
        {
            if (spriteRenderers == null || spriteRenderers.Length == 0) return;

            for (int i = 0; i < spriteRenderers.Length; i++)
            {
                if (spriteRenderers[i] == null) continue;
                spriteRenderers[i].enabled = visible;
            }
        }
        else
        {
            if (renderers == null || renderers.Length == 0) return;

            for (int i = 0; i < renderers.Length; i++)
            {
                if (renderers[i] == null) continue;
                renderers[i].enabled = visible;
            }
        }
    }
}

使い方の手順

ここからは、具体的な使用例を交えつつ、セットアップ手順を見ていきましょう。

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

  1. Invincibility.cs を作成し、上記のコードをコピペして保存します。
  2. Unity に戻ると自動でコンパイルされます。

手順②:プレイヤー(または敵)にアタッチ

例として「プレイヤーキャラクター」に無敵時間を付けたい場合:

  1. プレイヤーのプレハブ(またはシーン上の GameObject)を選択。
  2. Add Component から Invincibility を追加。
  3. 同じオブジェクト、または子オブジェクトに「Hurtbox」的なダメージ判定コンポーネントがある前提で、
    Hurtbox フィールドに、そのコンポーネントをドラッグ&ドロップします。
  4. 見た目を点滅させたいルートオブジェクトを Blink Root に設定します。
    特にこだわりがなければ、プレイヤー本体の GameObject を指定しておけばOKです。
  5. 2Dなら Use Sprite Renderer をオン、3Dモデルならオフにして Renderer ベースで点滅させましょう。

手順③:ダメージ処理側から呼び出す

次に、ダメージを受けるタイミングで StartInvincibility() を呼ぶようにします。
ここでは例として、超シンプルな Health コンポーネントを用意します。


using UnityEngine;

/// <summary>
/// 非常にシンプルな HP 管理コンポーネントの例。
/// ダメージを受けたら Invincibility を起動する。
/// </summary>
[DisallowMultipleComponent]
public class Health : MonoBehaviour
{
    [SerializeField]
    private int maxHp = 3;

    private int currentHp;
    private Invincibility invincibility;

    private void Awake()
    {
        currentHp = maxHp;
        invincibility = GetComponent<Invincibility>();
    }

    /// <summary>
    /// 外部からダメージを与えるときに呼ぶ。
    /// </summary>
    public void TakeDamage(int amount)
    {
        // すでに無敵中ならダメージを無視(任意の仕様)
        if (invincibility != null && invincibility.IsInvincible)
        {
            return;
        }

        currentHp -= amount;
        if (currentHp <= 0)
        {
            currentHp = 0;
            // ここで死亡処理などを行う
            Debug.Log($"{gameObject.name} は倒れた!");
            return;
        }

        // 無敵時間を開始
        if (invincibility != null)
        {
            invincibility.StartInvincibility();
        }
    }
}

これで、Health.TakeDamage() が呼ばれたときに無敵時間が始まり、
Hurtbox が一時停止&キャラクターが点滅するようになります。

手順④:具体的な使用例

  • プレイヤー
    2Dアクションゲームで、トゲや敵に触れたときにダメージを受けるプレイヤーにアタッチします。
    Hurtbox は、OnTriggerEnter2D で敵の攻撃判定を検知するコンポーネントなどを想定しています。
  • 敵キャラクター
    ボス敵に無敵時間をつけて、「連続ヒットで一瞬で溶ける」問題を防ぎます。
    HealthTakeDamage() 内で StartInvincibility() を呼ぶだけで、プレイヤーと同じ無敵ロジックを使い回せます。
  • 動く床(トラップ床)
    床自体がダメージを受けるギミック(例:プレイヤーが乗ると床が壊れる)にも応用できます。
    壊れる前に数回だけダメージを受ける床にして、ダメージを食らうたびに点滅+無敵時間で「今はこれ以上壊れない」演出を入れると、プレイヤーにとって視覚的にわかりやすくなります。

メリットと応用

この Invincibility コンポーネントを使うことで、次のようなメリットがあります。

  • 責務が分離される
    HP 管理(Health)と無敵時間・点滅(Invincibility)が別コンポーネントになることで、
    それぞれのコードがコンパクトに保たれ、保守性が上がります。
  • プレハブの再利用性が高まる
    「ダメージを受けるオブジェクト」ならどれでも、Invincibility をポン付けして StartInvincibility() を呼ぶだけで同じ挙動を共有できます。
    敵・プレイヤー・トラップなど、共通の演出を一括で調整できるのはかなり楽です。
  • レベルデザインがやりやすくなる
    レベルデザイナー(または自分の未来の自分)が、invincibleDurationblinkFrequency をインスペクターから調整するだけで、ゲームの難易度やテンポを変えられます。
    スクリプトを書き換えずにゲームバランスをいじれるのは大きな利点ですね。

改造案:無敵中は色を変える(半透明+赤色)

点滅だけでなく、「無敵中は少し赤く半透明にする」といった演出を追加したい場合は、
SetVisible() を拡張して、色もいじるとわかりやすいです。


private void SetInvincibleColor(bool invincible)
{
    if (!useSpriteRenderer || spriteRenderers == null) return;

    Color normal = Color.white;
    Color invincibleColor = new Color(1f, 0.5f, 0.5f, 0.6f); // 少し赤くて半透明

    for (int i = 0; i < spriteRenderers.Length; i++)
    {
        if (spriteRenderers[i] == null) continue;
        spriteRenderers[i].color = invincible ? invincibleColor : normal;
    }
}

これを StartInvincibility()ForceStopInvincibility() 内で呼び出すようにすれば、
「点滅+色変化」でよりリッチな無敵演出に発展させられます。

無敵時間のように、複数のオブジェクトで共通するロジックは、こうして1つのコンポーネントに切り出しておくと、
プロジェクト全体がかなりスッキリしてきます。ぜひ、自分のゲーム用にカスタマイズしてみてください。