【Cocos Creator】アタッチするだけ!HurtboxComponent (被ダメージ判定)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【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 物理は Collider2DRigidBody2D を組み合わせて使います。
本コンポーネントでは、トリガー(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 を毎回リセットしたい場合に有効。
  • hitboxTag: string
    • 「敵 Hitbox 側のノードのタグ名」。
    • コライダーの tag ではなく、Node.nameNode.layer などと組み合わせる用途を想定。
    • 今回はシンプルに「Hitbox」という名前のノードと重なったらダメージ、などに使える。
    • 空文字の場合はタグによるフィルタを行わない。
  • hitboxGroup: number
    • 敵 Hitbox が属する物理グループ(PhysicsSystem2D.PhysicsGroup)。
    • 0 の場合はグループによるフィルタを行わない。
  • baseDamage: number
    • Hitbox と接触したときに最低限適用されるダメージ。
    • Hitbox 側にダメージ量の情報がなければ、この値のみで HP を減らす。
    • 例: 10
  • readDamageFromHitboxComponent: boolean
    • Hitbox 側のコンポーネントから damage プロパティを読み取るかどうか。
    • オンの場合、Hitbox にアタッチされた任意のコンポーネントに damage: number があれば、その値を優先して使用する。
    • 見つからなければ baseDamage を使う(防御的実装)。
  • invincibleDuration: number
    • ダメージを受けた後の無敵時間(秒)。
    • この時間内は追加ダメージを受け付けない。
    • 0 の場合は無敵時間なし。
  • destroyOnDeath: boolean
    • HP が 0 以下になったときにノードを 破棄するかどうか。
    • オフの場合は、代わりに node.active = false にする。
  • logDebug: boolean
    • ダメージ発生時や HP 変化時に console.log でログを出すかどうか。
    • デバッグ時のみオンにするのがおすすめ。

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
    • Collider2DRigidBody2DgetComponent で取得し、見つからなければエラーログを出します。
    • RigidBody2D の typeKinematic に設定し、センサー用のボディとして扱います。
    • 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 で非アクティブ化します。
  • applyDamage / heal
    • 外部からも明示的にダメージや回復を適用できるよう、パブリックメソッドを用意しています。
    • ただし、本コンポーネント単体で完結しているため、これらを呼び出す他スクリプトは必須ではありません。

使用手順と動作確認

1. スクリプトファイルの作成

  1. エディタ上部メニュー、または Assets パネルで作業用フォルダを選択します(例: assets/scripts)。
  2. Assets パネルで右クリック → Create → TypeScript を選択します。
  3. ファイル名を HurtboxComponent.ts に変更します。
  4. 作成した HurtboxComponent.ts をダブルクリックして開き、先ほどの TypeScript コード全文を貼り付けて保存します。

2. テスト用ノード(Hurtbox側)の作成

ここでは、プレイヤーの被ダメージ判定として使う例を想定します。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite などを選択し、テスト用ノードを作成します。
    ノード名は例として Player とします。
  2. Player ノードを選択し、Inspector を確認します。
  3. Add Component → Physics 2D → Collider から、任意の 2D コライダー(例: BoxCollider2D)を追加します。
  4. 同じく Add Component → Physics 2D → RigidBody2D を追加します。
  5. 追加した RigidBody2D コンポーネントの設定:
    • Type: Kinematic(記事のコードで自動的に設定されますが、念のため確認)
    • 重力などはデフォルトのままで問題ありません。
  6. 追加した BoxCollider2D(または他の Collider2D)の設定:
    • Sensor: チェックを入れて true にします。
      (コード側でも sensor = true を設定しますが、エディタ上でも確認しておくと安心です)
    • 必要に応じてサイズやオフセットを調整します。
  7. Add Component → Custom → HurtboxComponent を選択し、Player ノードにアタッチします。
  8. 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)を作成します。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite などを選択し、ノードを作成します。
    ノード名を EnemyHitbox とします(Hitbox Tag と一致させる)。
  2. EnemyHitbox ノードを選択し、Inspector で:
    • Add Component → Physics 2D → Collider(例: BoxCollider2D)を追加。
    • Add Component → Physics 2D → RigidBody2D を追加。
    • RigidBody2D の TypeDynamic でも Kinematic でも構いません(動かしたいなら Dynamic)。
    • Collider2D の Sensor をオン(true)にしておきます。
  3. 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 で動作します。

4. 物理システムの有効化確認

  1. メインメニューから Project → Project Settings を開きます。
  2. 左側のリストから Physics 2D を選択します。
  3. Enable Physics(または同等の 2D 物理有効化設定)がオンになっていることを確認します。
  4. 必要に応じて Debug Draw をオンにすると、コライダーの形状が画面上に表示され、当たり判定の確認がしやすくなります。

5. シーン上での配置とテスト

  1. Scene ビューで PlayerEnemyHitbox の位置を調整し、再生時に重なりあうように配置します。
  2. 再生ボタン(▶)を押してゲームをプレビューします。
  3. ゲームビューで、EnemyHitbox をドラッグして Player に重ねるか、もしくはスクリプトやアニメーションでぶつかるようにします。
  4. Console パネルを確認し、次のようなログが出力されていれば成功です:
    • [HurtboxComponent] 受けるダメージ量: 25 from Hitbox Node: EnemyHitbox
    • [HurtboxComponent] ダメージ:25 HP:100 -> 75 ノード名:Player
    • HP が 0 になるまで繰り返し当てると、最後に:
      • [HurtboxComponent] HPが0になりました。死亡処理を実行します。ノード名: Player
  5. Destroy On Death が false の場合、Player ノードは 非アクティブ化され、シーン上から消えたように見えます。
  6. Destroy On Death を true にして再テストすると、今度はノード自体が 破棄されます。

6. 無敵時間と連続ヒットの確認

  1. Invincible Duration を 0.5 など大きめの値に設定します。
  2. ゲームを再生し、EnemyHitboxPlayer に重ねたままにします。
  3. Console ログを見ると、一定間隔でしかダメージログが出ないことが確認できます(0.5秒ごと)。
  4. Invincible Duration を 0 に戻すと、連続でダメージが発生するようになります。

まとめ

今回実装した HurtboxComponent は、

  • HP 管理(最大 HP / 現在 HP)
  • 2D 物理による被ダメージ判定(Collider2D + RigidBody2D)
  • Hitbox 側のダメージ情報の自動取得(任意)
  • 無敵時間(iFrame)の管理
  • 死亡時の破棄 or 非アクティブ化

といった要素を 1 つのコンポーネントに完結させた汎用スクリプトです。
外部の GameManager や HealthManager に依存しないため、どのノードにもそのままアタッチして使い回せるのが最大の利点です。

応用例としては:

  • プレイヤー、敵、ギミック(トゲ床など)にそれぞれ Hurtbox を付けて、共通の被ダメージロジックを使い回す。
  • Hitbox 側にさまざまな攻撃スクリプトを用意し、damage プロパティだけ共通化しておけば、攻撃パターンが増えても Hurtbox はそのまま使える。
  • UI 側で HP バーを表示したい場合は、currentHealthmaxHealth を読むだけで済む。

このように、「アタッチするだけで被ダメージ判定と HP 管理が完結するコンポーネント」を用意しておくと、キャラクターや敵の追加・調整が非常に楽になります。
プロジェクトの共通基盤として組み込んでおくと、ゲーム開発のスピードと保守性が大きく向上します。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!