【Cocos Creator 3.8】HurtboxComponent の実装:アタッチするだけで「被ダメージ判定とHP管理」を実現する汎用スクリプト
本記事では、ノードにアタッチするだけで「敵の Hitbox と重なったときに HP を減らす」処理が完結する、HurtboxComponent を実装します。
Cocos Creator 3.8.7 / TypeScript を前提に、外部の GameManager や HealthManager に一切依存しない、単体完結型のコンポーネントとして設計します。
このコンポーネントを使えば、プレイヤーや敵キャラクターなどに「被ダメージ判定エリア」と「HP管理」をまとめて持たせることができ、ノードごとに HP や当たり判定の設定を変えるだけで、汎用的なダメージ処理を構築できます。
コンポーネントの設計方針
1. 機能要件の整理
- このコンポーネントがアタッチされたノードは「Hurtbox(被ダメージ判定エリア)」として機能する。
- Hurtbox は Area2D(Cocos Creator では
Collider2D+RigidBody2D) を使って、敵の Hitbox と重なったことを検知する。 - Hitbox 側には Collider2D があり、特定のタグ名かグループで区別されているとみなす。
- 重なりを検知したら、このコンポーネント自身が持つ HP を減少させる(外部の HealthManager に依存しない)。
- HP が 0 以下になったら、ノードを非アクティブ化する、または破棄するなどの挙動を選択できるようにする。
- 同じ Hitbox によるダメージを 連続して食らいすぎないよう、無敵時間(iFrame) を設定可能にする。
Cocos Creator 3.8.7 では、2D 物理は Collider2D と RigidBody2D を組み合わせて使います。
本コンポーネントでは、トリガー(isTrigger = true)な Collider2D を前提とし、onBeginContact などのコールバックでダメージ判定を行います。
2. 外部依存をなくすためのアプローチ
- HP 管理は全て
HurtboxComponent内に実装し、HealthManager など別スクリプトに依存しない。 - Hitbox 側の情報も、タグ名(string) か 物理グループ(number) で判定し、他のスクリプトに問い合わせない。
- ダメージ量は、
- 基本ダメージ(固定値)
- 相手 Hitbox 側の
damageなどのプロパティをgetComponentで読む(あれば)
の 2 段階で設計するが、後者は存在しなくても動くようにする(防御的実装)。
- 必要な標準コンポーネント(
Collider2D,RigidBody2D)はgetComponentで取得し、存在しない場合はエラーログを出して何もしない。
3. インスペクタで設定可能なプロパティ設計
インスペクタで調整できるプロパティは次の通りです。
- maxHealth: number
- 最大 HP。
- 初期 HP はこの値で初期化される。
- 例: 100
- autoResetHealthOnEnable: boolean
- ノードが有効化されたときに HP を
maxHealthにリセットするかどうか。 - 敵の再出現などで HP を毎回リセットしたい場合に有効。
- ノードが有効化されたときに HP を
- hitboxTag: string
- 「敵 Hitbox 側のノードのタグ名」。
- コライダーの
tagではなく、Node.name や Node.layer などと組み合わせる用途を想定。 - 今回はシンプルに「Hitbox」という名前のノードと重なったらダメージ、などに使える。
- 空文字の場合はタグによるフィルタを行わない。
- hitboxGroup: number
- 敵 Hitbox が属する物理グループ(
PhysicsSystem2D.PhysicsGroup)。 - 0 の場合はグループによるフィルタを行わない。
- 敵 Hitbox が属する物理グループ(
- baseDamage: number
- Hitbox と接触したときに最低限適用されるダメージ。
- Hitbox 側にダメージ量の情報がなければ、この値のみで HP を減らす。
- 例: 10
- readDamageFromHitboxComponent: boolean
- Hitbox 側のコンポーネントから
damageプロパティを読み取るかどうか。 - オンの場合、Hitbox にアタッチされた任意のコンポーネントに
damage: numberがあれば、その値を優先して使用する。 - 見つからなければ
baseDamageを使う(防御的実装)。
- Hitbox 側のコンポーネントから
- invincibleDuration: number
- ダメージを受けた後の無敵時間(秒)。
- この時間内は追加ダメージを受け付けない。
- 0 の場合は無敵時間なし。
- destroyOnDeath: boolean
- HP が 0 以下になったときにノードを 破棄するかどうか。
- オフの場合は、代わりに
node.active = falseにする。
- logDebug: boolean
- ダメージ発生時や HP 変化時に
console.logでログを出すかどうか。 - デバッグ時のみオンにするのがおすすめ。
- ダメージ発生時や HP 変化時に
TypeScriptコードの実装
以下が、HurtboxComponent.ts の完全な実装例です。
import { _decorator, Component, Node, Collider2D, IPhysics2DContact, RigidBody2D, PhysicsSystem2D, ERigidBody2DType, sys } from 'cc';
const { ccclass, property } = _decorator;
/**
* HurtboxComponent
*
* - このコンポーネントを持つノードは「被ダメージ判定エリア」として機能します。
* - Collider2D (isTrigger = true) と RigidBody2D を必要とします。
* - 敵 Hitbox と重なったときに HP を減らし、0 以下で死亡処理を行います。
*/
@ccclass('HurtboxComponent')
export class HurtboxComponent extends Component {
@property({
tooltip: '最大HP。この値で初期HPが設定されます。'
})
public maxHealth: number = 100;
@property({
tooltip: 'ノードが有効化されたときにHPをmaxHealthにリセットするかどうか。'
})
public autoResetHealthOnEnable: boolean = true;
@property({
tooltip: '敵Hitboxノードの名前(完全一致)。空文字の場合、この条件は無視されます。例: "EnemyHitbox"'
})
public hitboxTag: string = '';
@property({
tooltip: '敵Hitboxの物理グループ。0の場合、この条件は無視されます。'
})
public hitboxGroup: number = 0;
@property({
tooltip: '最低限与えられるダメージ量。Hitbox側にdamage情報が無い場合、この値のみでHPを減らします。'
})
public baseDamage: number = 10;
@property({
tooltip: 'Hitbox側のコンポーネントからdamageプロパティを読み取るかどうか。'
})
public readDamageFromHitboxComponent: boolean = true;
@property({
tooltip: 'ダメージを受けた後の無敵時間(秒)。0の場合は無敵時間なし。'
})
public invincibleDuration: number = 0.2;
@property({
tooltip: 'HPが0以下になったときにノードを破棄するかどうか。オフの場合は非アクティブ化します。'
})
public destroyOnDeath: boolean = false;
@property({
tooltip: 'ダメージ発生やHP変化時にログを出力するかどうか。'
})
public logDebug: boolean = false;
// 現在のHP(インスペクタからは読み取り専用にしたいので@propertyは付けない)
private _currentHealth: number = 0;
// 無敵時間管理用
private _lastDamageTime: number = -Infinity;
// 物理コンポーネント
private _collider: Collider2D | null = null;
private _rigidBody: RigidBody2D | null = null;
public get currentHealth(): number {
return this._currentHealth;
}
onLoad() {
// 必要なコンポーネントを取得
this._collider = this.getComponent(Collider2D);
this._rigidBody = this.getComponent(RigidBody2D);
if (!this._collider) {
console.error('[HurtboxComponent] Collider2D が見つかりません。このノードに Collider2D を追加してください。ノード名:', this.node.name);
}
if (!this._rigidBody) {
console.error('[HurtboxComponent] RigidBody2D が見つかりません。このノードに RigidBody2D を追加してください。ノード名:', this.node.name);
} else {
// Hurtboxは通常、動かないセンサーとして扱うのでKinematicにしておく(必要に応じて変更可)
if (this._rigidBody.type !== ERigidBody2DType.Kinematic) {
this._rigidBody.type = ERigidBody2DType.Kinematic;
}
}
if (this._collider) {
// Triggerとして扱う
this._collider.sensor = true;
}
// 初期HP設定
this._currentHealth = this.maxHealth;
// コールバック登録
if (this._collider) {
this._collider.on('onBeginContact', this._onBeginContact, this);
}
}
onEnable() {
// ノードが再度有効化されたときにHPをリセットするかどうか
if (this.autoResetHealthOnEnable) {
this._currentHealth = this.maxHealth;
if (this.logDebug) {
console.log('[HurtboxComponent] HPリセット:', this._currentHealth, '/', this.maxHealth, 'ノード名:', this.node.name);
}
}
}
onDestroy() {
// コールバック解除
if (this._collider) {
this._collider.off('onBeginContact', this._onBeginContact, this);
}
}
/**
* 物理接触開始時のコールバック
*/
private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
// 無敵時間チェック
const now = sys.now() / 1000; // ms -> s
if (this.invincibleDuration > 0 && now - this._lastDamageTime ${this._currentHealth} ノード名:${this.node.name}`);
}
if (this._currentHealth 0 && now - this._lastDamageTime this.maxHealth) {
this._currentHealth = this.maxHealth;
}
if (this.logDebug) {
console.log(`[HurtboxComponent] 回復:${amount} HP:${prevHealth} -> ${this._currentHealth} ノード名:${this.node.name}`);
}
}
}
コードのポイント解説
- onLoad
Collider2DとRigidBody2DをgetComponentで取得し、見つからなければエラーログを出します。- RigidBody2D の
typeをKinematicに設定し、センサー用のボディとして扱います。 - Collider2D の
sensor = trueにして、当たりは判定するが物理的な押し返しはしないようにします。 - 初期 HP を
maxHealthで初期化し、onBeginContactコールバックを登録します。
- onEnable
autoResetHealthOnEnableが true の場合、ノードが再度有効になったタイミングで HP を全回復します。- 再利用される敵キャラなどに便利です。
- _onBeginContact
- 無敵時間をチェックし、
invincibleDuration内であればダメージを無視します。 _isValidHitboxで、Hitbox ノードがhitboxTag(名前)やhitboxGroup(物理グループ)条件を満たすかを確認します。_getDamageFromHitboxでダメージ量を決定し、_applyDamageで HP を減らします。
- 無敵時間をチェックし、
- _getDamageFromHitbox
readDamageFromHitboxComponentが true のとき、Hitbox ノードにアタッチされたコンポーネントを全て走査し、damage: numberプロパティを持つものを探します。- 見つかればその値をダメージとして使用し、見つからなければ
baseDamageを使います。 - ここでは具体的な Hitbox スクリプト名に依存せず、任意のコンポーネントから柔軟にダメージを拾えるようにしています。
- _applyDamage / _onDeath
- HP を減らし、0 以下なら
_onDeathを呼びます。 destroyOnDeathが true の場合はnode.destroy()、false の場合はnode.active = falseで非アクティブ化します。
- HP を減らし、0 以下なら
- applyDamage / heal
- 外部からも明示的にダメージや回復を適用できるよう、パブリックメソッドを用意しています。
- ただし、本コンポーネント単体で完結しているため、これらを呼び出す他スクリプトは必須ではありません。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ上部メニュー、または Assets パネルで作業用フォルダを選択します(例:
assets/scripts)。 - Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
HurtboxComponent.tsに変更します。 - 作成した
HurtboxComponent.tsをダブルクリックして開き、先ほどの TypeScript コード全文を貼り付けて保存します。
2. テスト用ノード(Hurtbox側)の作成
ここでは、プレイヤーの被ダメージ判定として使う例を想定します。
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite などを選択し、テスト用ノードを作成します。
ノード名は例としてPlayerとします。 Playerノードを選択し、Inspector を確認します。- Add Component → Physics 2D → Collider から、任意の 2D コライダー(例:
BoxCollider2D)を追加します。 - 同じく Add Component → Physics 2D → RigidBody2D を追加します。
- 追加した
RigidBody2Dコンポーネントの設定:- Type:
Kinematic(記事のコードで自動的に設定されますが、念のため確認) - 重力などはデフォルトのままで問題ありません。
- Type:
- 追加した
BoxCollider2D(または他の Collider2D)の設定:- Sensor: チェックを入れて true にします。
(コード側でもsensor = trueを設定しますが、エディタ上でも確認しておくと安心です) - 必要に応じてサイズやオフセットを調整します。
- Sensor: チェックを入れて true にします。
- Add Component → Custom → HurtboxComponent を選択し、
Playerノードにアタッチします。 - HurtboxComponent のプロパティを設定します:
- Max Health: 100
- Auto Reset Health On Enable: チェック(true)
- Hitbox Tag:
EnemyHitbox(後で作る Hitbox ノード名と合わせます) - Hitbox Group: 0(まずは未使用)
- Base Damage: 10
- Read Damage From Hitbox Component: チェック(true)
- Invincible Duration: 0.2
- Destroy On Death: オフ(false)
- Log Debug: チェック(true)(動作確認中はログを見たいのでオン)
3. テスト用 Hitbox ノードの作成
次に、Player の Hurtbox にダメージを与える側(Hitbox)を作成します。
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite などを選択し、ノードを作成します。
ノード名をEnemyHitboxとします(Hitbox Tagと一致させる)。 EnemyHitboxノードを選択し、Inspector で:- Add Component → Physics 2D → Collider(例:
BoxCollider2D)を追加。 - Add Component → Physics 2D → RigidBody2D を追加。
- RigidBody2D の Type は
DynamicでもKinematicでも構いません(動かしたいなら Dynamic)。 - Collider2D の Sensor をオン(true)にしておきます。
- Add Component → Physics 2D → Collider(例:
- Hitbox 側にダメージ量を持たせたい場合:
- 簡易的には、空の TypeScript コンポーネントを作成し、
damage: numberプロパティを持たせます。 - 例:
EnemyHitboxDamage.tsを作り、以下のように実装(任意。HurtboxComponent はこれに依存しません)。
import { _decorator, Component } from 'cc'; const { ccclass, property } = _decorator; @ccclass('EnemyHitboxDamage') export class EnemyHitboxDamage extends Component { @property public damage: number = 25; }- このコンポーネントを
EnemyHitboxにアタッチすると、HurtboxComponent は damage=25 を自動的に読み取って使用します。 - この追加コンポーネントは完全に任意であり、HurtboxComponent 自体は存在しなくても baseDamage で動作します。
- 簡易的には、空の TypeScript コンポーネントを作成し、
4. 物理システムの有効化確認
- メインメニューから Project → Project Settings を開きます。
- 左側のリストから Physics 2D を選択します。
- Enable Physics(または同等の 2D 物理有効化設定)がオンになっていることを確認します。
- 必要に応じて Debug Draw をオンにすると、コライダーの形状が画面上に表示され、当たり判定の確認がしやすくなります。
5. シーン上での配置とテスト
- Scene ビューで
PlayerとEnemyHitboxの位置を調整し、再生時に重なりあうように配置します。 - 再生ボタン(▶)を押してゲームをプレビューします。
- ゲームビューで、
EnemyHitboxをドラッグしてPlayerに重ねるか、もしくはスクリプトやアニメーションでぶつかるようにします。 - Console パネルを確認し、次のようなログが出力されていれば成功です:
[HurtboxComponent] 受けるダメージ量: 25 from Hitbox Node: EnemyHitbox[HurtboxComponent] ダメージ:25 HP:100 -> 75 ノード名:Player- HP が 0 になるまで繰り返し当てると、最後に:
[HurtboxComponent] HPが0になりました。死亡処理を実行します。ノード名: Player
Destroy On Deathが false の場合、Playerノードは 非アクティブ化され、シーン上から消えたように見えます。Destroy On Deathを true にして再テストすると、今度はノード自体が 破棄されます。
6. 無敵時間と連続ヒットの確認
Invincible Durationを 0.5 など大きめの値に設定します。- ゲームを再生し、
EnemyHitboxをPlayerに重ねたままにします。 - Console ログを見ると、一定間隔でしかダメージログが出ないことが確認できます(0.5秒ごと)。
Invincible Durationを 0 に戻すと、連続でダメージが発生するようになります。
まとめ
今回実装した HurtboxComponent は、
- HP 管理(最大 HP / 現在 HP)
- 2D 物理による被ダメージ判定(Collider2D + RigidBody2D)
- Hitbox 側のダメージ情報の自動取得(任意)
- 無敵時間(iFrame)の管理
- 死亡時の破棄 or 非アクティブ化
といった要素を 1 つのコンポーネントに完結させた汎用スクリプトです。
外部の GameManager や HealthManager に依存しないため、どのノードにもそのままアタッチして使い回せるのが最大の利点です。
応用例としては:
- プレイヤー、敵、ギミック(トゲ床など)にそれぞれ Hurtbox を付けて、共通の被ダメージロジックを使い回す。
- Hitbox 側にさまざまな攻撃スクリプトを用意し、
damageプロパティだけ共通化しておけば、攻撃パターンが増えても Hurtbox はそのまま使える。 - UI 側で HP バーを表示したい場合は、
currentHealthとmaxHealthを読むだけで済む。
このように、「アタッチするだけで被ダメージ判定と HP 管理が完結するコンポーネント」を用意しておくと、キャラクターや敵の追加・調整が非常に楽になります。
プロジェクトの共通基盤として組み込んでおくと、ゲーム開発のスピードと保守性が大きく向上します。




