【Cocos Creator 3.8】KnockbackReceiver の実装:アタッチするだけで「外部からノックバックを受けて一定時間だけ操作不能&吹き飛び」を実現する汎用スクリプト
このコンポーネントは、敵の攻撃や爆発などから「ノックバック(吹き飛び)」を受けたときに、
- 指定ベクトル方向へ一定時間だけ移動させる
- その間は「操作入力を無効化された状態」として扱えるフラグを提供する
という挙動を、ノードにアタッチするだけで実現します。
プレイヤーキャラや敵キャラのノードに付けておけば、他のスクリプトから applyKnockback() を呼ぶだけで、汎用的なノックバック処理を再利用できます。
コンポーネントの設計方針
機能要件の整理
- 外部スクリプトから「ノックバックベクトル(方向と強さ)」を渡して呼び出す API を持つ。
- ノックバック中は「通常操作ができない状態」を表すフラグを持つ。
- ノックバック中は、受け取ったベクトルに応じてノードを一定時間移動させる。
- ノックバック時間終了後は自動的に通常状態(操作可能)に戻る。
- 外部の GameManager や InputManager などには一切依存しない。
- 物理挙動(RigidBody)を使わず、Transform の移動のみで完結する。
- インスペクタから「強さ」「減衰」「時間」などを調整できる。
外部依存をなくすためのアプローチ
- 「操作入力を無効にする」処理は、このコンポーネント自身では行わず、
isKnockbackActiveという読み取り専用のフラグを公開する。 - プレイヤー制御スクリプトなどは、このフラグを参照して「入力を無視するかどうか」を自前で判断する。
- 移動は
this.node.worldPositionを直接更新することで実現し、
物理コンポーネント(RigidBody)への依存を避ける。
インスペクタで設定可能なプロパティ設計
KnockbackReceiver が持つプロパティと役割は以下の通りです。
- knockbackDuration (number)
- ノックバックが持続する時間(秒)。
- 例: 0.2〜0.5 くらいが一般的な「軽いノックバック」。
- knockbackStrengthMultiplier (number)
- 外部から渡されるベクトルの強さに乗算する係数。
- ゲーム全体の感触をここで一括調整できる。
- 例: 1.0 でそのまま、2.0 で倍の距離を飛ぶ。
- useDecay (boolean)
- ノックバック速度を時間経過とともに減衰させるかどうか。
- ON の場合:最初だけ強く、徐々に減速する。
- OFF の場合:一定速度でノックバックし続ける。
- decayCurvePower (number)
- 減衰カーブの形状を決める指数。
- 1.0:線形に減衰。
- 2.0:最初強く、後半で一気に減衰。
- 0.5:最初ゆっくり、後半で急に弱くなる。
useDecay = trueのときのみ使用。
- allowNewKnockbackWhileActive (boolean)
- ノックバック中に新しいノックバックを上書きできるかどうか。
- ON:コンボ攻撃などで連続ヒットしたとき、最新のノックバックで上書き。
- OFF:一度吹き飛び始めたら、終了するまで新しいノックバックは無視。
- debugLog (boolean)
- ノックバック開始・終了時に
console.logを出すかどうか。 - 挙動確認用。完成後は OFF にしておくとよい。
- ノックバック開始・終了時に
外部から利用する公開メソッド
applyKnockback(direction: Vec3, strength: number): void- direction: 吹き飛ぶ方向ベクトル(任意の長さ)。内部で正規化される。
- strength: ノックバックの強さ(速度の基礎値)。
- 内部では
direction.normalize()してstrength * knockbackStrengthMultiplierを掛けた速度を設定する。
isKnockbackActive: boolean(getter)- 現在ノックバック中かどうかを返す読み取り専用プロパティ。
- プレイヤー制御スクリプトなどで
if (knockbackReceiver.isKnockbackActive) { 入力を無視 }のように使う。
TypeScriptコードの実装
import { _decorator, Component, Vec3, Node, math } from 'cc';
const { ccclass, property } = _decorator;
/**
* KnockbackReceiver
* 外部からノックバックベクトルを受け取り、
* 一定時間ノードを吹き飛ばしつつ「操作不能状態」を表現する汎用コンポーネント。
*
* 使用例:
* const kb = node.getComponent(KnockbackReceiver);
* kb?.applyKnockback(new Vec3(1, 0, 0), 5); // 右方向に強さ5でノックバック
*/
@ccclass('KnockbackReceiver')
export class KnockbackReceiver extends Component {
@property({
tooltip: 'ノックバックが持続する時間(秒)。\n例: 0.2 ~ 0.5 で軽いノックバック。'
})
public knockbackDuration: number = 0.3;
@property({
tooltip: '外部から渡される強さに乗算する係数。\n1.0でそのまま、2.0で倍の距離を飛びます。'
})
public knockbackStrengthMultiplier: number = 1.0;
@property({
tooltip: 'ノックバック速度を時間経過とともに減衰させるかどうか。'
})
public useDecay: boolean = true;
@property({
tooltip: '減衰カーブの指数。\n1.0: 線形、2.0: 最初強く後半で一気に減衰。',
visible: function (this: KnockbackReceiver) {
return this.useDecay;
}
})
public decayCurvePower: number = 1.5;
@property({
tooltip: 'ノックバック中に新しいノックバックを上書きできるかどうか。'
})
public allowNewKnockbackWhileActive: boolean = true;
@property({
tooltip: 'ノックバック開始・終了時にログを出力するかどうか。'
})
public debugLog: boolean = false;
// 内部状態
private _isKnockbackActive: boolean = false;
private _elapsedTime: number = 0;
private _currentVelocity: Vec3 = new Vec3();
private _startVelocity: Vec3 = new Vec3();
/**
* 現在ノックバック中かどうかを返す読み取り専用プロパティ。
* 他のスクリプトから「入力を無効にするかどうか」の判定に利用します。
*/
public get isKnockbackActive(): boolean {
return this._isKnockbackActive;
}
onLoad() {
// 特殊な必須コンポーネントはないため、ここでは防御的チェックのみ。
if (!this.node) {
console.error('[KnockbackReceiver] node が存在しません。');
}
}
start() {
// 初期化
this._resetKnockbackState();
}
update(deltaTime: number) {
if (!this._isKnockbackActive) {
return;
}
this._elapsedTime += deltaTime;
const t = math.clamp01(this._elapsedTime / Math.max(this.knockbackDuration, 0.0001));
// 減衰係数(0~1)
let factor = 1.0;
if (this.useDecay) {
// (1 - t)^p で減衰。tが1に近づくほど0に近づく。
factor = Math.pow(1.0 - t, Math.max(this.decayCurvePower, 0.01));
}
// 現在の速度ベクトル = 初速度 * 減衰係数
Vec3.multiplyScalar(this._currentVelocity, this._startVelocity, factor);
// 位置更新
const worldPos = this.node.worldPosition;
const deltaMove = new Vec3(
this._currentVelocity.x * deltaTime,
this._currentVelocity.y * deltaTime,
this._currentVelocity.z * deltaTime
);
Vec3.add(worldPos, worldPos, deltaMove);
this.node.worldPosition = worldPos;
// 時間が経過しきったらノックバック終了
if (this._elapsedTime >= this.knockbackDuration) {
this._endKnockback();
}
}
/**
* ノックバックを適用する。
* @param direction 吹き飛ぶ方向ベクトル(任意の長さ)。内部で正規化されます。
* @param strength ノックバックの強さ(速度の基礎値)。
*/
public applyKnockback(direction: Vec3, strength: number): void {
if (!direction) {
console.warn('[KnockbackReceiver] applyKnockback が無効な direction で呼ばれました。');
return;
}
if (strength
ライフサイクルメソッドの解説
- onLoad()
- 必須コンポーネントは特にないため、最低限の防御的チェックのみ。
- start()
_resetKnockbackState()を呼び出して内部状態を初期化。- シーン開始時にノックバック状態がクリアされていることを保証します。
- update(deltaTime)
_isKnockbackActiveがtrueのときのみ処理を行う。- 経過時間
_elapsedTimeを更新し、knockbackDurationから正規化したtを計算。 useDecayが有効なら、(1 - t) ^ decayCurvePowerで減衰係数を算出。- 初速度
_startVelocityに減衰係数を掛けて_currentVelocityを更新。 deltaTimeを掛けてフレームごとの移動量を算出し、node.worldPositionに加算。- 経過時間が
knockbackDurationを超えたら_endKnockback()を呼んで終了。
- applyKnockback(direction, strength)
- 外部からノックバックを発生させる唯一の公開メソッド。
- 無効な引数(
direction == nullやstrength <= 0)の場合は警告を出して無視。 - ノックバック中かつ
allowNewKnockbackWhileActive == falseの場合は新規ノックバックを無視。 - 方向ベクトルを正規化し、
strength * knockbackStrengthMultiplierを掛けて初速度を決定。 - 内部状態を更新し、
_isKnockbackActive = trueにすることでupdate()による移動が開始される。
使用手順と動作確認
1. スクリプトファイルを作成する
- エディタ上部メニューまたは Assets パネルで、任意のフォルダ(例:
assets/scripts)を選択します。 - そのフォルダ上で右クリック → Create → TypeScript を選択します。
- 新しく作成されたスクリプトファイルの名前を
KnockbackReceiver.tsに変更します。 - ダブルクリックしてエディタ(VS Code など)で開き、上記の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用ノードを作成する
ここでは、2Dゲームのプレイヤーキャラを想定して簡単なテストを行います。
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択して、テスト用のスプライトノードを作成します。
- 作成されたノードの名前を
Playerなど、分かりやすい名前に変更します。 - 必要であれば、Inspector の Sprite コンポーネントで任意の画像を設定して見た目を確認しやすくします。
3. KnockbackReceiver をアタッチする
- Hierarchy で先ほど作成した
Playerノードを選択します。 - Inspector の一番下にある Add Component ボタンをクリックします。
- Custom → KnockbackReceiver を選択してアタッチします。
4. インスペクタでプロパティを設定する
Player ノードにアタッチされた KnockbackReceiver コンポーネントを確認し、以下のように設定してみましょう(例):
- Knockback Duration:
0.3 - Knockback Strength Multiplier:
1.0 - Use Decay:
ON - Decay Curve Power:
1.5 - Allow New Knockback While Active:
ON - Debug Log:
ON(動作確認中のみ推奨)
5. ノックバックを呼び出す簡易テストスクリプト
実際にノックバックが動いているかを確認するために、同じノードに簡単なテストスクリプトを追加します。
- Assets パネルで右クリック → Create → TypeScript を選択し、ファイル名を
KnockbackTest.tsにします。 - 以下のコードを貼り付けて保存します。
import { _decorator, Component, Vec3, input, Input, KeyCode, EventKeyboard } from 'cc';
import { KnockbackReceiver } from './KnockbackReceiver';
const { ccclass } = _decorator;
@ccclass('KnockbackTest')
export class KnockbackTest extends Component {
private _knockbackReceiver: KnockbackReceiver | null = null;
onLoad() {
this._knockbackReceiver = this.getComponent(KnockbackReceiver);
if (!this._knockbackReceiver) {
console.error('[KnockbackTest] KnockbackReceiver が同じノードにアタッチされていません。');
}
// キーボード入力を監視
input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
}
onDestroy() {
input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
}
private _onKeyDown(event: EventKeyboard) {
if (!this._knockbackReceiver) {
return;
}
// スペースキーで右方向ノックバック
if (event.keyCode === KeyCode.SPACE) {
const dir = new Vec3(1, 0, 0); // 右方向
const strength = 5; // 基本強さ
this._knockbackReceiver.applyKnockback(dir, strength);
}
// Aキーで左方向ノックバック
if (event.keyCode === KeyCode.KEY_A) {
const dir = new Vec3(-1, 0, 0); // 左方向
const strength = 5;
this._knockbackReceiver.applyKnockback(dir, strength);
}
// Wキーで上方向ノックバック
if (event.keyCode === KeyCode.KEY_W) {
const dir = new Vec3(0, 1, 0); // 上方向
const strength = 5;
this._knockbackReceiver.applyKnockback(dir, strength);
}
}
}
このテストスクリプトは、
- スペースキー: 右方向にノックバック
- A キー: 左方向にノックバック
- W キー: 上方向にノックバック
という挙動を行います。
6. テストスクリプトをノードにアタッチする
- Hierarchy で
Playerノードを選択します。 - Inspector の Add Component → Custom → KnockbackTest を選択してアタッチします。
7. プレビューで動作確認
- エディタ右上の Preview ボタン(再生アイコン)をクリックして、ブラウザまたはシミュレータでゲームを実行します。
- ゲーム画面が表示されたら、スペースキー / A / W を押して、
Playerノードが指定方向に一瞬吹き飛ぶことを確認します。 Debug Logを ON にしていれば、コンソールに[KnockbackReceiver] ノックバック開始 ...[KnockbackReceiver] ノックバック終了
のログが表示されるはずです。
8. 実際のプレイヤー制御との連携例
実際のゲームでは、プレイヤー制御スクリプト側で isKnockbackActive を参照して入力を無効化します。例えば:
import { _decorator, Component, Vec3, input, Input, EventKeyboard, KeyCode } from 'cc';
import { KnockbackReceiver } from './KnockbackReceiver';
const { ccclass } = _decorator;
@ccclass('SimplePlayerController')
export class SimplePlayerController extends Component {
public moveSpeed: number = 3;
private _moveDir: Vec3 = new Vec3();
private _kb: KnockbackReceiver | null = null;
onLoad() {
this._kb = this.getComponent(KnockbackReceiver);
input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
input.on(Input.EventType.KEY_UP, this._onKeyUp, this);
}
onDestroy() {
input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
input.off(Input.EventType.KEY_UP, this._onKeyUp, this);
}
update(deltaTime: number) {
// ノックバック中はプレイヤー入力による移動を無効化
if (this._kb && this._kb.isKnockbackActive) {
return;
}
if (this._moveDir.lengthSqr() > 0) {
const pos = this.node.worldPosition;
const move = new Vec3(
this._moveDir.x * this.moveSpeed * deltaTime,
this._moveDir.y * this.moveSpeed * deltaTime,
0
);
Vec3.add(pos, pos, move);
this.node.worldPosition = pos;
}
}
private _onKeyDown(event: EventKeyboard) {
if (event.keyCode === KeyCode.KEY_D) this._moveDir.x = 1;
if (event.keyCode === KeyCode.KEY_A) this._moveDir.x = -1;
if (event.keyCode === KeyCode.KEY_W) this._moveDir.y = 1;
if (event.keyCode === KeyCode.KEY_S) this._moveDir.y = -1;
}
private _onKeyUp(event: EventKeyboard) {
if (event.keyCode === KeyCode.KEY_D && this._moveDir.x > 0) this._moveDir.x = 0;
if (event.keyCode === KeyCode.KEY_A && this._moveDir.x < 0) this._moveDir.x = 0;
if (event.keyCode === KeyCode.KEY_W && this._moveDir.y > 0) this._moveDir.y = 0;
if (event.keyCode === KeyCode.KEY_S && this._moveDir.y < 0) this._moveDir.y = 0;
}
}
このように、KnockbackReceiver は「ノックバック状態と移動のみ」を担当し、
「入力の無効化」は呼び出し元側でフラグを見て制御する、という役割分担にすることで、完全に独立した汎用コンポーネントとして運用できます。
まとめ
- KnockbackReceiver は、任意のノードにアタッチするだけで
- 外部からベクトルと強さを渡してノックバックさせる
- ノックバック中かどうかを簡単に判定できる
- ノックバックの時間・強さ・減衰の感触をインスペクタから調整できる
汎用コンポーネントです。
- GameManager や InputManager などには一切依存せず、このスクリプト単体で完結します。
- プレイヤー・敵・オブジェクトなど、どのノードにも使い回せるため、
「ノックバック処理を毎回書き直す」ことなく、ゲーム全体の開発効率を大きく向上させられます。 - 今後の応用としては、
- ノックバック中にアニメーションのステートを切り替える
- ノックバック方向に応じて向きを変える
- 3Dゲームで Z 軸方向のノックバックを利用する
といった拡張も、同じコンポーネントに少し手を加えるだけで実現できます。
まずはこの記事のコードをそのままプロジェクトに組み込み、
キー入力や攻撃ヒット時などから applyKnockback() を呼び出して、ノックバック演出の感触を調整してみてください。




