【Cocos Creator 3.8】Kamikaze(特攻AI)の実装:アタッチするだけで「発見時に加速し、接触した瞬間に自爆ダメージ」を行う汎用スクリプト
このガイドでは、任意の敵キャラクターにアタッチするだけで、
- 一定距離内にターゲットを発見すると移動速度が上がり
- ターゲットに接触した瞬間に自爆ダメージを与え、自身は消滅する
という挙動を実現する汎用コンポーネント Kamikaze を、Cocos Creator 3.8.7 + TypeScript で実装します。
ターゲットの指定・移動速度・発見距離・ダメージ量などはすべてインスペクタから調整可能で、外部の GameManager やシングルトンに依存しない「単体で完結する」設計になっています。
コンポーネントの設計方針
1. 機能要件の整理
- ターゲット(通常はプレイヤー)に向かって移動する。
- ターゲットとの距離が「発見距離」より小さくなったら「発見状態」になり、移動速度を上げる。
- ターゲットに接触した瞬間に「自爆」し、ターゲットにダメージを与える。
- 自爆後は自分自身のノードを破棄する。
- ターゲットへのダメージ通知も、このコンポーネント単体で完結させる。
ここで「他のノードに依存しない」ことが条件なので、
- ターゲットの参照はインスペクタで直接設定できるようにする(
@property(Node))。 - ダメージ通知は「ターゲットに
receiveDamage(damage: number)というメソッドがあれば呼び出す」という汎用的な形にし、存在しなければ警告ログを出すだけにする。 - 移動は Transform を直接更新する(物理挙動に依存しない)。
- 接触判定はシンプルに「距離が
contactRadius以下になったら接触とみなす」という距離ベースの判定にする(Collider や Rigidbody への依存を避ける)。
これにより、どんな 2D/3D ゲームでも「とりあえずターゲットに向かって突撃して爆発する敵」を簡単に作ることができます。
2. インスペクタで設定可能なプロパティ設計
以下のようなプロパティを用意します。
- target(
Node | null)- 説明: 追いかけるターゲットノード(通常はプレイヤー)。
- 設定方法: Inspector でシーン内のノードをドラッグ&ドロップ。
- 未設定の場合は自動でターゲットを探さず、警告ログを出して動作を停止する。
- normalSpeed(
number)- 説明: 未発見時(通常巡回状態)の移動速度(単位: ユニット/秒)。
- 例: 1.0〜3.0 程度。
- rushSpeed(
number)- 説明: ターゲット発見後(特攻状態)の移動速度(ユニット/秒)。
- 例: 5.0〜10.0 程度。
normalSpeedより大きくする。
- detectRadius(
number)- 説明: ターゲットを「発見」したとみなす距離(ユニット)。
- この距離より近づくと、速度が
rushSpeedに変化する。
- contactRadius(
number)- 説明: ターゲットに「接触」したとみなす距離(ユニット)。
- この距離より近づいた瞬間に自爆ダメージ処理を行う。
detectRadiusよりも小さく設定するのが一般的(例: detect=5, contact=0.5)。
- damage(
number)- 説明: 接触時にターゲットへ与えるダメージ量。
- ターゲット側が任意の HP 管理ロジックを持っている前提で、
receiveDamage(damage: number)を呼び出す。
- destroySelfOnExplode(
boolean)- 説明: 自爆時に自分自身のノードを破棄するかどうか。
- 通常は
true。テスト用に false にして挙動を確認することも可能。
- debugDraw(
boolean)- 説明: エディタまたはゲーム実行中に、発見範囲/接触範囲を Gizmo で可視化するかどうか。
- 有効にすると Scene ビュー上で円が描画され、調整がしやすくなる。
なお、今回は「完全に独立したコンポーネント」とするため、
- GameManager などの外部スクリプトへの参照は一切持たない。
- ターゲットへのダメージ通知も「メソッドがあれば呼ぶ」だけにとどめ、存在しない場合はログを出すだけでゲーム進行を止めない。
TypeScriptコードの実装
以下が完成した Kamikaze.ts の全コードです。
import { _decorator, Component, Node, Vec3, math, director, Color, Gizmo, geometry, log, warn } from 'cc';
const { ccclass, property, executeInEditMode, menu } = _decorator;
/**
* Kamikaze
* ターゲットを発見すると加速し、接触した瞬間に自爆ダメージを与える汎用特攻AIコンポーネント。
*
* 想定使用例:
* - 敵キャラクターのノードにアタッチし、target にプレイヤーノードを指定するだけで動作。
* - ターゲット側に receiveDamage(damage: number) があれば自動で呼び出される。
*/
@ccclass('Kamikaze')
@executeInEditMode(true)
@menu('Custom/Kamikaze')
export class Kamikaze extends Component {
@property({
type: Node,
tooltip: '追いかけるターゲットノード(通常はプレイヤー)。\n未設定の場合、このコンポーネントは動作しません。'
})
public target: Node | null = null;
@property({
tooltip: '通常時の移動速度(ユニット/秒)。'
})
public normalSpeed: number = 2.0;
@property({
tooltip: 'ターゲット発見後(特攻状態)の移動速度(ユニット/秒)。'
})
public rushSpeed: number = 6.0;
@property({
tooltip: 'ターゲットを「発見」したとみなす距離(ユニット)。\nこの距離より近づくと速度が rushSpeed に切り替わります。'
})
public detectRadius: number = 5.0;
@property({
tooltip: 'ターゲットに「接触」したとみなす距離(ユニット)。\nこの距離より近づいた瞬間に自爆ダメージ処理を行います。'
})
public contactRadius: number = 0.5;
@property({
tooltip: '接触時にターゲットへ与えるダメージ量。\nターゲットに receiveDamage(damage: number) があれば呼び出します。'
})
public damage: number = 10;
@property({
tooltip: '自爆時にこのノードを自動的に破棄するかどうか。'
})
public destroySelfOnExplode: boolean = true;
@property({
tooltip: 'Sceneビュー上で発見範囲/接触範囲を可視化するかどうか(Gizmo描画)。'
})
public debugDraw: boolean = true;
// 内部状態
private _isRushing: boolean = false;
private _hasExploded: boolean = false;
private _tempVec: Vec3 = new Vec3();
onLoad() {
// 防御的チェック:ターゲット未設定の場合は警告を出す
if (!this.target) {
warn('[Kamikaze] target が設定されていません。このコンポーネントは動作しません。 Node:', this.node.name);
}
// contactRadius は detectRadius より小さい方が自然なので、逆転していたら警告
if (this.contactRadius >= this.detectRadius) {
warn('[Kamikaze] contactRadius が detectRadius 以上に設定されています。意図した挙動にならない可能性があります。');
}
}
start() {
// 実行時に初期状態をリセット
this._isRushing = false;
this._hasExploded = false;
}
update(deltaTime: number) {
// エディタ上でプレビュー中に動かしたくない場合は、実行中かどうかチェックしてもよい
if (!director.isPaused() && director.getScene()) {
this._updateLogic(deltaTime);
}
}
/**
* メインロジック更新
*/
private _updateLogic(deltaTime: number) {
if (this._hasExploded) {
return;
}
if (!this.target) {
// ターゲット未設定なら何もしない
return;
}
// ターゲットとの距離を計算
const selfPos = this.node.worldPosition;
const targetPos = this.target.worldPosition;
Vec3.subtract(this._tempVec, targetPos, selfPos);
const distance = this._tempVec.length();
// 発見状態の切り替え
if (distance <= this.detectRadius) {
if (!this._isRushing) {
this._isRushing = true;
log(`[Kamikaze] ターゲットを発見しました。特攻モードに移行します。 Node: ${this.node.name}`);
}
}
// 接触判定
if (distance <= this.contactRadius) {
this._explode();
return;
}
// ターゲットへ移動
if (distance > 0.0001) {
// 正規化して方向ベクトルを得る
const dir = this._tempVec;
dir.normalize();
// 現在の速度を決定
const speed = this._isRushing ? this.rushSpeed : this.normalSpeed;
// 1フレーム分の移動量を計算
const move = dir.multiplyScalar(speed * deltaTime);
// ワールド座標で移動
const newPos = new Vec3();
Vec3.add(newPos, selfPos, move);
this.node.setWorldPosition(newPos);
}
}
/**
* 自爆処理
*/
private _explode() {
if (this._hasExploded) {
return;
}
this._hasExploded = true;
log(`[Kamikaze] 自爆しました。ターゲットにダメージを与えます。 Damage: ${this.damage}, Node: ${this.node.name}`);
// ターゲットにダメージ通知
if (this.target) {
// any キャストで汎用的にメソッド存在チェック
const anyTarget: any = this.target.getComponent(Component) || this.target;
const receiver: any = anyTarget;
if (receiver && typeof receiver.receiveDamage === 'function') {
try {
receiver.receiveDamage(this.damage);
} catch (e) {
warn('[Kamikaze] ターゲットの receiveDamage 呼び出し中に例外が発生しました:', e);
}
} else {
warn('[Kamikaze] ターゲットに receiveDamage(damage: number) メソッドが見つかりませんでした。ダメージは通知されません。');
}
}
// 自身を破棄
if (this.destroySelfOnExplode) {
this.node.destroy();
}
}
/**
* SceneビューでのGizmo描画(発見範囲と接触範囲の可視化)
* Cocos Creator 3.8 ではカスタムGizmoのAPIが限定的なため、
* ここでは簡易的な疑似コード的実装例として残しています。
*
* 実際のプロジェクトでは、Editor用拡張やデバッグ用の描画コンポーネントと組み合わせてください。
*/
// エディタ専用の簡易的な可視化(実行環境によっては無視されます)
onDrawGizmos?(gizmo: Gizmo) {
if (!this.debugDraw) {
return;
}
const pos = this.node.worldPosition;
const detectColor = new Color(0, 255, 0, 128); // 緑
const contactColor = new Color(255, 0, 0, 128); // 赤
// 発見範囲
gizmo.addCircle(new geometry.Circle(pos.x, pos.y, this.detectRadius), detectColor);
// 接触範囲
gizmo.addCircle(new geometry.Circle(pos.x, pos.y, this.contactRadius), contactColor);
}
}
コードのポイント解説
- onLoad
- ターゲット未設定時に
warnを出力し、防御的に動作を抑制。 contactRadius >= detectRadiusの場合に警告を出し、設定ミスに気づきやすくしている。
- ターゲット未設定時に
- start
- 実行開始時に内部フラグ
_isRushing/_hasExplodedをリセット。
- 実行開始時に内部フラグ
- update
- 毎フレーム
_updateLogicを呼び出し、特攻AIのメイン処理を実行。 - ターゲットとの距離をもとに「発見」「接触」「移動」を制御。
- 毎フレーム
- _updateLogic
- ターゲット未設定・既に自爆済みの場合は即 return して無駄な処理をしない。
- 距離が
detectRadius以下になったら_isRushing = trueにし、ログを出力。 - 距離が
contactRadius以下になったら_explode()を呼び出して自爆。 - それ以外の場合は、ターゲットへの方向ベクトルを正規化し、速度に応じた移動量を加算。
- _explode
- 二重呼び出し防止のため
_hasExplodedフラグを使用。 - ターゲットに
receiveDamage(damage: number)メソッドがあれば呼び出し、なければ警告ログのみ。 destroySelfOnExplodeが true の場合、自身のノードをthis.node.destroy()で破棄。
- 二重呼び出し防止のため
- onDrawGizmos(任意)
- Scene ビュー上で発見範囲/接触範囲の円を描画するためのフック。
- 実際の挙動はエディタバージョンや設定に依存するため、プロジェクトに合わせて調整してください。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- 新しく作成されたスクリプトに
Kamikaze.tsという名前を付けます。 - ダブルクリックしてエディタ(VSCode など)で開き、先ほどのコードをすべて貼り付けて保存します。
2. テスト用シーンの準備
ここでは 2D ゲームを想定した簡単なテスト手順を紹介します(3D でも同様の手順で構いません)。
- プレイヤーノードの作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択します。
- 作成されたノードの名前を
Playerに変更します。 - Sprite コンポーネントに適当な画像を設定し、見た目を確認しやすくします。
- 簡易的な HP / ダメージ受け取り処理の追加(任意)
ダメージ通知を確認したい場合は、Player に以下のような簡易コンポーネントを追加しておくと分かりやすいです(任意)。
- Assets パネルで右クリック → Create → TypeScript を選択し、
DummyHealth.tsなどの名前を付けます。 - 以下のようなシンプルなコードを貼り付けます(完全に任意、Kamikaze 本体とは独立):
import { _decorator, Component, log } from 'cc'; const { ccclass, property } = _decorator; @ccclass('DummyHealth') export class DummyHealth extends Component { @property public hp: number = 100; public receiveDamage(damage: number) { this.hp -= damage; log(`[DummyHealth] ダメージを受けました: ${damage}, 残りHP: ${this.hp}`); if (this.hp <= 0) { log('[DummyHealth] プレイヤーは倒れました。'); } } }- Hierarchy の
Playerノードを選択し、Inspector で Add Component → Custom → DummyHealth を追加します。
- Assets パネルで右クリック → Create → TypeScript を選択し、
- Kamikaze 敵ノードの作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択します。
- 作成されたノードの名前を
KamikazeEnemyに変更します。 - Sprite に敵っぽい画像を設定し、位置を Player から少し離れた場所(例: X= -5, Y=0)に配置します。
3. Kamikaze コンポーネントのアタッチ
- Hierarchy で
KamikazeEnemyノードを選択します。 - Inspector で Add Component → Custom → Kamikaze を選択し、コンポーネントを追加します。
- Inspector 上で Kamikaze の各プロパティを設定します(例):
- Target:
Playerノードをドラッグ&ドロップ - Normal Speed:
2.0 - Rush Speed:
7.0 - Detect Radius:
5.0 - Contact Radius:
0.5 - Damage:
20 - Destroy Self On Explode: チェックをオン(true)
- Debug Draw: 必要に応じてオン(true)
4. 動作確認
- Scene ビューで
KamikazeEnemyとPlayerの位置関係を確認し、KamikazeEnemy が detectRadius より遠くにいる場合は、ゆっくりと Player に近づいてくることを確認します。 - ゲームを再生(Play)します。
- コンソールログ(Console)を開き、以下のようなログが出ることを確認します。
- 敵が detectRadius 内に入った瞬間:
[Kamikaze] ターゲットを発見しました。特攻モードに移行します。
- 敵が contactRadius 内に入り、自爆した瞬間:
[Kamikaze] 自爆しました。ターゲットにダメージを与えます。 Damage: 20, Node: KamikazeEnemy
- Player に DummyHealth を付けている場合:
[DummyHealth] ダメージを受けました: 20, 残りHP: 80など
- 敵が detectRadius 内に入った瞬間:
Destroy Self On Explodeが true の場合、自爆後にKamikazeEnemyノードが Hierarchy から消えることも確認します。
5. よくある調整ポイント
- 動きが速すぎる/遅すぎる
normalSpeed/rushSpeedを調整してください。- 2D/3D のスケールやカメラ距離によって「体感速度」が変わるので、実際に再生して調整すると良いです。
- すぐに自爆してしまう/なかなか自爆しない
contactRadiusを調整してください。- プレイヤーのスプライトサイズや当たり判定を考慮して、0.3〜1.0 くらいの範囲で試してみると良いです。
- 発見が早すぎる/遅すぎる
detectRadiusを調整してください。- 画面に入ったらすぐ特攻させたい場合は、カメラ範囲に合わせて大きめの値を設定します。
- ダメージが適用されない
- ターゲット側に
receiveDamage(damage: number)メソッドを持つコンポーネントがアタッチされているか確認してください。 - メソッド名・引数の型が一致しているかも確認します。
- 存在しない場合、コンソールに
receiveDamage が見つかりませんでしたの警告が出ます。
- ターゲット側に
まとめ
この Kamikaze コンポーネントは、
- ターゲットノードを Inspector で指定するだけで
- 発見距離・接触距離・通常速度・特攻速度・ダメージ量を細かく調整できる
- 完全に独立した「特攻AI」を実現する汎用スクリプト
として設計されています。
GameManager や外部シングルトンに依存せず、
- 敵キャラクターのバリエーションごとに
normalSpeed/rushSpeed/damageを変えるだけで、 - 軽い特攻兵・重い自爆ロボット・高速ドローンなど、様々な敵キャラクターを簡単に作成できます。
また、距離ベースの接触判定を使っているため、Collider/Physics の設定に悩まされることなく、プロトタイプ段階から素早く「突撃して爆発する敵」をシーンに追加できます。
このコンポーネントをベースに、
- 自爆時にパーティクルエフェクトや SE を再生する
- 特攻開始前にチャージ演出を入れる
- ターゲットを見失ったら元の位置に戻る
といった拡張を行うことで、よりリッチな敵 AI を構築していくことも容易です。
まずは本記事のコードをそのままコピーして、シーンに 1 体だけ Kamikaze 敵を配置し、挙動を確認してみてください。そこからパラメータをいじるだけで、多様な特攻キャラクターを素早く量産できるようになります。
