Unityを触り始めた頃にありがちなのが、Update() の中に「移動」「入力」「エフェクト」「ダメージ処理」「爆発処理」などを全部まとめて書いてしまうパターンです。
最初は動くので気持ちいいのですが、あとから「爆発の仕様をちょっと変えたい」「敵にも同じ爆風を使いたい」となったときに、巨大なスクリプトの中から関連コードを探すハメになり、修正のたびにバグが増えていきます。

こういうときに意識したいのが「1コンポーネント = 1つの責務」という考え方ですね。
爆発の物理的な押し出しだけを担当する小さなコンポーネントに分けておけば、プレイヤーのスクリプトや敵のAIスクリプトとは独立して、爆風ロジックだけを再利用・調整できます。

この記事では、爆発時に、範囲内の Rigidbody を中心から外側へ吹き飛ばす専用コンポーネントとして、ExplosionForce を実装していきます。

【Unity】一発で吹き飛ばす爆風物理!「ExplosionForce」コンポーネント

今回作る ExplosionForce は、こんなイメージで使います。

  • 爆発エフェクトのプレハブにアタッチしておく
  • 爆発が発生したタイミングで Explode() を呼ぶ(または自動で Start 時に爆発)
  • 指定した半径内の Rigidbody に対して、爆心地から外側へ力を加える

物理的な押し出しだけに責務を絞ることで、「ダメージ計算」「エフェクト表示」「サウンド再生」などとはきれいに分離できます。

フルコード:ExplosionForce.cs


using System.Collections.Generic;
using UnityEngine;

/// <summary> 
/// 爆発時に、指定半径内の Rigidbody に爆風の力を与えるコンポーネント。
/// ・物理的な押し出しのみを担当(ダメージやエフェクトは別コンポーネントに分離推奨)
/// ・OnDrawGizmos で爆風範囲を可視化可能
/// </summary>
[DisallowMultipleComponent]
public class ExplosionForce : MonoBehaviour
{
    // --- 爆風の基本パラメータ ---

    [Header("Explosion Settings")]
    [Tooltip("爆風の中心位置。未指定ならこのオブジェクトの Transform.position を使用")]
    [SerializeField] private Transform explosionCenter;

    [Tooltip("爆風の最大半径(この距離までの Rigidbody に力を与える)")]
    [SerializeField] private float radius = 5f;

    [Tooltip("爆風の強さ(大きいほど強く吹き飛ばす)")]
    [SerializeField] private float force = 10f;

    [Tooltip("爆風の上向き補正。0 なら完全に水平、値を上げると少し上方向に持ち上がる")]
    [SerializeField] private float upwardsModifier = 0.0f;

    [Tooltip("力の加え方(Impulse 推奨:瞬間的な爆発力)")]
    [SerializeField] private ForceMode forceMode = ForceMode.Impulse;

    [Header("Falloff Settings")]
    [Tooltip("距離による減衰を有効にするか(true: 爆心地に近いほど強く、遠いほど弱くなる)")]
    [SerializeField] private bool useDistanceFalloff = true;

    [Tooltip("減衰カーブ。横軸: 距離0〜1(0:中心,1:半径端) 縦軸: 強さの倍率")]
    [SerializeField] private AnimationCurve falloffCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);

    [Header("Layer / Filter Settings")]
    [Tooltip("爆風の対象とするレイヤー(例: Player, Enemy, DynamicObjects など)")]
    [SerializeField] private LayerMask targetLayers = ~0; // デフォルトは全レイヤー

    [Tooltip("自分自身にアタッチされている Rigidbody を除外するか")]
    [SerializeField] private bool ignoreSelfRigidbody = true;

    [Header("Timing")]
    [Tooltip("Start() で自動的に爆発させるか(プレハブ生成と同時に爆発させたい場合など)")]
    [SerializeField] private bool explodeOnStart = true;

    [Tooltip("自動爆発の場合、生成から何秒後に爆発させるか")]
    [SerializeField] private float explodeDelay = 0.0f;

    [Header("Debug")]
    [Tooltip("Scene ビューに爆風の半径をワイヤーフレームで表示するか")]
    [SerializeField] private bool drawGizmos = true;

    [Tooltip("Gizmos の色")]
    [SerializeField] private Color gizmoColor = new Color(1f, 0.5f, 0f, 0.3f);

    // すでに爆発済みかどうかのフラグ(誤爆防止)
    private bool hasExploded = false;

    // 自身にアタッチされている Rigidbody(ignoreSelfRigidbody 用)
    private Rigidbody selfRigidbody;

    private void Awake()
    {
        // 自身の Rigidbody をキャッシュ(存在しない場合は null)
        selfRigidbody = GetComponent<Rigidbody>();
    }

    private void Start()
    {
        // 自動爆発が有効なら、Start で爆発を予約
        if (explodeOnStart)
        {
            if (explodeDelay <= 0f)
            {
                Explode();
            }
            else
            {
                Invoke(nameof(Explode), explodeDelay);
            }
        }
    }

    /// <summary>
    /// 爆発を実行し、半径内の Rigidbody に力を与えるメイン関数。
    /// 外部のスクリプトから手動で呼び出してもOK。
    /// 例: GetComponent<ExplosionForce>().Explode();
    /// </summary>
    public void Explode()
    {
        // 二重に爆発させないようにガード
        if (hasExploded)
        {
            return;
        }

        hasExploded = true;

        // 爆心地の取得(未指定なら自分の位置)
        Vector3 center = explosionCenter != null
            ? explosionCenter.position
            : transform.position;

        // 半径内にあるコライダーをすべて取得
        Collider[] hitColliders = Physics.OverlapSphere(center, radius, targetLayers, QueryTriggerInteraction.Ignore);

        // 重複した Rigidbody への多重適用を防ぐため、一時的な HashSet で管理
        HashSet<Rigidbody> processedRigidbodies = new HashSet<Rigidbody>();

        foreach (Collider col in hitColliders)
        {
            if (col.attachedRigidbody == null)
            {
                // Rigidbody を持たないオブジェクトはスキップ
                continue;
            }

            Rigidbody rb = col.attachedRigidbody;

            // 自分自身の Rigidbody を除外するオプション
            if (ignoreSelfRigidbody && rb == selfRigidbody)
            {
                continue;
            }

            // 同じ Rigidbody に対しては一度だけ処理する
            if (processedRigidbodies.Contains(rb))
            {
                continue;
            }
            processedRigidbodies.Add(rb);

            // 爆心地から対象へのベクトル
            Vector3 direction = (rb.worldCenterOfMass - center);

            // 爆心地と完全に同じ位置にいる場合、方向が出せないのでランダム方向に吹き飛ばす
            if (direction.sqrMagnitude <= Mathf.Epsilon)
            {
                direction = Random.onUnitSphere;
            }

            float distance = direction.magnitude;
            Vector3 normalizedDirection = direction / distance;

            // 距離による減衰係数を計算(0〜1)
            float distanceFactor = 1f;
            if (useDistanceFalloff)
            {
                // 0:中心, 1:半径の端
                float t = Mathf.Clamp01(distance / radius);
                distanceFactor = Mathf.Clamp01(falloffCurve.Evaluate(t));
            }

            // 実際に適用する力の大きさ
            float finalForce = force * distanceFactor;

            // 上向き補正を適用(Unity の AddExplosionForce と同様のイメージ)
            Vector3 finalDirection = normalizedDirection;
            if (!Mathf.Approximately(upwardsModifier, 0f))
            {
                // 上方向成分を足して少し持ち上がるようにする
                finalDirection += Vector3.up * upwardsModifier;
                finalDirection.Normalize();
            }

            // 最終的な力のベクトル
            Vector3 forceVector = finalDirection * finalForce;

            // Rigidbody に力を加える
            rb.AddForce(forceVector, forceMode);
        }
    }

    /// <summary>
    /// Scene ビューで爆風半径を可視化する。
    /// レベルデザイン時の調整に便利。
    /// </summary>
    private void OnDrawGizmosSelected()
    {
        if (!drawGizmos)
        {
            return;
        }

        Gizmos.color = gizmoColor;

        Vector3 center = explosionCenter != null
            ? explosionCenter.position
            : transform.position;

        Gizmos.DrawWireSphere(center, radius);
    }

    /// <summary>
    /// 外部からパラメータを一括設定したいとき用のヘルパー関数。
    /// 爆発プレハブをスクリプトから生成する場合などに便利。
    /// </summary>
    public void Setup(float radius, float force, float upwardsModifier, LayerMask targetLayers)
    {
        this.radius = radius;
        this.force = force;
        this.upwardsModifier = upwardsModifier;
        this.targetLayers = targetLayers;
    }
}

使い方の手順

ここでは、代表的な3パターンの使い方を例にしながら手順を説明します。

手順①:プレハブに ExplosionForce を仕込む

  1. Hierarchy で空の GameObject を作成し、名前を Explosion などにします。
  2. ExplosionForce.cs をこの GameObject にアタッチします。
  3. 必要であれば、パーティクル(爆発エフェクト)や AudioSource(爆発音)などを子オブジェクトとしてぶら下げます。
  4. この GameObject を Project ビューにドラッグしてプレハブ化します。

これで「爆風だけを扱うプレハブ」ができました。
パラメータは Inspector から自由に調整できます。

  • Radius: 爆風の届く距離
  • Force: 吹き飛ばしの強さ
  • Upwards Modifier: 0.5〜1.0 くらいにすると、少し上方向に飛びやすくなって気持ちいいです
  • Target Layers: Player, Enemy, DynamicObjects など、動かしたいレイヤーを指定

手順②:プレイヤーのグレネードに使う例

例えば、プレイヤーが投げるグレネードが一定時間後に爆発する場合、グレネードのプレハブ側で ExplosionForce を呼び出します。


using UnityEngine;

public class Grenade : MonoBehaviour
{
    [SerializeField] private ExplosionForce explosionForce;
    [SerializeField] private float fuseTime = 2.0f; // 起爆までの時間

    private void Start()
    {
        // 一定時間後に爆発させる
        Invoke(nameof(Detonate), fuseTime);
    }

    private void Detonate()
    {
        if (explosionForce != null)
        {
            explosionForce.Explode();
        }

        // 爆発エフェクトなどを再生したあと、自分自身を削除する想定
        Destroy(gameObject, 0.1f);
    }
}

このように、グレネードは「いつ爆発するか」だけを知っていて、爆風の物理処理は ExplosionForce に丸投げできます。
責務が分かれているので、爆風の仕様変更があっても ExplosionForce だけをいじれば済みます。

手順③:敵の自爆攻撃に使う例

敵がプレイヤーに近づいて自爆するタイプの攻撃でも、同じ ExplosionForce を再利用できます。

  • 敵のプレハブに ExplosionForce をアタッチしておく
  • HP が 0 になったり、一定条件を満たしたときに Explode() を呼ぶ

敵の AI ロジックは「いつ自爆するか」を決めるだけで、吹き飛ばしの挙動は共通化できます。

手順④:動く床や破壊可能オブジェクトへの利用例

動く床や破壊可能オブジェクトにも Rigidbody を付けておけば、ExplosionForce でまとめて吹き飛ばせます。

  • 「破壊可能オブジェクト」レイヤーを作成し、それらのオブジェクトに設定
  • ExplosionForce の Target Layers にそのレイヤーを含める

レベルデザイン時には、Scene ビューで ExplosionForce のオブジェクトを配置し、OnDrawGizmos のワイヤーフレームを見ながら半径を調整すると、どの範囲までオブジェクトが吹き飛ぶか一目で分かって便利です。

メリットと応用

ExplosionForce を単独のコンポーネントとして切り出すメリットは多いです。

  • 責務が明確:爆風の物理処理だけを担当するので、ダメージやエフェクトとは独立して調整可能。
  • プレハブの再利用性が高い:プレイヤー用グレネード、敵の自爆、トラップなど、あらゆる爆発系ギミックで同じコンポーネントを使い回せる。
  • レベルデザインが楽:Gizmos で爆風範囲が見えるため、「ここで爆発したらこの箱が落ちる」といった配置調整が直感的。
  • テストしやすい:ExplosionForce だけをシーンに置いて、パラメータをいじりながら挙動を確認できる。

また、減衰カーブを AnimationCurve で持っているので、

  • 中心付近はほぼ一定の強さ、端に近づくほど急激に弱くする
  • 逆に、中心は弱め・中間距離が一番強いなど、変わった爆風パターン

といった「ゲームらしいチューニング」も Inspector から直感的に行えます。

改造案:タグで対象をフィルタリングする

もっと細かく対象を絞りたい場合、「特定のタグを持つ Rigidbody にのみ爆風を適用する」ような改造も簡単にできます。
例えば、ExplosionForce 内に次のようなヘルパー関数を追加して、Explode() から呼ぶロジックを切り替えるのもアリです。


/// <summary>
/// 指定したタグを持つ Rigidbody のみを爆風対象とするサンプル関数。
/// 例: "Player" だけ吹き飛ばしたい場合など。
/// </summary>
private void ApplyExplosionToTaggedRigidbodies(Vector3 center, string targetTag)
{
    Collider[] hitColliders = Physics.OverlapSphere(center, radius, targetLayers, QueryTriggerInteraction.Ignore);

    foreach (Collider col in hitColliders)
    {
        if (!col.CompareTag(targetTag))
        {
            continue;
        }

        Rigidbody rb = col.attachedRigidbody;
        if (rb == null)
        {
            continue;
        }

        // ここから先は Explode() 内のロジックとほぼ同様に力を加える
        Vector3 direction = (rb.worldCenterOfMass - center);
        if (direction.sqrMagnitude <= Mathf.Epsilon)
        {
            direction = Random.onUnitSphere;
        }

        float distance = direction.magnitude;
        Vector3 normalizedDirection = direction / distance;

        float distanceFactor = 1f;
        if (useDistanceFalloff)
        {
            float t = Mathf.Clamp01(distance / radius);
            distanceFactor = Mathf.Clamp01(falloffCurve.Evaluate(t));
        }

        float finalForce = force * distanceFactor;
        Vector3 finalDirection = normalizedDirection;
        if (!Mathf.Approximately(upwardsModifier, 0f))
        {
            finalDirection += Vector3.up * upwardsModifier;
            finalDirection.Normalize();
        }

        Vector3 forceVector = finalDirection * finalForce;
        rb.AddForce(forceVector, forceMode);
    }
}

このように、ExplosionForce は「爆風の物理」を担当する小さなモジュールとして設計しておくと、タグやダメージ処理などのゲーム固有ロジックを、別コンポーネントやヘルパー関数として後からいくらでも積み増しできるようになります。
巨大な God クラスを作る前に、まずはこうした小さなコンポーネントから積み上げていきましょう。