【Cocos Creator】アタッチするだけ!TargetFollower (ターゲット追尾)の実装方法【TypeScript】

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

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

脱・初心者!Godot 4 ゲーム開発の「2歩目」

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

Godot4ローグライク入門 ~ダンジョン自動生成~

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

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

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

【Cocos Creator 3.8】TargetFollower の実装:アタッチするだけで「指定ターゲットを追尾する敵AI移動」を実現する汎用スクリプト

このガイドでは、任意のノード(敵キャラなど)にアタッチするだけで、指定したターゲット(プレイヤーなど)へ向かって自動で移動する「TargetFollower」コンポーネントを実装します。外部の GameManager やシングルトンに一切依存せず、インスペクタからターゲットや移動速度、追尾方法を調整するだけで使える、汎用的な敵AI移動コンポーネントを目指します。

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

機能要件の整理

  • 指定したターゲット Node に向かって親ノード(このコンポーネントをアタッチしたノード)を移動させる。
  • 2D / 3D どちらでも利用できるよう、基本は「XZ 平面 or XY 平面でのベクター移動」とし、回転は任意。
  • ターゲットが未指定の場合でもゲームがクラッシュしないよう、防御的に動作(警告ログのみ)。
  • 追尾の ON/OFF、追尾開始距離 / 停止距離の設定など、よくある敵AI挙動をプロパティで調整可能にする。
  • 外部スクリプトへの依存を完全になくし、すべてインスペクタから設定できるようにする。

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

以下のようなプロパティを用意します。

  • target: Node | null
    追尾対象となるノード(プレイヤーなど)。インスペクタからドラッグ&ドロップで指定。
  • moveSpeed: number
    追尾速度(単位: ユニット/秒)。
    例: 2.0 ~ 10.0 あたりを目安に調整。
  • enableFollow: boolean
    追尾機能の有効 / 無効フラグ。
    ゲーム中に一時停止したい場合に便利。
  • useWorldSpace: boolean
    • true: ワールド座標で追尾(通常はこちら)。
    • false: 親ノードからのローカル座標で追尾。

    2Dゲームでも 3Dゲームでも扱いやすいように、座標系を明示的に選択できるようにします。

  • followAxisX: boolean
    X 軸方向に追尾するかどうか。
  • followAxisY: boolean
    Y 軸方向に追尾するかどうか(2Dゲームでは高さ方向)。
  • followAxisZ: boolean
    Z 軸方向に追尾するかどうか(3Dゲームで前後方向)。
  • startFollowDistance: number
    ターゲットとの距離がこの値以下になったら追尾を開始する。
    0 以下の場合は、常に追尾。
  • stopDistance: number
    ターゲットとの距離がこの値以下になったら、それ以上近づかない(停止)。
    例: 0.5 ~ 1.0 で「近接攻撃の間合い」などを表現。
  • smoothTurn: boolean
    見た目の回転をターゲット方向に向けるかどうか。
    2D の場合は Y 軸回転(回転スプライト)、3D の場合は Y 軸を中心に回頭する想定。
  • turnSpeed: number
    回転速度(度/秒)。smoothTurn が有効なときのみ使用。
  • debugLog: boolean
    ターゲット未設定や距離判定などのログを出すかどうか。開発時のみ ON にする想定。

これらを組み合わせることで、以下のような挙動を簡単に実現できます。

  • 2D 横スクロールゲームで、X のみ追尾する敵。
  • 見下ろし型 2D/3D で、X/Z 平面上を追尾する敵。
  • 一定距離まで近づくと止まり、攻撃モーションに移行する前段階の移動AI。

TypeScriptコードの実装

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


import { _decorator, Component, Node, Vec3, math, Quat, warn, log } from 'cc';
const { ccclass, property } = _decorator;

/**
 * TargetFollower
 * 任意のノードにアタッチして、指定ターゲットへ向かって移動させる汎用コンポーネント。
 * - 外部スクリプトへの依存なし
 * - インスペクタからターゲットや速度、追尾軸などを調整可能
 */
@ccclass('TargetFollower')
export class TargetFollower extends Component {

    @property({
        type: Node,
        tooltip: '追尾対象となるノード(プレイヤーなど)。\n空の場合は追尾しません。'
    })
    public target: Node | null = null;

    @property({
        tooltip: '追尾速度(ユニット/秒)。\n値が大きいほど速く移動します。'
    })
    public moveSpeed: number = 3.0;

    @property({
        tooltip: '追尾機能の有効/無効。\nゲーム中に一時停止したい場合などに使用します。'
    })
    public enableFollow: boolean = true;

    @property({
        tooltip: 'true: ワールド座標で追尾。\nfalse: ローカル座標で追尾。'
    })
    public useWorldSpace: boolean = true;

    @property({
        tooltip: 'X軸方向に追尾するかどうか。'
    })
    public followAxisX: boolean = true;

    @property({
        tooltip: 'Y軸方向に追尾するかどうか。\n2Dゲームで高さ方向を無視したい場合はOFFにします。'
    })
    public followAxisY: boolean = true;

    @property({
        tooltip: 'Z軸方向に追尾するかどうか。\n2Dゲーム(横スクロール等)では通常OFFにします。'
    })
    public followAxisZ: boolean = true;

    @property({
        tooltip: 'この距離以下になったら追尾を開始します。\n0以下の場合は常に追尾します。'
    })
    public startFollowDistance: number = 0.0;

    @property({
        tooltip: 'この距離以下には近づきません(停止距離)。\n0の場合は完全に重なるまで近づきます。'
    })
    public stopDistance: number = 0.5;

    @property({
        tooltip: 'ターゲット方向へ見た目の回転を行うかどうか。'
    })
    public smoothTurn: boolean = false;

    @property({
        tooltip: '回転速度(度/秒)。\n smoothTurn が有効なときのみ使用されます。'
    })
    public turnSpeed: number = 360.0;

    @property({
        tooltip: 'デバッグログを出力するかどうか。\n開発時のみONにすることを推奨します。'
    })
    public debugLog: boolean = false;

    // 作業用の一時ベクター(GC削減のために再利用)
    private _currentPos: Vec3 = new Vec3();
    private _targetPos: Vec3 = new Vec3();
    private _direction: Vec3 = new Vec3();
    private _up: Vec3 = new Vec3(0, 1, 0);
    private _tmpQuat: Quat = new Quat();

    onLoad() {
        // onLoad では、主に設定チェックとログ出力のみ行います。
        if (!this.target) {
            warn('[TargetFollower] target が設定されていません。このノードは追尾しません。', this.node.name);
        }

        if (this.moveSpeed < 0) {
            warn('[TargetFollower] moveSpeed が負の値です。0 にリセットします。');
            this.moveSpeed = 0;
        }

        if (this.stopDistance < 0) {
            warn('[TargetFollower] stopDistance が負の値です。0 にリセットします。');
            this.stopDistance = 0;
        }

        if (this.debugLog) {
            log(`[TargetFollower] onLoad: node=${this.node.name}, moveSpeed=${this.moveSpeed}`);
        }
    }

    start() {
        // start では特別な初期化は行わず、主にログのみ。
        if (this.debugLog) {
            log('[TargetFollower] start: ready on node', this.node.name);
        }
    }

    update(deltaTime: number) {
        // 毎フレーム、追尾処理を行います。
        if (!this.enableFollow) {
            return;
        }

        if (!this.target) {
            // ターゲットが設定されていない場合は何もしない(警告は onLoad で1回のみ)
            return;
        }

        // 現在位置とターゲット位置の取得(ワールド or ローカル)
        if (this.useWorldSpace) {
            this.node.getWorldPosition(this._currentPos);
            this.target.getWorldPosition(this._targetPos);
        } else {
            this.node.getPosition(this._currentPos);
            this.target.getPosition(this._targetPos);
        }

        // 方向ベクトル = ターゲット - 自分
        Vec3.subtract(this._direction, this._targetPos, this._currentPos);

        // 追尾軸の制御(不要な軸は0にする)
        if (!this.followAxisX) this._direction.x = 0;
        if (!this.followAxisY) this._direction.y = 0;
        if (!this.followAxisZ) this._direction.z = 0;

        // 距離の計算(方向ベクトルの長さ)
        const distance = this._direction.length();

        // 開始距離チェック
        if (this.startFollowDistance > 0 && distance > this.startFollowDistance) {
            // まだ追尾開始距離に入っていない場合は何もしない
            if (this.debugLog) {
                log(`[TargetFollower] waiting: distance=${distance.toFixed(2)} > startFollowDistance=${this.startFollowDistance}`);
            }
            return;
        }

        // 停止距離チェック
        if (distance <= this.stopDistance) {
            // これ以上近づかない
            if (this.debugLog) {
                log(`[TargetFollower] stopped: distance=${distance.toFixed(2)} <= stopDistance=${this.stopDistance}`);
            }
            // 回転だけは行いたい場合があるので、smoothTurn は継続
            if (this.smoothTurn && distance > 0.0001) {
                this._applySmoothTurn(deltaTime);
            }
            return;
        }

        // 正規化して進行方向を得る
        if (distance > 0.0001) {
            this._direction.normalize();
        } else {
            // ほぼ同じ位置の場合は移動不要
            return;
        }

        // 移動量 = 方向 * 速度 * 経過時間
        const moveStep = this.moveSpeed * deltaTime;
        Vec3.scaleAndAdd(this._currentPos, this._currentPos, this._direction, moveStep);

        // 新しい位置を適用
        if (this.useWorldSpace) {
            this.node.setWorldPosition(this._currentPos);
        } else {
            this.node.setPosition(this._currentPos);
        }

        // 見た目の回転処理
        if (this.smoothTurn) {
            this._applySmoothTurn(deltaTime);
        }
    }

    /**
     * ターゲット方向へスムーズに回転させる処理。
     * 2D/3D ともに、Y軸を上方向とした回転を想定しています。
     */
    private _applySmoothTurn(deltaTime: number) {
        if (!this.target) {
            return;
        }

        // 位置の再取得(追尾と同じ座標系を使用)
        if (this.useWorldSpace) {
            this.node.getWorldPosition(this._currentPos);
            this.target.getWorldPosition(this._targetPos);
        } else {
            this.node.getPosition(this._currentPos);
            this.target.getPosition(this._targetPos);
        }

        // ターゲット方向のベクトル
        Vec3.subtract(this._direction, this._targetPos, this._currentPos);

        // 追尾軸の制御(移動と同じルール)
        if (!this.followAxisX) this._direction.x = 0;
        if (!this.followAxisY) this._direction.y = 0;
        if (!this.followAxisZ) this._direction.z = 0;

        if (this._direction.length() <= 0.0001) {
            return;
        }
        this._direction.normalize();

        // 現在の回転と目標回転を取得
        const currentRot = this.node.getWorldRotation();
        const targetRot = new Quat();

        // upベクトルはY軸固定(2D/3D共通で扱いやすい)
        Quat.fromViewUp(targetRot, this._direction, this._up);

        // slerp でスムーズに補間
        const t = math.clamp01(this.turnSpeed * deltaTime / 360.0);
        Quat.slerp(this._tmpQuat, currentRot, targetRot, t);

        // 回転を適用
        this.node.setWorldRotation(this._tmpQuat);
    }
}

コードの主要部分の解説

  • onLoad()
    • ターゲット未設定や負の速度など、明らかにおかしい設定値をチェックし、警告ログを出します。
    • ゲーム開始前に一度だけ呼ばれるため、ここでエラーに気づきやすくなります。
  • start()
    • 今回は特別な初期化は不要なので、デバッグログのみ。
    • 将来的に「初期位置の記録」などが必要になった場合はここに追加できます。
  • update(deltaTime)
    • enableFollow が false の場合は何もしません。
    • target が null の場合も安全に return します。
    • ワールド座標 / ローカル座標を選択して、現在位置とターゲット位置を取得します。
    • 方向ベクトル(ターゲット – 自分)を計算し、追尾しない軸は 0 にします。
    • 距離を計算し、startFollowDistancestopDistance に基づいて「追尾開始 / 停止」を制御します。
    • 距離が十分離れている場合のみ、正規化した方向ベクトルに速度と deltaTime を掛けて移動します。
    • smoothTurn が true の場合は、移動後に _applySmoothTurn() で見た目の回転を行います。
  • _applySmoothTurn(deltaTime)
    • ターゲット方向のベクトルを再計算し、Quat.fromViewUp() を使って「その方向を向く回転」を算出します。
    • 現在の回転と目標回転を Quat.slerp() で補間し、turnSpeed(度/秒)に応じて滑らかに回頭します。
    • Y軸を上方向とした回転を想定しているため、2D/3D の多くのケースで自然な挙動になります。

使用手順と動作確認

ここからは、実際に Cocos Creator 3.8.7 のエディタ上でこのコンポーネントを使う手順を説明します。

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

  1. Assets パネルで右クリックします。
  2. Create → TypeScript を選択します。
  3. ファイル名を TargetFollower.ts にします。
  4. 作成された TargetFollower.ts をダブルクリックして開き、既存のコードをすべて削除し、前述のコードをそのまま貼り付けて保存します。

2. テスト用シーンとノードの準備

ここでは 2D ゲーム風の簡単なテストを例に説明します(3D でも手順はほぼ同じです)。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、プレイヤー用ノード Player を作成します。
  2. 同様に、敵キャラ用ノードとして Enemy という Sprite ノードを作成します。
  3. Scene ビュー上で PlayerEnemy の位置を少し離して配置します(例: Player を x=0, Enemy を x=-5 に配置)。

3. TargetFollower コンポーネントをアタッチ

  1. Hierarchy で Enemy ノードを選択します。
  2. Inspector の下部にある Add Component ボタンをクリックします。
  3. Custom → TargetFollower を選択してアタッチします。

4. インスペクタでプロパティを設定

Enemy ノードの Inspector に表示された TargetFollower のプロパティを次のように設定してみましょう。

  • target: Player ノードをドラッグ&ドロップで指定。
  • moveSpeed: 3(お好みで 1~10 の範囲で調整)。
  • enableFollow: チェック ON。
  • useWorldSpace: チェック ON(2D なら基本 ON でOK)。
  • followAxisX: チェック ON。
  • followAxisY: チェック OFF(横スクロール想定で高さを無視)。
  • followAxisZ: チェック OFF(2DなのでZは使わない)。
  • startFollowDistance: 0(常に追尾開始)。
  • stopDistance: 0.5(ある程度近づいたら停止)。
  • smoothTurn: 2D でスプライトを回転させたい場合のみ ON(不要なら OFF)。
  • turnSpeed: 360(smoothTurn を ON にした場合のみ有効)。
  • debugLog: 動作確認でログを見たい場合だけ ON。

5. 再生して動作確認

  1. エディタ右上の Play ボタンをクリックしてゲームを実行します。
  2. シーンが再生されると、Enemy が自動的に Player の方向へ移動し、stopDistance で指定した距離まで近づいたところで停止するはずです。
  3. もし Player をキーボード入力などで動かすスクリプトを別途付けていれば、Enemy が常に Player を追いかけ続ける挙動が確認できます。

6. 3D シーンでの利用例(任意)

3D の TPS や見下ろし型ゲームで使う場合は、次のように設定すると扱いやすくなります。

  • useWorldSpace: ON
  • followAxisX: ON
  • followAxisY: OFF(高さを無視して水平面のみ追尾)
  • followAxisZ: ON
  • smoothTurn: ON(敵がプレイヤーの方向を向くように)
  • turnSpeed: 180 ~ 360 程度

この設定により、敵キャラが XZ 平面上でプレイヤーを追いかけつつ、常にプレイヤーの方向を向く自然な挙動になります。

まとめ

今回実装した TargetFollower コンポーネントは、

  • ターゲット指定と速度・距離・軸の設定だけで、敵AIの「追尾移動」を簡単に実現できる。
  • 外部の GameManager やシングルトンに依存せず、このスクリプト単体で完結している。
  • 2D / 3D を問わず、座標系と追尾軸を切り替えるだけで様々なゲームに再利用できる。
  • 開始距離 / 停止距離 / 回転速度などをインスペクタから調整できるため、デザイナーもパラメータ調整しやすい。

このような「アタッチするだけで使える汎用コンポーネント」をプロジェクト内にストックしておくと、敵の種類を増やしたり、新しいステージを作る際に「移動ロジックを毎回書き直す」必要がなくなり、ゲーム開発のスピードと安定性が大きく向上します。

ここからさらに発展させたい場合は、

  • 追尾中にアニメーション(走る / 止まる)を切り替える。
  • 視界角度や障害物判定を追加して「見えているときだけ追尾」する。
  • ランダムな待機時間や、パトロールポイントとの組み合わせでより複雑なAIを構築する。

といった機能も、今回の TargetFollower をベースに、同じく「独立した汎用コンポーネント」として積み上げていくと管理しやすくなります。

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

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

脱・初心者!Godot 4 ゲーム開発の「2歩目」

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

Godot4ローグライク入門 ~ダンジョン自動生成~

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

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

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

URLをコピーしました!