【Cocos Creator 3.8】AreaTriggerの実装:アタッチするだけで「プレイヤーが入った瞬間にイベントを発火して自動消滅」する汎用スクリプト
この記事では、2Dゲームでよく使う「エリアにプレイヤーが入ったらイベントを起動して、そのエリアは使い捨てで消える」という仕組みを、1つのコンポーネントだけで実現します。
Cocos Creator 3.8.7 + TypeScript 環境で、ノードにアタッチするだけで使えるように設計し、外部のGameManagerやシングルトンに一切依存しない汎用トリガーコンポーネントとして実装します。
コンポーネントの設計方針
想定する利用シーン
- ステージの特定エリアに入ったらイベント開始(カットシーン、会話、敵出現など)
- 一度だけ有効なチェックポイント・チュートリアルポップアップの起動
- 隠しエリアに入ったらアイテム出現・実績解除など
これらを、「エリア用ノードに AreaTrigger をアタッチするだけ」で実現できるようにします。
要件整理
- このコンポーネントがアタッチされたノード(親:Area2D)が「当たり判定エリア」となる。
- プレイヤーがこのエリアに入った瞬間に「イベントが起動した」ことを通知する。
- イベント起動後、このエリアノードは自動的に消滅する(再利用されない一度きりのトリガー)。
- 外部スクリプトへの依存は禁止。
→ 通知は 以下の2つの方法で行う:- ① 任意のノードにアタッチされたコンポーネントのメソッドを呼び出す(インスペクタで指定)
- ② コンソールログやエディタ上のテストに使える簡易ログ(最低限のフィードバック)
- 「プレイヤーかどうか」の判定は、対象ノード名またはグループ名で行う(インスペクタで指定)。
- 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物理設定や他のコライダー設定に応じて切り替え可能。
- true の場合:Collider2D の
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()で判定し、プレイヤーでなければ無視。 - プレイヤーなら:
- ログ出力(任意)
_notifyTarget()で任意メソッド呼び出し_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. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択。
- ファイル名を
AreaTrigger.tsにします。 - 自動生成されたコードをすべて削除し、本記事の 「TypeScriptコードの実装」 セクションのコードを貼り付け、保存します。
2. テスト用シーンの準備(エリアノードの作成)
- Hierarchy パネルで右クリック → Create → 2D Object → Node などで、空のノードを作成。
- ノード名を
Area2D_Testなどに変更します。
- ノード名を
- このノードをエリアとして見えるようにしたい場合は、Sprite などを追加しても構いません(任意)。
- Inspector で Add Component → Physics 2D → BoxCollider2D を追加。
- 「Is Trigger」チェックボックスを ON にしておくと、トリガーとして扱いやすくなります。
- Size を調整して、プレイヤーが通過する範囲をカバーするようにします。
- Inspector で Add Component → Physics 2D → RigidBody2D を追加。
- Type を Static に設定しておくと、動かないエリアとして扱いやすいです。
- Inspector で Add Component → Custom → AreaTrigger を選択してアタッチします。
3. プレイヤーノードの準備
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite などでプレイヤー用ノードを作成。
- ノード名を
Playerにします(デフォルトのplayerNodeName = "Player"と一致させるため)。 - Inspector で Add Component → Physics 2D → BoxCollider2D を追加。
- Size をプレイヤーの見た目に合わせて調整します。
- Inspector で Add Component → Physics 2D → RigidBody2D を追加。
- Type を Dynamic に設定し、重力や移動スクリプトで動かせるようにします。
この時点で、Player が Area2D_Test のエリア内に侵入すると、AreaTrigger が反応する状態になります。
4. 簡易テスト(ログのみで確認する)
- Area2D_Test ノードを選択し、Inspector で AreaTrigger コンポーネントのプロパティを確認します。
- 初期状態では以下になっているはずです:
- playerNodeName :
Player - playerGroupName : 空
- useTriggerEvent :
true - autoDestroyDelay :
0 - targetNode :
null - targetComponentName : 空
- targetMethodName : 空
- logOnTrigger :
true
- playerNodeName :
- このままでも、プレイヤーが侵入した瞬間にコンソールにログが出て、エリアノードが破棄されます。
- エディタ右上の Play ボタンでシーンを再生し、プレイヤーを動かしてエリアに侵入させてみてください。
- Console パネルに
[AreaTrigger] プレイヤーがエリアに侵入しました...のログが出力されます。 - Hierarchy から Area2D_Test ノードが消えていれば成功です。
- Console パネルに
5. 任意のイベントを起動してみる(メソッド呼び出しの設定)
次に、AreaTrigger から別のコンポーネントのメソッドを呼び出してみましょう。
ここでは例として GameEventReceiver という簡単な受け取り側コンポーネントを作ります。
5-1. 受け取り側コンポーネントの作成
- Assets パネルで右クリック → Create → TypeScript を選択。
- ファイル名を
GameEventReceiver.tsにします。 - 以下のコードを貼り付けます(このスクリプトは 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. 受け取り側ノードにアタッチ
- Hierarchy で右クリック → Create → 2D Object → Node を選択し、
EventReceiverなどの名前を付けます。 - Inspector で Add Component → Custom → GameEventReceiver を追加します。
- 必要に応じて
messageプロパティを変更します(例:"Tutorial Start!")。
5-3. AreaTrigger から呼び出す設定
- Area2D_Test ノードを選択し、Inspector の AreaTrigger を確認します。
- 以下のように設定します:
- targetNode : Hierarchy から EventReceiver ノードをドラッグ&ドロップ
- targetComponentName :
GameEventReceiver(@ccclass の名前と一致させる) - targetMethodName :
onAreaTriggered - autoDestroyDelay : 0 のままで OK(または 0.1 などにしても良い)
- シーンを再生し、プレイヤーをエリアに侵入させます。
- Console パネルに以下のようなログが出ていれば成功です:
[AreaTrigger] プレイヤーがエリアに侵入しました...[GameEventReceiver] onAreaTriggered 呼び出し。message: Tutorial Start! areaNode: Area2D_Test
6. グループ名(playerGroupName)での判定を使う場合
プロジェクトによっては、プレイヤーの名前が固定できない場合や、複数のプレイヤーキャラが存在する場合があります。そのようなときは、playerGroupName を活用します。
- プレイヤーノードの
nameに、共通の文字列(例:"player")を含めるようにします。- 例:
"player1","player2","main_player"など。
- 例:
- Area2D_Test の AreaTrigger プロパティを次のように設定:
- playerNodeName : 空文字にする(名前による厳密判定を無効化)
- playerGroupName :
"player"
- これで、名前に
"player"を含むノードすべてをプレイヤーとして扱うようになります。
より厳密なグループレイヤー判定を行いたい場合は、プロジェクトの layer 設計に合わせて _isPlayer() 内の実装を変更してください。
まとめ
本記事では、Cocos Creator 3.8.7 + TypeScript 環境で、
- 「プレイヤーがエリアに入ったらイベントを起動し、自動で消滅する」AreaTrigger コンポーネント
を、完全に独立した汎用スクリプトとして実装しました。
ポイントは次の通りです:
- 外部の GameManager やシングルトンに依存せず、すべてインスペクタのプロパティから設定できる。
- プレイヤー判定は 名前 / グループ名で柔軟に行える。
- 任意ノードの任意コンポーネント・任意メソッドを文字列指定で呼び出せるため、さまざまなイベント起動に再利用可能。
- イベント発火後は自動でノードを破棄し、「一度きりのトリガー」として扱える。
- Collider2D / Rigidbody2D の存在チェックやログ出力で、防御的な実装になっている。
この AreaTrigger をプロジェクトのテンプレートとして用意しておけば、
- ステージ開始イベント
- チュートリアルポップアップ
- ボス戦突入演出
- 隠し要素の発見トリガー
などを、「エリアノードを置いて、AreaTrigger をアタッチし、通知先メソッドを指定するだけ」でどんどん追加していけます。
ゲームの規模が大きくなるほど、こうした再利用可能な汎用コンポーネントが、開発スピードと保守性を大きく向上させてくれます。
必要に応じて、
- 複数回発火を許可するフラグ
- 侵入ではなく退場時に発火するオプション
- 特定のタグ付きノードだけを許可するフィルタ
などを追加して、自分のプロジェクトに最適化した AreaTrigger を育てていくのもおすすめです。




