【Cocos Creator】アタッチするだけ!AreaTrigger (イベント起動)の実装方法【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】AreaTriggerの実装:アタッチするだけで「プレイヤーが入った瞬間にイベントを発火して自動消滅」する汎用スクリプト

この記事では、2Dゲームでよく使う「エリアにプレイヤーが入ったらイベントを起動して、そのエリアは使い捨てで消える」という仕組みを、1つのコンポーネントだけで実現します。
Cocos Creator 3.8.7 + TypeScript 環境で、ノードにアタッチするだけで使えるように設計し、外部のGameManagerやシングルトンに一切依存しない汎用トリガーコンポーネントとして実装します。


コンポーネントの設計方針

想定する利用シーン

  • ステージの特定エリアに入ったらイベント開始(カットシーン、会話、敵出現など)
  • 一度だけ有効なチェックポイント・チュートリアルポップアップの起動
  • 隠しエリアに入ったらアイテム出現・実績解除など

これらを、「エリア用ノードに AreaTrigger をアタッチするだけ」で実現できるようにします。

要件整理

  1. このコンポーネントがアタッチされたノード(親:Area2D)が「当たり判定エリア」となる。
  2. プレイヤーがこのエリアに入った瞬間に「イベントが起動した」ことを通知する。
  3. イベント起動後、このエリアノードは自動的に消滅する(再利用されない一度きりのトリガー)。
  4. 外部スクリプトへの依存は禁止。
    → 通知は 以下の2つの方法で行う:
    • 任意のノードにアタッチされたコンポーネントのメソッドを呼び出す(インスペクタで指定)
    • コンソールログやエディタ上のテストに使える簡易ログ(最低限のフィードバック)
  5. 「プレイヤーかどうか」の判定は、対象ノード名またはグループ名で行う(インスペクタで指定)。
  6. 2D物理を利用し、Collider2D の onBeginContact / onTriggerEnter 系イベントで検知する。

必要となる標準コンポーネント

AreaTrigger を使うノード(エリアノード)には、最低限以下のコンポーネントが必要です:

  • Collider2D(例:BoxCollider2D, CircleCollider2D など)
    • 「トリガー」として使うので、isTrigger = true 推奨
  • Rigidbody2D(静的でも可)
    • 物理イベントを安定して受け取るために推奨(Static / Kinematic など)

コンポーネント内では getComponent でこれらの存在を確認し、存在しない場合はエラーログを出力して、エディタでの設定ミスに気付きやすくします。

インスペクタで設定可能なプロパティ設計

AreaTrigger コンポーネントのプロパティは次のように設計します。

  • playerNodeName : string
    • プレイヤーノードの 名前 を指定する。
    • 空文字の場合は、名前では判定しない。
    • 例: "Player"
  • playerGroupName : string
    • プレイヤーが属する グループ名 を指定する。
    • 空文字の場合は、グループでは判定しない。
    • 例: "player"
  • useTriggerEvent : boolean
    • true の場合:Collider2D の onTriggerEnter 系イベントを使う。
    • false の場合:onBeginContact(物理接触)を使う。
    • 2D物理設定や他のコライダー設定に応じて切り替え可能。
  • autoDestroyDelay : number
    • イベント発火後、何秒後にこのノードを破棄するか
    • 0 以下の場合は、即座に破棄する。
    • 例: 0.2 秒など、わずかに遅らせて安全に処理させることも可能。
  • targetNode : Node | null
    • イベント通知先のノード。
    • ここにアタッチされたコンポーネントのメソッドを呼び出す。
    • 未設定(null)の場合は、ログ出力のみを行う。
  • targetComponentName : string
    • 通知先コンポーネントのクラス名(@ccclass で指定した名前)を文字列で指定。
    • 例: "GameEventReceiver"
  • targetMethodName : string
    • 呼び出したいメソッド名。
    • 例: "onAreaTriggered"
    • シグネチャは (areaNode: Node) => void を推奨(引数はこの AreaTrigger が付いているノード)。
  • logOnTrigger : boolean
    • イベント発火時に console.log を出すかどうか。
    • デバッグ時は true、本番でうるさい場合は false に。

これらを使って、「何をプレイヤーとみなすか」「どこに通知するか」「どう破棄するか」をすべてインスペクタから調整できるようにします。


TypeScriptコードの実装

以下が完成した AreaTrigger.ts の全コードです。


import { _decorator, Component, Node, Collider2D, IPhysics2DContact, Contact2DType, RigidBody2D, director, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

/**
 * AreaTrigger
 * 
 * このコンポーネントを 2D コライダーノードにアタッチすると、
 * プレイヤーがエリアに侵入した瞬間に任意のメソッドを呼び出し、
 * その後自動的に自分自身のノードを破棄します。
 * 
 * 外部シングルトンや GameManager に依存せず、
 * すべての設定はインスペクタから行う設計になっています。
 */
@ccclass('AreaTrigger')
export class AreaTrigger extends Component {

    @property({
        tooltip: 'プレイヤーノードの名前。\n空の場合、名前による判定は行いません。\n例: "Player"'
    })
    public playerNodeName: string = 'Player';

    @property({
        tooltip: 'プレイヤーが属するグループ名。\n空の場合、グループによる判定は行いません。\n例: "player"'
    })
    public playerGroupName: string = '';

    @property({
        tooltip: 'true: トリガーイベント (onTriggerEnter) を使用。\nfalse: 物理接触イベント (onBeginContact) を使用。'
    })
    public useTriggerEvent: boolean = true;

    @property({
        tooltip: 'イベント発火後、何秒後にこのノードを破棄するか。\n0 以下の場合は即座に破棄します。'
    })
    public autoDestroyDelay: number = 0;

    @property({
        tooltip: 'イベント通知先のノード。\nここにアタッチされたコンポーネントのメソッドを呼び出します。\n未設定の場合はログ出力のみ行います。'
    })
    public targetNode: Node | null = null;

    @property({
        tooltip: '通知先コンポーネントのクラス名 (@ccclass で指定した名前)。\n例: "GameEventReceiver"'
    })
    public targetComponentName: string = '';

    @property({
        tooltip: '通知先メソッド名。\n例: "onAreaTriggered"\nシグネチャは (areaNode: Node) => void を推奨。'
    })
    public targetMethodName: string = '';

    @property({
        tooltip: 'true の場合、トリガー発火時に console.log で情報を出力します。'
    })
    public logOnTrigger: boolean = true;

    /** 既にトリガーが発火したかどうか(多重発火防止) */
    private _triggered: boolean = false;

    /** このノードに付いている Collider2D への参照 */
    private _collider: Collider2D | null = null;

    onLoad() {
        // Collider2D の取得と存在チェック
        this._collider = this.getComponent(Collider2D);
        if (!this._collider) {
            console.error('[AreaTrigger] Collider2D がこのノードにアタッチされていません。トリガーは動作しません。ノード名:', this.node.name);
        } else {
            // イベントの登録
            if (this.useTriggerEvent) {
                // isTrigger = true の場合のイベント
                this._collider.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
            } else {
                // 通常の物理接触イベント
                this._collider.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
            }
        }

        // Rigidbody2D が無い場合は警告(必須ではないが推奨)
        const rb = this.getComponent(RigidBody2D);
        if (!rb) {
            console.warn('[AreaTrigger] Rigidbody2D がこのノードに存在しません。物理イベントが発生しない場合は、Static などで追加してください。ノード名:', this.node.name);
        }
    }

    onDestroy() {
        // イベントの解除(防御的に解除)
        if (this._collider) {
            this._collider.off(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
        }
    }

    /**
     * 物理接触・トリガー侵入時に呼ばれるコールバック。
     */
    private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        if (this._triggered) {
            return;
        }

        const otherNode = otherCollider.node;

        // プレイヤー判定
        if (!this._isPlayer(otherNode)) {
            return;
        }

        this._triggered = true;

        if (this.logOnTrigger) {
            console.log('[AreaTrigger] プレイヤーがエリアに侵入しました。エリアノード:', this.node.name, 'プレイヤーノード:', otherNode.name);
        }

        // 任意ターゲットへの通知
        this._notifyTarget();

        // 自動破棄
        this._destroySelf();
    }

    /**
     * 引数のノードが「プレイヤー」とみなせるかどうかを判定します。
     * playerNodeName / playerGroupName に基づいてチェックします。
     */
    private _isPlayer(node: Node): boolean {
        // 名前による判定
        if (this.playerNodeName && node.name !== this.playerNodeName) {
            // 名前が指定されていて、かつ一致しないならプレイヤーではない
            return false;
        }

        // グループ名による判定(Cocos Creator 3.x では node.layer を使うため、
        // グループ名判定はプロジェクト側の実装に依存します。
        // ここでは簡易的に、node.name にグループ名が含まれている場合も許可する例を示します。
        if (this.playerGroupName) {
            const lowerName = node.name.toLowerCase();
            const lowerGroup = this.playerGroupName.toLowerCase();
            if (!lowerName.includes(lowerGroup)) {
                // 厳密なグループレイヤー判定を行いたい場合は、
                // プロジェクト固有の layer 設計に合わせてここを書き換えてください。
                return false;
            }
        }

        return true;
    }

    /**
     * targetNode / targetComponentName / targetMethodName に基づき、
     * 任意のメソッドを呼び出します。
     */
    private _notifyTarget() {
        if (!this.targetNode) {
            if (this.logOnTrigger) {
                console.warn('[AreaTrigger] targetNode が設定されていないため、メソッド呼び出しは行われません。');
            }
            return;
        }

        if (!this.targetComponentName || !this.targetMethodName) {
            if (this.logOnTrigger) {
                console.warn('[AreaTrigger] targetComponentName または targetMethodName が設定されていないため、メソッド呼び出しは行われません。');
            }
            return;
        }

        // 任意コンポーネントの取得(クラス名文字列から)
        const comp = this.targetNode.getComponent(this.targetComponentName as any);
        if (!comp) {
            console.error('[AreaTrigger] 指定されたコンポーネントが targetNode に見つかりません。componentName:', this.targetComponentName, 'targetNode:', this.targetNode.name);
            return;
        }

        const method: unknown = (comp as any)[this.targetMethodName];
        if (typeof method !== 'function') {
            console.error('[AreaTrigger] 指定されたメソッドがコンポーネントに存在しません。methodName:', this.targetMethodName, 'componentName:', this.targetComponentName);
            return;
        }

        try {
            // メソッドに this.node(エリアノード)を渡して呼び出す
            (method as (areaNode: Node) => void).call(comp, this.node);
        } catch (e) {
            console.error('[AreaTrigger] メソッド呼び出し中にエラーが発生しました。', e);
        }
    }

    /**
     * 自身のノードを破棄します。
     * autoDestroyDelay が 0 以下なら即時、それ以外は指定秒数後に破棄。
     */
    private _destroySelf() {
        if (!this.node || !this.node.isValid) {
            return;
        }

        if (this.autoDestroyDelay <= 0) {
            this.node.destroy();
        } else {
            // director.getScheduler() を使わず、scheduleOnce を利用
            this.scheduleOnce(() => {
                if (this.node && this.node.isValid) {
                    this.node.destroy();
                }
            }, this.autoDestroyDelay);
        }
    }
}

コードのポイント解説

  • onLoad()
    • このノードに Collider2D が付いているか確認し、存在しない場合は console.error で通知。
    • Contact2DType.BEGIN_CONTACT_onBeginContact を登録。
    • Rigidbody2D が無い場合は console.warn で注意喚起(推奨だが必須ではない)。
  • _onBeginContact()
    • 多重発火防止のため、_triggered フラグでガード。
    • 接触した相手のノードを _isPlayer() で判定し、プレイヤーでなければ無視。
    • プレイヤーなら:
      1. ログ出力(任意)
      2. _notifyTarget() で任意メソッド呼び出し
      3. _destroySelf() で自動破棄
  • _isPlayer()
    • playerNodeName が設定されていれば、node.name が完全一致するかを確認。
    • playerGroupName が設定されていれば、簡易的に node.name にグループ名が含まれているかを確認(プロジェクトの layer 設計に合わせて改造可能)。
  • _notifyTarget()
    • targetNode, targetComponentName, targetMethodName がすべて設定されている場合のみ動作。
    • targetNode.getComponent(this.targetComponentName as any) でコンポーネント取得。
    • 指定メソッドが存在し、関数であることをチェックしてから call で実行。
    • 引数には この AreaTrigger が付いているノード(エリアノード)を渡す。
  • _destroySelf()
    • autoDestroyDelay <= 0 なら this.node.destroy() で即破棄。
    • それ以外は scheduleOnce で指定秒数後に破棄。

使用手順と動作確認

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

  1. Assets パネルで右クリック → Create → TypeScript を選択。
  2. ファイル名を AreaTrigger.ts にします。
  3. 自動生成されたコードをすべて削除し、本記事の 「TypeScriptコードの実装」 セクションのコードを貼り付け、保存します。

2. テスト用シーンの準備(エリアノードの作成)

  1. Hierarchy パネルで右クリック → Create → 2D Object → Node などで、空のノードを作成。
    • ノード名を Area2D_Test などに変更します。
  2. このノードをエリアとして見えるようにしたい場合は、Sprite などを追加しても構いません(任意)。
  3. Inspector で Add Component → Physics 2D → BoxCollider2D を追加。
    • 「Is Trigger」チェックボックスを ON にしておくと、トリガーとして扱いやすくなります。
    • Size を調整して、プレイヤーが通過する範囲をカバーするようにします。
  4. Inspector で Add Component → Physics 2D → RigidBody2D を追加。
    • Type を Static に設定しておくと、動かないエリアとして扱いやすいです。
  5. Inspector で Add Component → Custom → AreaTrigger を選択してアタッチします。

3. プレイヤーノードの準備

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite などでプレイヤー用ノードを作成。
  2. ノード名を Player にします(デフォルトの playerNodeName = "Player" と一致させるため)。
  3. Inspector で Add Component → Physics 2D → BoxCollider2D を追加。
    • Size をプレイヤーの見た目に合わせて調整します。
  4. Inspector で Add Component → Physics 2D → RigidBody2D を追加。
    • Type を Dynamic に設定し、重力や移動スクリプトで動かせるようにします。

この時点で、PlayerArea2D_Test のエリア内に侵入すると、AreaTrigger が反応する状態になります。

4. 簡易テスト(ログのみで確認する)

  1. Area2D_Test ノードを選択し、Inspector で AreaTrigger コンポーネントのプロパティを確認します。
  2. 初期状態では以下になっているはずです:
    • playerNodeName : Player
    • playerGroupName : 空
    • useTriggerEvent : true
    • autoDestroyDelay : 0
    • targetNode : null
    • targetComponentName : 空
    • targetMethodName : 空
    • logOnTrigger : true
  3. このままでも、プレイヤーが侵入した瞬間にコンソールにログが出て、エリアノードが破棄されます。
  4. エディタ右上の Play ボタンでシーンを再生し、プレイヤーを動かしてエリアに侵入させてみてください。
    • Console パネルに [AreaTrigger] プレイヤーがエリアに侵入しました... のログが出力されます。
    • Hierarchy から Area2D_Test ノードが消えていれば成功です。

5. 任意のイベントを起動してみる(メソッド呼び出しの設定)

次に、AreaTrigger から別のコンポーネントのメソッドを呼び出してみましょう。
ここでは例として GameEventReceiver という簡単な受け取り側コンポーネントを作ります。

5-1. 受け取り側コンポーネントの作成

  1. Assets パネルで右クリック → Create → TypeScript を選択。
  2. ファイル名を GameEventReceiver.ts にします。
  3. 以下のコードを貼り付けます(このスクリプトは AreaTrigger に依存されるだけで、AreaTrigger 自体は依存していません)。

import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('GameEventReceiver')
export class GameEventReceiver extends Component {

    @property({
        tooltip: 'テスト用のメッセージ。'
    })
    public message: string = 'Area triggered!';

    /**
     * AreaTrigger から呼び出される想定のメソッド。
     * シグネチャは (areaNode: Node) => void にしておくと扱いやすいです。
     */
    public onAreaTriggered(areaNode: Node) {
        console.log('[GameEventReceiver] onAreaTriggered 呼び出し。message:', this.message, 'areaNode:', areaNode.name);
        // ここでカットシーン開始や敵出現など、好きな処理を実装できます。
    }
}

5-2. 受け取り側ノードにアタッチ

  1. Hierarchy で右クリック → Create → 2D Object → Node を選択し、EventReceiver などの名前を付けます。
  2. Inspector で Add Component → Custom → GameEventReceiver を追加します。
  3. 必要に応じて message プロパティを変更します(例:"Tutorial Start!")。

5-3. AreaTrigger から呼び出す設定

  1. Area2D_Test ノードを選択し、Inspector の AreaTrigger を確認します。
  2. 以下のように設定します:
    • targetNode : Hierarchy から EventReceiver ノードをドラッグ&ドロップ
    • targetComponentName : GameEventReceiver(@ccclass の名前と一致させる)
    • targetMethodName : onAreaTriggered
    • autoDestroyDelay : 0 のままで OK(または 0.1 などにしても良い)
  3. シーンを再生し、プレイヤーをエリアに侵入させます。
  4. Console パネルに以下のようなログが出ていれば成功です:
    • [AreaTrigger] プレイヤーがエリアに侵入しました...
    • [GameEventReceiver] onAreaTriggered 呼び出し。message: Tutorial Start! areaNode: Area2D_Test

6. グループ名(playerGroupName)での判定を使う場合

プロジェクトによっては、プレイヤーの名前が固定できない場合や、複数のプレイヤーキャラが存在する場合があります。そのようなときは、playerGroupName を活用します。

  1. プレイヤーノードの name に、共通の文字列(例:"player")を含めるようにします。
    • 例:"player1", "player2", "main_player" など。
  2. Area2D_Test の AreaTrigger プロパティを次のように設定:
    • playerNodeName : 空文字にする(名前による厳密判定を無効化)
    • playerGroupName : "player"
  3. これで、名前に "player" を含むノードすべてをプレイヤーとして扱うようになります。

より厳密なグループレイヤー判定を行いたい場合は、プロジェクトの layer 設計に合わせて _isPlayer() 内の実装を変更してください。


まとめ

本記事では、Cocos Creator 3.8.7 + TypeScript 環境で、

  • 「プレイヤーがエリアに入ったらイベントを起動し、自動で消滅する」AreaTrigger コンポーネント

を、完全に独立した汎用スクリプトとして実装しました。

ポイントは次の通りです:

  • 外部の GameManager やシングルトンに依存せず、すべてインスペクタのプロパティから設定できる。
  • プレイヤー判定は 名前 / グループ名で柔軟に行える。
  • 任意ノードの任意コンポーネント・任意メソッドを文字列指定で呼び出せるため、さまざまなイベント起動に再利用可能
  • イベント発火後は自動でノードを破棄し、「一度きりのトリガー」として扱える。
  • Collider2D / Rigidbody2D の存在チェックやログ出力で、防御的な実装になっている。

この AreaTrigger をプロジェクトのテンプレートとして用意しておけば、

  • ステージ開始イベント
  • チュートリアルポップアップ
  • ボス戦突入演出
  • 隠し要素の発見トリガー

などを、「エリアノードを置いて、AreaTrigger をアタッチし、通知先メソッドを指定するだけ」でどんどん追加していけます。
ゲームの規模が大きくなるほど、こうした再利用可能な汎用コンポーネントが、開発スピードと保守性を大きく向上させてくれます。

必要に応じて、

  • 複数回発火を許可するフラグ
  • 侵入ではなく退場時に発火するオプション
  • 特定のタグ付きノードだけを許可するフィルタ

などを追加して、自分のプロジェクトに最適化した AreaTrigger を育てていくのもおすすめです。

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をコピーしました!