Unityのプロジェクトが少し大きくなってくると、つい「とりあえず全部PlayerControllerのUpdateに書いてしまう」という実装になりがちですよね。
移動処理、ジャンプ、ダッシュ、ダメージ、ノックバック、アニメーション…すべてを1つのスクリプトに詰め込むと、
- どこで何が起きているか追えない
- ノックバックだけ仕様変更したいのに、プレイヤー全体のコードを触る必要が出る
- 敵やギミックにも同じノックバック処理を使い回したいのに、コピペ地獄になる
といった問題が発生しやすくなります。
そこで今回は、「ノックバックの受け取り」と「一時的な入力無効化」だけに責務を絞ったコンポーネントを用意して、プレイヤーや敵などに後付けできるようにしてみましょう。
題して、「KnockbackReceiver(ノックバック受付)」コンポーネントです。
【Unity】入力を一時停止して吹き飛ばす!「KnockbackReceiver」コンポーネント
このコンポーネントは、
- 外部から「ノックバック方向と強さ」をベクトルとして受け取る
- 一定時間、親オブジェクトの操作入力を無効化する(移動スクリプトなど)
- Rigidbody2D / Rigidbody に力を与えて吹き飛ばす
という役割にだけ集中させます。
「ダメージ計算」や「HP管理」「入力の読み取り」などは別コンポーネントに任せる前提ですね。
対応パターン
- 2Dゲーム(Rigidbody2D)
- 3Dゲーム(Rigidbody)
どちらにも対応できるように、2D/3Dを切り替え可能な設計にしてあります。
KnockbackReceiver.cs(フルコード)
using System.Collections;
using UnityEngine;
namespace Sample.Knockback
{
/// <summary>
/// 外部からノックバックベクトルを受け取り、
/// 一定時間入力を無効化しつつ、Rigidbody に力を与えるコンポーネント。
///
/// ・ダメージ計算やHP管理は別コンポーネントに任せる
/// ・「ノックバック」と「入力の一時停止」だけを担当する
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(Rigidbody))]
public class KnockbackReceiver : MonoBehaviour
{
// --- 設定項目(インスペクターから調整) ---
[Header("次のコンポーネントの入力を一時停止します")]
[Tooltip("移動や操作入力を司るコンポーネントをアサインします(例: PlayerController, CharacterMover など)。\n" +
"INockbackControllable を実装していると自動検出も行われます。")]
[SerializeField] private MonoBehaviour[] controllableComponents;
[Header("ノックバック設定")]
[Tooltip("ノックバック中の入力無効時間(秒)")]
[SerializeField] private float disableInputDuration = 0.4f;
[Tooltip("ノックバックを受けたときに現在の速度をリセットするかどうか")]
[SerializeField] private bool resetVelocityOnKnockback = true;
[Tooltip("ノックバック方向ベクトルに掛ける倍率。外部からのベクトルが正規化されていない場合の保険として使えます。")]
[SerializeField] private float knockbackPowerMultiplier = 1.0f;
[Header("物理設定(3D/2D 切り替え)")]
[Tooltip("true: 2D物理(Rigidbody2D)を使う / false: 3D物理(Rigidbody)を使う")]
[SerializeField] private bool use2DPhysics = false;
[Tooltip("ノックバック適用時に、Y成分をゼロにして水平ノックバックにするかどうか")]
[SerializeField] private bool forceHorizontal = false;
[Header("デバッグ")]
[Tooltip("ノックバック中かどうか(読み取り専用・デバッグ用)")]
[SerializeField] private bool isInKnockback = false;
// --- 内部参照 ---
private Rigidbody _rigidbody3D;
private Rigidbody2D _rigidbody2D;
// INockbackControllable を実装しているコンポーネントをキャッシュ
private INockbackControllable[] _controllables;
// 現在進行中のノックバックコルーチン
private Coroutine _knockbackRoutine;
// --- 公開インターフェース ---
/// <summary>
/// 外部からノックバックを要求するためのメソッド。
/// directionAndPower: ノックバック方向と強さを含むベクトル。
/// (例)右上へ強く飛ばす => new Vector3(1, 1.5f, 0) * 10f;
/// </summary>
/// <param name="directionAndPower">ノックバック方向と強さを表すベクトル</param>
/// <param name="durationOverride">null 以外を指定すると、入力無効時間を一時的に上書き</param>
public void ApplyKnockback(Vector3 directionAndPower, float? durationOverride = null)
{
// すでにノックバック中でも、上書きしたい場合はここで止めて再スタート
if (_knockbackRoutine != null)
{
StopCoroutine(_knockbackRoutine);
}
// 実際の処理はコルーチンで行う
_knockbackRoutine = StartCoroutine(KnockbackRoutine(directionAndPower, durationOverride));
}
/// <summary>
/// 現在ノックバック中かどうかを取得するプロパティ(読み取り専用)。
/// </summary>
public bool IsInKnockback => isInKnockback;
// --- Unity ライフサイクル ---
private void Awake()
{
// Rigidbody の取得(2D/3D両対応)
if (use2DPhysics)
{
_rigidbody2D = GetComponent<Rigidbody2D>();
if (_rigidbody2D == null)
{
Debug.LogError($"[{nameof(KnockbackReceiver)}] use2DPhysics が true ですが、Rigidbody2D が見つかりません。");
}
}
else
{
_rigidbody3D = GetComponent<Rigidbody>();
if (_rigidbody3D == null)
{
Debug.LogError($"[{nameof(KnockbackReceiver)}] use2DPhysics が false ですが、Rigidbody が見つかりません。");
}
}
// INockbackControllable 実装コンポーネントの自動検出
var list = new System.Collections.Generic.List<INockbackControllable>();
// 明示的にアサインされたコンポーネントを優先
if (controllableComponents != null)
{
foreach (var mb in controllableComponents)
{
if (mb is INockbackControllable controllable)
{
list.Add(controllable);
}
else if (mb != null)
{
Debug.LogWarning(
$"[{nameof(KnockbackReceiver)}] {mb.GetType().Name} は INockbackControllable を実装していません。"
);
}
}
}
// 自動検出(同一GameObject上の INockbackControllable)
var autoFound = GetComponents<MonoBehaviour>();
foreach (var mb in autoFound)
{
if (mb is INockbackControllable controllable && !list.Contains(controllable))
{
list.Add(controllable);
}
}
_controllables = list.ToArray();
}
// --- コルーチン本体 ---
private IEnumerator KnockbackRoutine(Vector3 directionAndPower, float? durationOverride)
{
isInKnockback = true;
// 入力無効化時間の決定
float duration = durationOverride ?? disableInputDuration;
// 水平方向のみのノックバックにしたい場合、Y成分をゼロにする
if (forceHorizontal)
{
directionAndPower.y = 0f;
}
// ベクトルに倍率を掛けて最終的なノックバックベクトルを作る
Vector3 finalForce = directionAndPower * knockbackPowerMultiplier;
// 速度リセット
if (resetVelocityOnKnockback)
{
if (use2DPhysics && _rigidbody2D != null)
{
_rigidbody2D.velocity = Vector2.zero;
}
else if (!use2DPhysics && _rigidbody3D != null)
{
_rigidbody3D.velocity = Vector3.zero;
}
}
// 入力を無効化
SetControllablesEnabled(false);
// 物理挙動でノックバックを適用
if (use2DPhysics && _rigidbody2D != null)
{
_rigidbody2D.AddForce(finalForce, ForceMode2D.Impulse);
}
else if (!use2DPhysics && _rigidbody3D != null)
{
_rigidbody3D.AddForce(finalForce, ForceMode.Impulse);
}
// 一定時間待機
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
yield return null;
}
// 入力を再度有効化
SetControllablesEnabled(true);
isInKnockback = false;
_knockbackRoutine = null;
}
// --- ヘルパー ---
/// <summary>
/// 入力制御コンポーネントの有効/無効を切り替える。
/// INockbackControllable を優先的に使い、なければ enabled を直接切り替える。
/// </summary>
private void SetControllablesEnabled(bool enabled)
{
if (_controllables == null || _controllables.Length == 0)
{
return;
}
foreach (var controllable in _controllables)
{
if (controllable == null) continue;
controllable.SetControlEnabled(enabled);
}
}
}
/// <summary>
/// KnockbackReceiver によって入力のON/OFFを制御されたいコンポーネントが
/// 実装すべきインターフェース。
///
/// 例:
/// - PlayerController
/// - EnemyAIController
/// - CharacterMovement など
/// </summary>
public interface INockbackControllable
{
/// <summary>
/// 入力や自律移動の有効/無効を切り替える。
/// KnockbackReceiver から呼ばれます。
/// </summary>
void SetControlEnabled(bool enabled);
}
}
サンプル:シンプルな2Dプレイヤー移動 & ノックバック連携
KnockbackReceiver と連携するための、最小限の2Dプレイヤー移動コンポーネントも載せておきます。
「入力のON/OFF」をインターフェース経由で受け取るだけのシンプルな実装です。
using UnityEngine;
using Sample.Knockback; // KnockbackReceiver の名前空間
/// <summary>
/// 非常にシンプルな2Dプレイヤー移動コンポーネント。
/// INockbackControllable を実装し、KnockbackReceiver から入力のON/OFF制御を受ける。
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class SimplePlayerMover2D : MonoBehaviour, INockbackControllable
{
[Header("移動設定")]
[SerializeField] private float moveSpeed = 5f;
private Rigidbody2D _rb;
private bool _controlEnabled = true;
private void Awake()
{
_rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
if (!_controlEnabled)
{
// ノックバック中など、入力が無効な場合は何もしない
return;
}
// キーボード入力(左右移動のみのシンプルな例)
float x = Input.GetAxisRaw("Horizontal");
Vector2 velocity = _rb.velocity;
velocity.x = x * moveSpeed;
_rb.velocity = velocity;
}
/// <summary>
/// KnockbackReceiver から呼ばれる入力ON/OFF。
/// </summary>
public void SetControlEnabled(bool enabled)
{
_controlEnabled = enabled;
if (!enabled)
{
// 入力停止時に移動速度だけ止めたい場合はここで調整
Vector2 v = _rb.velocity;
v.x = 0f;
_rb.velocity = v;
}
}
}
サンプル:敵からノックバックを与えるトリガー
敵やトゲ床などからプレイヤーにノックバックを与える例です。
オブジェクト同士が衝突したとき、相手の KnockbackReceiver を探して ApplyKnockback を呼ぶだけのシンプルなコンポーネントです。
using UnityEngine;
using Sample.Knockback;
/// <summary>
/// プレイヤーなどに触れたとき、ノックバックを与えるコンポーネント。
/// 2Dコライダーのトリガーを想定。
/// </summary>
[RequireComponent(typeof(Collider2D))]
public class KnockbackGiver2D : MonoBehaviour
{
[Header("ノックバック設定")]
[SerializeField] private float knockbackPower = 10f;
[SerializeField] private float knockbackUpwardPower = 4f;
[SerializeField] private float disableDuration = 0.4f;
[Header("対象レイヤー")]
[SerializeField] private LayerMask targetLayer;
private void OnTriggerEnter2D(Collider2D other)
{
// レイヤーマスクで対象を絞る
if ((targetLayer.value & (1 << other.gameObject.layer)) == 0)
{
return;
}
// KnockbackReceiver を探す(親も含めて検索)
KnockbackReceiver receiver = other.GetComponentInParent<KnockbackReceiver>();
if (receiver == null) return;
// 自分と相手の位置関係から、左右方向を決定
Vector3 dir = (other.transform.position - transform.position).normalized;
// 上方向の成分を少し足して、ふわっと飛ばす
dir.y = knockbackUpwardPower;
// パワーを掛けた最終ベクトル
Vector3 force = dir * knockbackPower;
// ノックバックを適用
receiver.ApplyKnockback(force, disableDuration);
}
}
使い方の手順
-
プレイヤー(または敵)オブジェクトに Rigidbody を追加
2DゲームならRigidbody2D、3DゲームならRigidbodyをアタッチします。
物理挙動で吹き飛ばしたいので、Body TypeはDynamicを選びましょう。 -
移動スクリプトを用意して INockbackControllable を実装
例として上記のSimplePlayerMover2Dをプレイヤーにアタッチします。
既存のプレイヤー制御スクリプトがある場合は、そのクラスにpublic void SetControlEnabled(bool enabled) { // enabled が false のときに入力を無視するように実装 }を追加して
INockbackControllableを実装してください。 -
KnockbackReceiver をプレイヤー(または敵)にアタッチ
use2DPhysicsを 2Dゲームならtrue、3Dゲームならfalseに設定disableInputDurationで「何秒間操作を無効にするか」を調整controllableComponentsに、入力を止めたいスクリプト(例:SimplePlayerMover2D)をドラッグ&ドロップ- もしくは、そのスクリプトが同じ GameObject 上にあれば、自動検出に任せてもOKです
-
ノックバックを与える側(敵やトゲ床)に KnockbackGiver をアタッチ
例として、KnockbackGiver2Dを敵オブジェクトにアタッチし、- 2D Collider を
Is Triggerにする targetLayerにプレイヤーのレイヤーを指定knockbackPowerとknockbackUpwardPowerで吹き飛び方を調整
こうしておけば、プレイヤーが敵に触れた瞬間に
ApplyKnockbackが呼ばれ、
一定時間プレイヤー操作が無効になりつつ、物理的に吹き飛ぶようになります。 - 2D Collider を
この構成なら、
- プレイヤーだけでなく、敵キャラや動く床にも KnockbackReceiver を付けて同じ仕組みを使い回せる
- 「ノックバックの仕様だけ変えたい」ときに、KnockbackReceiver だけを修正すればよい
- 移動スクリプトは「入力ON/OFFを受け取る」という責務だけ追加すればよく、肥大化を防げる
メリットと応用
この「KnockbackReceiver」コンポーネントを導入することで、
- プレハブの再利用性が上がる
プレイヤー用、敵用、ギミック用…とそれぞれの移動ロジックは別でも、
「ノックバックされる」という共通挙動だけは同じコンポーネントで表現できます。
つまり、「ノックバックされるキャラのプレハブ」を量産しやすくなります。 - レベルデザインが楽になる
トゲ床、爆発オブジェクト、風圧、パンチギミックなど、
「触れたらノックバックするギミック」をすべてKnockbackGiver2D / 3D系コンポーネントで統一できます。
レベルデザイナーは「どの方向に、どれくらい吹き飛ばすか」だけをインスペクターで調整すればOKになります。 - Godクラス化を防げる
ダメージ処理、アニメーション、サウンド、ノックバック…を全部 PlayerController に書くのではなく、
「ノックバック受付」は KnockbackReceiver に任せることで、責務が分離されます。
結果として、バグ調査や仕様変更時の影響範囲が小さくなります。
改造案:ノックバック終了時にコールバックを呼ぶ
「ノックバックが終わったら無敵時間を解除したい」「着地エフェクトを出したい」といったケース向けに、
KnockbackReceiver に終了時コールバックを追加する改造案です。
KnockbackReceiver の中に、例えばこんなメソッドを追加してみると便利です。
public void ApplyKnockbackWithCallback(
Vector3 directionAndPower,
System.Action onFinished,
float? durationOverride = null)
{
// 通常のノックバックを開始
ApplyKnockback(directionAndPower, durationOverride);
// もし既存コルーチンに手を加えたくない場合は、
// 別コルーチンでタイミングを管理するのもアリ
StartCoroutine(InvokeAfterKnockback(onFinished));
}
private IEnumerator InvokeAfterKnockback(System.Action onFinished)
{
// ノックバック終了を待つ
while (IsInKnockback)
{
yield return null;
}
onFinished?.Invoke();
}
これを使えば、
- ノックバック終了後に「無敵フラグを折る」
- ノックバック終了後に「立ちポーズに戻すアニメーションを再生する」
といった処理を、さらに別コンポーネントに切り出して実装しやすくなります。
ノックバックという1つの挙動も、コンポーネント化して責務を分けることで、
プロジェクト全体の見通しがかなり良くなるので、ぜひ取り入れてみてください。




