Unityを触り始めた頃は、ダメージ処理も無敵時間も点滅演出も、つい Update() の中に全部書いてしまいがちですよね。
「HPの管理」「ダメージ判定」「無敵時間」「見た目の点滅」が1つの巨大スクリプトにまとまると、次のような問題が出てきます。
- 処理の追加・修正をすると、別の機能が壊れやすい
- 敵・プレイヤー・トラップなど、全部に同じようなコードをコピペすることになる
- テストしたい機能だけを個別に確認しづらい
そこでこの記事では、「ダメージを受けたあと一定時間だけ無敵になり、その間は Hurtbox の監視を止めつつ、見た目を点滅させる」専用のコンポーネントを用意して、役割をしっかり分けていきます。
無敵時間だけをきれいにコンポーネント化しておけば、プレイヤーにも敵にもボスにも、同じ仕組みをそのまま再利用できて便利ですね。
【Unity】ダメージ後の無敵&点滅をまるっと担当!「Invincibility」コンポーネント
ここでは、以下のような前提で設計します。
- Hurtbox:ダメージ判定を行うコンポーネント(この記事では「ダメージを受ける側」の当たり判定担当と想定)
- Invincibility:ダメージを受けたタイミングで呼び出され、一定時間 Hurtbox を無効化し、見た目を点滅させる
ダメージを受ける側のスクリプト(例:Health コンポーネント)から、Invincibility の StartInvincibility() を呼ぶだけで動くようにしておきます。
フルコード: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;
}
}
}
}
使い方の手順
ここからは、具体的な使用例を交えつつ、セットアップ手順を見ていきましょう。
手順①:スクリプトをプロジェクトに追加
Invincibility.csを作成し、上記のコードをコピペして保存します。- Unity に戻ると自動でコンパイルされます。
手順②:プレイヤー(または敵)にアタッチ
例として「プレイヤーキャラクター」に無敵時間を付けたい場合:
- プレイヤーのプレハブ(またはシーン上の GameObject)を選択。
Add ComponentからInvincibilityを追加。- 同じオブジェクト、または子オブジェクトに「Hurtbox」的なダメージ判定コンポーネントがある前提で、
Hurtboxフィールドに、そのコンポーネントをドラッグ&ドロップします。 - 見た目を点滅させたいルートオブジェクトを
Blink Rootに設定します。
特にこだわりがなければ、プレイヤー本体の GameObject を指定しておけばOKです。 - 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で敵の攻撃判定を検知するコンポーネントなどを想定しています。 - 敵キャラクター
ボス敵に無敵時間をつけて、「連続ヒットで一瞬で溶ける」問題を防ぎます。
HealthのTakeDamage()内でStartInvincibility()を呼ぶだけで、プレイヤーと同じ無敵ロジックを使い回せます。 - 動く床(トラップ床)
床自体がダメージを受けるギミック(例:プレイヤーが乗ると床が壊れる)にも応用できます。
壊れる前に数回だけダメージを受ける床にして、ダメージを食らうたびに点滅+無敵時間で「今はこれ以上壊れない」演出を入れると、プレイヤーにとって視覚的にわかりやすくなります。
メリットと応用
この Invincibility コンポーネントを使うことで、次のようなメリットがあります。
- 責務が分離される
HP 管理(Health)と無敵時間・点滅(Invincibility)が別コンポーネントになることで、
それぞれのコードがコンパクトに保たれ、保守性が上がります。 - プレハブの再利用性が高まる
「ダメージを受けるオブジェクト」ならどれでも、Invincibilityをポン付けしてStartInvincibility()を呼ぶだけで同じ挙動を共有できます。
敵・プレイヤー・トラップなど、共通の演出を一括で調整できるのはかなり楽です。 - レベルデザインがやりやすくなる
レベルデザイナー(または自分の未来の自分)が、invincibleDurationやblinkFrequencyをインスペクターから調整するだけで、ゲームの難易度やテンポを変えられます。
スクリプトを書き換えずにゲームバランスをいじれるのは大きな利点ですね。
改造案:無敵中は色を変える(半透明+赤色)
点滅だけでなく、「無敵中は少し赤く半透明にする」といった演出を追加したい場合は、
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つのコンポーネントに切り出しておくと、
プロジェクト全体がかなりスッキリしてきます。ぜひ、自分のゲーム用にカスタマイズしてみてください。
