【Cocos Creator 3.8】SelfDestruct コンポーネントの実装:アタッチするだけで「一定時間後 or 衝突時に自動で自滅する弾丸ノード」を実現する汎用スクリプト
弾丸やエフェクトなど、「しばらくしたら勝手に消えてほしい」「何かに当たったら即消えてほしい」というノードは、どんなゲームでも頻出です。この SelfDestruct コンポーネントをノードにアタッチしておけば、時間経過・衝突をトリガーに自動でノードを破棄してくれるため、毎回専用スクリプトを書く必要がなくなります。
さらに、弾丸を子ノードとして発射するケースを想定し、「自分自身」だけでなく「親ノード」を消す」動作もインスペクタから切り替えられるようにします。
コンポーネントの設計方針
要件整理
- ノードにアタッチするだけで動作する。
- 生成から一定時間後に自動で破棄できる。
- 物理衝突(2D/3D)やトリガー接触を検知して破棄できる。
- 「自分自身」か「親ノード」のどちらを消すかを選択できる。
- 外部の GameManager やシングルトンには一切依存しない。
- 2D 物理・3D 物理どちらにも対応できるよう、防御的に実装する。
SelfDestruct は、以下の2つの破棄条件をサポートします。
- 時間経過による自壊:生成から指定秒数後に破棄。
- 衝突・トリガー接触による自壊:何かにぶつかったら破棄。
また、弾丸を親ノードごと消したいケース(例:弾丸の見た目ノードは子、当たり判定やロジックは親にある)を考慮し、どのノードを破棄対象にするかを選べるようにします。
インスペクタで設定可能なプロパティ
SelfDestruct コンポーネントに用意する @property は次のとおりです。
- destroyTargetMode: ‘SELF’ | ‘PARENT’
- 破棄対象を指定します。
SELF: このコンポーネントが付いているノード自身を破棄。PARENT: 親ノードを破棄(親が存在しない場合は自分を破棄)。
- enableTimeDestroy: boolean
- 時間経過による自壊を有効にするかどうか。
- ON のときだけ timeToDestroy が有効になります。
- timeToDestroy: number
- 生成から破棄までの時間(秒)。
- 0 以下の値を設定した場合は無視されます(エラーログを出して無効化)。
- enableCollisionDestroy: boolean
- 衝突・トリガー接触による自壊を有効にするかどうか。
- ON のときだけ OnCollision/OnTrigger 系コールバックで破棄を試みます。
- destroyOnCollisionEnter: boolean
- Collision(非トリガー)の「Enter」で破棄するか。
- destroyOnCollisionStay: boolean
- Collision(非トリガー)の「Stay」で破棄するか。
- destroyOnCollisionExit: boolean
- Collision(非トリガー)の「Exit」で破棄するか。
- destroyOnTriggerEnter: boolean
- Trigger(isTrigger = true)の「Enter」で破棄するか。
- destroyOnTriggerStay: boolean
- Trigger(isTrigger = true)の「Stay」で破棄するか。
- destroyOnTriggerExit: boolean
- Trigger(isTrigger = true)の「Exit」で破棄するか。
- logWarnings: boolean
- 設定ミスや物理コンポーネント未設定時に警告ログを出すかどうか。
- デバッグ時は ON、本番では OFF にするなど切り替え可能。
防御的な実装ポイント
- 物理衝突を使う場合、2D なら
Collider2D、3D ならColliderが必要です。 - SelfDestruct 側では 必須コンポーネントとしては強制せず、存在しない場合は
logWarningsに応じて警告を出すだけにします。 - 時間破棄だけを使うケースでは Collider が不要なため、依存を強制しません。
TypeScriptコードの実装
import { _decorator, Component, Node, Enum, warn, log, Collider2D, IPhysics2DContact, Contact2DType, Collider, ICollisionEvent, ITriggerEvent } from 'cc';
const { ccclass, property } = _decorator;
/**
* 破棄対象のモード
*/
enum DestroyTargetMode {
SELF = 0,
PARENT = 1,
}
@ccclass('SelfDestruct')
export class SelfDestruct extends Component {
@property({
type: Enum(DestroyTargetMode),
tooltip: '破棄対象を選択します。\nSELF: このノード自身を破棄\nPARENT: 親ノードを破棄(親が無ければ自分を破棄)',
})
public destroyTargetMode: DestroyTargetMode = DestroyTargetMode.SELF;
@property({
tooltip: '時間経過による自壊を有効にするかどうか。',
})
public enableTimeDestroy: boolean = true;
@property({
tooltip: '生成から何秒後に破棄するか。\n0 以下の場合は無効として扱われます。',
min: 0,
})
public timeToDestroy: number = 3.0;
@property({
tooltip: '衝突・トリガー接触による自壊を有効にするかどうか。\n2D: Collider2D / 3D: Collider が必要です。',
})
public enableCollisionDestroy: boolean = true;
@property({
tooltip: '非トリガー衝突(Collision)の Enter で破棄するかどうか。',
})
public destroyOnCollisionEnter: boolean = true;
@property({
tooltip: '非トリガー衝突(Collision)の Stay で破棄するかどうか。',
})
public destroyOnCollisionStay: boolean = false;
@property({
tooltip: '非トリガー衝突(Collision)の Exit で破棄するかどうか。',
})
public destroyOnCollisionExit: boolean = false;
@property({
tooltip: 'トリガー接触(Trigger)の Enter で破棄するかどうか。',
})
public destroyOnTriggerEnter: boolean = true;
@property({
tooltip: 'トリガー接触(Trigger)の Stay で破棄するかどうか。',
})
public destroyOnTriggerStay: boolean = false;
@property({
tooltip: 'トリガー接触(Trigger)の Exit で破棄するかどうか。',
})
public destroyOnTriggerExit: boolean = false;
@property({
tooltip: '設定ミスや Collider 未設定時に警告ログを出すかどうか。',
})
public logWarnings: boolean = true;
// 内部状態
private _timer: number = 0;
private _scheduled: boolean = false;
private _destroyed: boolean = false;
// 2D / 3D の Collider 参照(存在すれば使用)
private _collider2D: Collider2D | null = null;
private _collider3D: Collider | null = null;
onLoad() {
// 2D / 3D Collider を取得(あれば使用)
this._collider2D = this.getComponent(Collider2D);
this._collider3D = this.getComponent(Collider);
if (this.enableCollisionDestroy) {
if (!this._collider2D && !this._collider3D) {
if (this.logWarnings) {
warn('[SelfDestruct] enableCollisionDestroy が ON ですが、このノードに Collider2D / Collider が見つかりません。衝突では破棄されません。ノードに Collider を追加してください。');
}
}
}
// 時間破棄の設定チェック
if (this.enableTimeDestroy) {
if (this.timeToDestroy <= 0) {
if (this.logWarnings) {
warn('[SelfDestruct] enableTimeDestroy が ON ですが、timeToDestroy が 0 以下です。時間による自壊は無効になります。');
}
this.enableTimeDestroy = false;
} else {
this._timer = 0;
this._scheduled = true;
}
}
// 衝突イベントの登録(2D)
if (this.enableCollisionDestroy && this._collider2D) {
// 2D 衝突イベント
this._collider2D.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact2D, this);
this._collider2D.on(Contact2DType.STAY_CONTACT, this._onStayContact2D, this);
this._collider2D.on(Contact2DType.END_CONTACT, this._onEndContact2D, this);
}
// 衝突イベントの登録(3D)
if (this.enableCollisionDestroy && this._collider3D) {
// 3D Collision
this._collider3D.on('onCollisionEnter', this._onCollisionEnter3D, this);
this._collider3D.on('onCollisionStay', this._onCollisionStay3D, this);
this._collider3D.on('onCollisionExit', this._onCollisionExit3D, this);
// 3D Trigger
this._collider3D.on('onTriggerEnter', this._onTriggerEnter3D, this);
this._collider3D.on('onTriggerStay', this._onTriggerStay3D, this);
this._collider3D.on('onTriggerExit', this._onTriggerExit3D, this);
}
}
onDestroy() {
this._destroyed = true;
// 2D イベント解除
if (this._collider2D) {
this._collider2D.off(Contact2DType.BEGIN_CONTACT, this._onBeginContact2D, this);
this._collider2D.off(Contact2DType.STAY_CONTACT, this._onStayContact2D, this);
this._collider2D.off(Contact2DType.END_CONTACT, this._onEndContact2D, this);
}
// 3D イベント解除
if (this._collider3D) {
this._collider3D.off('onCollisionEnter', this._onCollisionEnter3D, this);
this._collider3D.off('onCollisionStay', this._onCollisionStay3D, this);
this._collider3D.off('onCollisionExit', this._onCollisionExit3D, this);
this._collider3D.off('onTriggerEnter', this._onTriggerEnter3D, this);
this._collider3D.off('onTriggerStay', this._onTriggerStay3D, this);
this._collider3D.off('onTriggerExit', this._onTriggerExit3D, this);
}
}
update(deltaTime: number) {
if (this._destroyed) {
return;
}
// 時間経過による自壊
if (this.enableTimeDestroy && this._scheduled) {
this._timer += deltaTime;
if (this._timer >= this.timeToDestroy) {
this._scheduled = false;
this._doDestroy('time');
}
}
}
/**
* 実際に破棄処理を行う共通関数。
* 既に破棄済みであれば何もしない。
*/
private _doDestroy(reason: 'time' | 'collision' | 'trigger') {
if (this._destroyed) {
return;
}
this._destroyed = true;
let target: Node | null = null;
if (this.destroyTargetMode === DestroyTargetMode.SELF) {
target = this.node;
} else if (this.destroyTargetMode === DestroyTargetMode.PARENT) {
target = this.node.parent ? this.node.parent : this.node;
}
if (!target) {
if (this.logWarnings) {
warn('[SelfDestruct] 破棄対象ノードが見つかりません。何も破棄されませんでした。');
}
return;
}
if (this.logWarnings) {
log(`[SelfDestruct] ノード "${target.name}" を破棄します。理由: ${reason}`);
}
target.destroy();
}
// -----------------------
// 2D 衝突イベントハンドラ
// -----------------------
private _onBeginContact2D(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnCollisionEnter) {
this._doDestroy('collision');
}
}
private _onStayContact2D(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnCollisionStay) {
this._doDestroy('collision');
}
}
private _onEndContact2D(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnCollisionExit) {
this._doDestroy('collision');
}
}
// -----------------------
// 3D Collision イベント
// -----------------------
private _onCollisionEnter3D(event: ICollisionEvent) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnCollisionEnter) {
this._doDestroy('collision');
}
}
private _onCollisionStay3D(event: ICollisionEvent) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnCollisionStay) {
this._doDestroy('collision');
}
}
private _onCollisionExit3D(event: ICollisionEvent) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnCollisionExit) {
this._doDestroy('collision');
}
}
// -----------------------
// 3D Trigger イベント
// -----------------------
private _onTriggerEnter3D(event: ITriggerEvent) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnTriggerEnter) {
this._doDestroy('trigger');
}
}
private _onTriggerStay3D(event: ITriggerEvent) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnTriggerStay) {
this._doDestroy('trigger');
}
}
private _onTriggerExit3D(event: ITriggerEvent) {
if (!this.enableCollisionDestroy) return;
if (this.destroyOnTriggerExit) {
this._doDestroy('trigger');
}
}
}
コードの要点解説
- onLoad
- 2D 用
Collider2D、3D 用ColliderをgetComponentで取得。 enableTimeDestroyとtimeToDestroyをチェックし、タイマーを初期化。- 2D/3D の衝突・トリガーイベントを登録。
- Collider が無いのに
enableCollisionDestroyが ON の場合は警告ログ。
- 2D 用
- update
- 時間破棄が有効なとき、
deltaTimeを積算してtimeToDestroyに達したら_doDestroy('time')を呼び出し。
- 時間破棄が有効なとき、
- _doDestroy
- 重複破棄を防ぐため
_destroyedフラグでガード。 destroyTargetModeに応じて「自分」か「親」を破棄対象に選択。logWarningsが ON の場合、破棄理由と対象ノード名をログ出力。Node.destroy()を呼び出してノードを破棄。
- 重複破棄を防ぐため
- 衝突イベント群
- 2D:
Contact2DType.BEGIN_CONTACT / STAY_CONTACT / END_CONTACTを使用。 - 3D:
onCollisionEnter / onCollisionStay / onCollisionExitおよびonTriggerEnter / Stay / Exitを使用。 - 各イベント内で対応するフラグ(
destroyOnCollisionEnterなど)を見て_doDestroy('collision' | 'trigger')を呼ぶだけの薄いラッパー。
- 2D:
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を SelfDestruct.ts にします。
- 自動生成された中身をすべて削除し、本記事の
SelfDestructクラスのコードを貼り付けて保存します。
2D 弾丸での使用例(Physics2D)
2. テスト用シーンの準備
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite などで弾丸用ノードを作成し、名前を Bullet にします。
- 同じく、壁となるノード(例:Wall)を作成します。
- メニューの Project → Project Settings → Feature Cropping → Physics 2D を有効にし、2D 物理を ON にしておきます。(既に有効なら不要)
3. 物理コンポーネントの設定(2D)
Bullet ノードに対して:
- Inspector で Add Component → Physics 2D → RigidBody2D を追加します。
- Type を Dynamic など、弾丸として動くタイプに設定します。
- Inspector で Add Component → Physics 2D → CircleCollider2D(または BoxCollider2D)を追加します。
- Size/Radius を弾丸の見た目に合わせて調整します。
- 「Is Trigger」を OFF にすると Collision として扱われます。
Wall ノードに対して:
- Inspector で Add Component → Physics 2D → RigidBody2D を追加し、Type を Static にします。
- Inspector で Add Component → Physics 2D → BoxCollider2D を追加し、壁のサイズに合わせます。
4. SelfDestruct コンポーネントのアタッチ
- Hierarchy で Bullet ノードを選択します。
- Inspector の Add Component → Custom → SelfDestruct を選択してアタッチします。
- プロパティを次のように設定します(一例):
- Destroy Target Mode:
SELF- 弾丸ノード自身を破棄します。
- Enable Time Destroy:
true - Time To Destroy:
5- 5秒経過したら自動で消えます。
- Enable Collision Destroy:
true - Destroy On Collision Enter:
true - Destroy On Collision Stay:
false - Destroy On Collision Exit:
false - Destroy On Trigger Enter/Stay/Exit:
false(2D では使われませんが OFF のままでOK) - Log Warnings:
true(挙動をログで確認したい場合)
5. 再生して確認
- シーンを保存し、▶︎(Play)ボタンで再生します。
- Bullet に初速を与えるなどして Wall に向かって動かします(Rigidbody2D の Linear Velocity を設定するか、別スクリプトで移動させてもOK)。
- Bullet が Wall に衝突した瞬間に、Bullet ノードが破棄されることを確認します。
- もし壁に当たらなかった場合でも、5秒後に自動で Bullet が消えることも確認できます。
3D 弾丸での使用例(Physics3D)
6. 3D シーンでの設定
- Hierarchy で右クリック → Create → 3D Object → Sphere などで弾丸用ノードを作成し、名前を Bullet3D にします。
- 同様に、壁となる Wall3D ノードを作成します(Cube など)。
Bullet3D に対して:
- Add Component → Physics → RigidBody を追加します。
- Add Component → Physics → SphereCollider を追加します(または BoxCollider)。
- Is Trigger を ON にすると Trigger、OFF にすると Collision として扱われます。
- Add Component → Custom → SelfDestruct を追加し、例えば次のように設定します:
- Destroy Target Mode:
SELF - Enable Time Destroy:
false(衝突のみで消したい場合) - Enable Collision Destroy:
true - Collider がトリガーの場合:
- Destroy On Trigger Enter:
true - その他の Trigger/Collision フラグは
falseでOK。
- Destroy On Trigger Enter:
Wall3D に対して:
- Add Component → Physics → RigidBody(Type を Static に)と BoxCollider を追加します。
再生して Bullet3D を Wall3D に向かって発射すると、衝突 or トリガー接触したタイミングで Bullet3D が自動的に破棄されます。
親ノードごと消したい場合の設定
例えば、以下のような構成を想定します:
- BulletRoot(親ノード)
- 移動ロジックやスクリプトが付いている。
- Collider / Rigidbody もここに付いている。
- └ BulletVisual(子ノード)
- 見た目用の Sprite / Model だけが付いている。
- SelfDestruct をこちらに付けたい。
この場合:
- BulletVisual ノードに SelfDestruct をアタッチします。
- Destroy Target Mode を
PARENTに設定します。 - Collider は BulletRoot に付いていても問題ありません。SelfDestruct は 自分の親を破棄対象として選択します。
こうしておくと、子ノードの SelfDestruct が発火しても、親の BulletRoot ごときれいに破棄されます。
まとめ
- SelfDestruct コンポーネントは、ノードにアタッチするだけで
- 時間経過による自動破棄
- 衝突・トリガー接触による自動破棄
- 「自分」か「親」のどちらを破棄するかの選択
を提供する汎用スクリプトです。
- 外部の GameManager やシングルトンには一切依存せず、この1ファイルだけで完結します。
- 弾丸・一時的なエフェクト・一時 UI(トースト表示など)など、「一定条件で勝手に消えてほしいノード」に幅広く流用できます。
- プロパティをインスペクタから調整するだけで挙動を切り替えられるため、ゲームごと・シーンごとの細かい要件にも柔軟に対応できます。
プロジェクトのテンプレートとして SelfDestruct を組み込んでおけば、今後「弾丸を作るたびに寿命処理を書く」といった重複作業を大きく減らせます。まずはテストシーンで動作を確認し、気に入った設定をプレハブ(Prefab)化しておくと、さらに開発効率が上がります。




