Unityを触り始めた頃、つい何でもかんでも Update() に書いてしまいがちですよね。
「ダメージ処理」「ノックバック処理」「アニメーション切り替え」「入力処理」…全部1つのスクリプトに詰め込むと、だんだん手が付けられない「Godクラス」が出来上がってしまいます。

とくにアクションゲームでは、

  • ダメージは受けるけど、吹き飛びは無効化したい(ボスのスーパーアーマーなど)
  • 特定の攻撃中だけノックバックを止めたい

といった「状態によってノックバック挙動を切り替える」要件がよく出てきます。
これを1つの巨大なプレイヤー/敵スクリプトに全部書いてしまうと、条件分岐だらけになってメンテナンスが大変です。

そこで今回は、「ダメージは通すけど、ノックバックだけ無効化する」という責務だけを切り出した、
小さなコンポーネント 「SuperArmor」 を作ってみましょう。

【Unity】吹き飛ばないタフなキャラを作る!「SuperArmor」コンポーネント

この記事では、以下のような構成を前提にします。

  • KnockbackReceiver … ノックバック(吹き飛び)を処理するコンポーネント
  • Damageable … ダメージを受けてHPを減らすコンポーネント
  • SuperArmor … 「ダメージは通すが、ノックバックを無効化する」状態を管理するコンポーネント

SuperArmor自体は「ノックバックのON/OFFを制御するだけ」に責務を限定し、
ノックバックの実装やダメージ処理は別コンポーネントに任せる設計にします。


前提:シンプルな KnockbackReceiver と Damageable

まずは、SuperArmor と連携するためのシンプルな KnockbackReceiverDamageable を用意します。
既に自作のものがある場合は、それに合わせて IsKnockbackDisabled のようなフラグを読みに行く形に改造してもOKです。

using UnityEngine;

/// <summary>ノックバック(吹き飛び)を処理するコンポーネント</summary>
[RequireComponent(typeof(Rigidbody))]
public class KnockbackReceiver : MonoBehaviour
{
    [Header("ノックバック設定")]
    [SerializeField] private float knockbackForce = 10f;   // ノックバックの基本力
    [SerializeField] private float verticalForce = 2f;     // 少し浮かせる場合の上方向成分

    private Rigidbody _rb;

    // 外部からノックバック無効化を制御するためのフラグ
    public bool IsKnockbackDisabled { get; set; }

    private void Awake()
    {
        _rb = GetComponent<Rigidbody>();
    }

    /// <summary>
    /// 攻撃側から呼び出してノックバックさせる
    /// direction は「攻撃された方向とは逆向き」のベクトルを想定
    /// </summary>
    public void ApplyKnockback(Vector3 direction)
    {
        // スーパーアーマーなどでノックバックが無効な場合は何もしない
        if (IsKnockbackDisabled)
        {
            // デバッグ用ログ(必要に応じてコメントアウト)
            // Debug.Log($"{name}: Knockback is disabled.");
            return;
        }

        // direction がゼロベクトルの場合は安全のためリターン
        if (direction.sqrMagnitude < 0.0001f)
        {
            return;
        }

        // 正規化して力を計算
        Vector3 knockDir = direction.normalized;
        Vector3 force = (knockDir * knockbackForce) + (Vector3.up * verticalForce);

        // 既存の速度を少し抑えてからノックバックを加える
        _rb.velocity = new Vector3(_rb.velocity.x * 0.3f, _rb.velocity.y, _rb.velocity.z * 0.3f);
        _rb.AddForce(force, ForceMode.VelocityChange);
    }
}

/// <summary>ダメージを受けるコンポーネント(HP管理)</summary>
public class Damageable : MonoBehaviour
{
    [Header("HP設定")]
    [SerializeField] private int maxHp = 100;

    private int _currentHp;

    // HPの現在値を外部から読み取りたい場合用
    public int CurrentHp => _currentHp;

    private void Awake()
    {
        _currentHp = maxHp;
    }

    /// <summary>ダメージを受ける</summary>
    public void TakeDamage(int amount)
    {
        if (amount <= 0) return;

        _currentHp -= amount;
        _currentHp = Mathf.Max(_currentHp, 0);

        // ここでアニメーションやSE再生などを呼び出してもOK
        // Debug.Log($"{name} took {amount} damage. HP: {_currentHp}/{maxHp}");

        if (_currentHp == 0)
        {
            OnDead();
        }
    }

    private void OnDead()
    {
        // 死亡時の処理(とりあえず非アクティブ化)
        // 実際のゲームではアニメーション再生やリスポーン処理などをここに
        gameObject.SetActive(false);
    }
}

本題:SuperArmor コンポーネントの実装

いよいよ SuperArmor 本体です。
SuperArmor は以下の責務だけを持ちます。

  • ノックバック無効フラグを管理する
  • KnockbackReceiver にその状態を反映する
  • 任意のタイミングで ON/OFF できる API を提供する

ダメージ処理は Damageable が担当し、SuperArmor は一切触りません。
「ダメージは受けるが、吹き飛びだけ無効化する」という分離がポイントです。

using System.Collections;
using UnityEngine;

/// <summary>
/// スーパーアーマー状態を管理するコンポーネント。
/// ダメージは受けるが、KnockbackReceiver による吹き飛びを無効化する。
/// </summary>
[RequireComponent(typeof(KnockbackReceiver))]
public class SuperArmor : MonoBehaviour
{
    [Header("初期状態")]
    [SerializeField]
    private bool startWithSuperArmor = false; 
    // 最初からスーパーアーマー状態にするかどうか(ボスなど)

    [Header("デバッグ用表示")]
    [SerializeField]
    private bool showDebugLog = false;

    private KnockbackReceiver _knockbackReceiver;

    // 現在スーパーアーマーが有効かどうか(外部から読み取り専用)
    public bool IsActive { get; private set; }

    private Coroutine _autoDisableCoroutine;

    private void Awake()
    {
        _knockbackReceiver = GetComponent<KnockbackReceiver>();
    }

    private void Start()
    {
        // 初期状態を反映
        if (startWithSuperArmor)
        {
            EnableSuperArmor();
        }
        else
        {
            DisableSuperArmor();
        }
    }

    /// <summary>
    /// スーパーアーマーを有効化する(ノックバック無効)。
    /// </summary>
    public void EnableSuperArmor()
    {
        IsActive = true;
        _knockbackReceiver.IsKnockbackDisabled = true;

        if (showDebugLog)
        {
            Debug.Log($"[{name}] SuperArmor ENABLED");
        }
    }

    /// <summary>
    /// スーパーアーマーを無効化する(ノックバック有効)。
    /// </summary>
    public void DisableSuperArmor()
    {
        IsActive = false;
        _knockbackReceiver.IsKnockbackDisabled = false;

        if (showDebugLog)
        {
            Debug.Log($"[{name}] SuperArmor DISABLED");
        }
    }

    /// <summary>
    /// 一定時間だけスーパーアーマーを付与する。
    /// 例:攻撃モーション中だけ、被弾しても吹き飛ばないようにする。
    /// </summary>
    /// <param name="duration">付与時間(秒)</param>
    public void EnableForSeconds(float duration)
    {
        if (duration <= 0f)
        {
            // 0以下の場合は即時OFF扱い
            DisableSuperArmor();
            return;
        }

        // すでにタイマーが動いていたらキャンセル
        if (_autoDisableCoroutine != null)
        {
            StopCoroutine(_autoDisableCoroutine);
        }

        _autoDisableCoroutine = StartCoroutine(AutoDisableRoutine(duration));
    }

    /// <summary>
    /// 外部から「今スーパーアーマー中ならノックバックを飛ばしてほしい」などの判定に使えるヘルパー。
    /// </summary>
    public bool IsSuperArmorActive()
    {
        return IsActive;
    }

    /// <summary>
    /// 一定時間後に自動でスーパーアーマーをOFFにするコルーチン。
    /// </summary>
    private IEnumerator AutoDisableRoutine(float duration)
    {
        EnableSuperArmor();

        float timer = 0f;
        while (timer < duration)
        {
            timer += Time.deltaTime;
            yield return null;
        }

        DisableSuperArmor();
        _autoDisableCoroutine = null;
    }
}

攻撃側からの利用例:ダメージ+ノックバックを与えるコンポーネント

SuperArmor の動作を確認するために、攻撃判定用の簡単なコンポーネントも用意しておきます。
プレイヤーや敵の「武器」オブジェクトに付けて、OnTriggerEnter でダメージとノックバックを与えるイメージです。

using UnityEngine;

/// <summary>
/// 当たった相手にダメージ+ノックバックを与えるシンプルな攻撃コンポーネント。
/// </summary>
public class SimpleAttacker : MonoBehaviour
{
    [Header("攻撃設定")]
    [SerializeField] private int damage = 10;
    [SerializeField] private float knockbackPower = 1f; // KnockbackReceiver の knockbackForce に乗算する係数

    [Header("攻撃者のTransform(ノックバック方向計算用)")]
    [SerializeField] private Transform attackerRoot;

    private void Reset()
    {
        // デフォルトで自分自身を攻撃者の基準にする
        attackerRoot = transform;
    }

    private void OnTriggerEnter(Collider other)
    {
        // ダメージ処理
        Damageable damageable = other.GetComponentInParent<Damageable>();
        if (damageable != null)
        {
            damageable.TakeDamage(damage);
        }

        // ノックバック処理
        KnockbackReceiver knockbackReceiver = other.GetComponentInParent<KnockbackReceiver>();
        if (knockbackReceiver != null)
        {
            // 「攻撃された側から見てどの方向に吹き飛ぶか」を計算
            Vector3 dir = (other.transform.position - attackerRoot.position).normalized;

            // Power を掛けて方向ベクトルをスケール
            knockbackReceiver.ApplyKnockback(dir * knockbackPower);
        }
    }
}

SuperArmor が有効なキャラにこの攻撃を当てると、HPは減るがノックバックは発生しない挙動になります。


使い方の手順

  1. プレイヤー or 敵キャラのプレハブに基礎コンポーネントを追加
    例として、3Dアクションゲームの敵キャラを想定します。
    • 敵キャラの GameObject(例:Enemy)を選択
    • Rigidbody を追加(Use Gravity ON / Constraints は必要に応じて設定)
    • KnockbackReceiver を追加
    • Damageable を追加
  2. SuperArmor コンポーネントを追加
    同じ Enemy GameObject に SuperArmor を追加します。
    • Start With Super Armor を ON にすると、常時吹き飛ばないボスのような挙動になります。
    • OFF にしておけば、スクリプトから任意のタイミングで有効化できます。
    • 動作確認用に Show Debug Log を ON にしておくと、状態変化がコンソールに出ます。
  3. 攻撃側(プレイヤー武器や敵の当たり判定)に SimpleAttacker を追加
    例:プレイヤーの剣オブジェクトに以下を設定します。
    • 剣オブジェクトに Collider(Is Trigger = ON)を追加
    • SimpleAttacker を追加
    • Attacker Root にプレイヤーのルート Transform をドラッグ&ドロップ
    • ダメージ量や Knockback Power を調整

    これで剣の当たり判定が敵に触れると、DamageableKnockbackReceiver に処理が飛びます。

  4. 一時的なスーパーアーマーをスクリプトから付与する
    例として、敵の攻撃モーション中だけスーパーアーマーを付与したい場合、
    敵AIやアニメーション制御スクリプトから次のように呼び出します。
    public class EnemyAttackSample : MonoBehaviour
    {
        [SerializeField] private SuperArmor superArmor;
        [SerializeField] private float attackSuperArmorDuration = 0.8f;
    
        public void StartAttack()
        {
            // 攻撃開始時にスーパーアーマーを一定時間付与
            superArmor.EnableForSeconds(attackSuperArmorDuration);
    
            // ここで攻撃アニメーション再生などを行う
            // animator.SetTrigger("Attack");
        }
    }
    

    これで「攻撃モーション中だけ、被弾しても吹き飛ばない敵」が簡単に作れます。
    プレイヤー側でも同じように適用できるので、「強攻撃中だけスーパーアーマー」などの実装にも便利ですね。


メリットと応用

SuperArmor を独立したコンポーネントとして切り出すことで、次のようなメリットがあります。

  • プレハブのバリエーション管理が楽になる
    • 通常敵:Damageable + KnockbackReceiver
    • 常時スーパーアーマー敵:Damageable + KnockbackReceiver + SuperArmor(startWithSuperArmor = true)
    • 一部攻撃中のみスーパーアーマー敵:SuperArmor を追加し、AIから EnableForSeconds() を呼ぶ

    というように、コンポーネントの組み合わせだけで挙動を切り替えられます。

  • 巨大なプレイヤー/敵スクリプトを避けられる
    ノックバック無効化のロジックをプレイヤーや敵のメインスクリプトに書き始めると、
    条件分岐(if文)だらけになって保守しづらくなります。
    SuperArmor に「ノックバック無効化」という責務を閉じ込めることで、
    他のコンポーネントは「スーパーアーマーがあるかどうか」を気にせずシンプルに書けます。
  • アニメーションやステートマシンとの連携がしやすい
    Animator の StateMachineBehaviour やタイムラインから EnableSuperArmor() / DisableSuperArmor() を呼ぶだけで、
    特定のアニメーション中だけスーパーアーマーを付与する、といった制御も簡単に行えます。

さらに応用として、「特定の攻撃タイプだけノックバック無効」 といった拡張も考えられます。
例えば「小攻撃のノックバックは無効だが、必殺技のノックバックは通す」といったボス挙動です。

その一歩として、「スーパーアーマー中でも、強制的にノックバックを通す」ための
簡単な API を SuperArmor に追加する改造案を示しておきます。

/// <summary>
/// スーパーアーマー中であっても、強制的にノックバックを適用したいときに呼ぶ。
/// 例:必殺技による強制吹き飛ばしなど。
/// </summary>
public void ForceKnockback(Vector3 direction)
{
    // 一時的にフラグを無効化してノックバックを通す
    bool prevState = _knockbackReceiver.IsKnockbackDisabled;
    _knockbackReceiver.IsKnockbackDisabled = false;
    _knockbackReceiver.ApplyKnockback(direction);
    _knockbackReceiver.IsKnockbackDisabled = prevState;
}

このように小さなコンポーネント単位で責務を分けておくと、
「このボスだけ特殊な挙動をさせたい」といった要件にも柔軟に対応しやすくなります。
ぜひ自分のプロジェクトに合わせて SuperArmor を育ててみてください。