【Cocos Creator】アタッチするだけ!Checkpoint (復活地点)の実装方法【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】Checkpoint(復活地点)の実装:アタッチするだけで「最後に通過した地点の座標を自動保存」できる汎用スクリプト

この記事では、プレイヤーが通過したチェックポイントの座標を自動で保存しておき、あとから別のスクリプト(プレイヤー側など)から簡単に参照できる「Checkpoint(復活地点)」コンポーネントを実装します。

このコンポーネントは、GameManager などのグローバルスクリプトに依存せず、チェックポイントのノードにアタッチするだけで、「最後に通過したチェックポイントのワールド座標」を静的プロパティとして保持します。プレイヤー側のスクリプトからは、Checkpoint.getLastCheckpointPosition() を呼ぶだけで、どこからでも復活地点を取得できます。


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

要件整理

  • プレイヤーがチェックポイントに触れたタイミングで、そのチェックポイントのワールド座標を保存する。
  • 保存された座標は、GameManager 等に依存せず、このコンポーネント単体で完結して管理する。
  • 他のスクリプトからは 静的メソッド 経由で復活地点を取得できるようにする。
  • 「プレイヤーが触れたかどうか」の判定方法を、インスペクタから選べるようにする。
    • 2D 物理コライダーのトリガーCollider2DonTriggerEnter
    • ノードの距離判定(プレイヤーのノードを指定し、一定距離以内に入ったら通過とみなす)
  • 必要な標準コンポーネントが無い場合は、エラーログを出して防御的に振る舞う

外部依存をなくすためのアプローチ

  • GameManager やシングルトンは一切使わない
  • 最後に通過したチェックポイントの情報は、Checkpoint クラス自身の static プロパティとして保持する。
  • 復活地点を取得したい側(プレイヤーなど)は、Checkpoint.getLastCheckpointPosition() を呼び出すだけで良い。
  • プレイヤー判定に必要な情報(「プレイヤーのノード」や「判定に使うタグ名」など)は、すべて @property 経由でインスペクタから指定する。

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

今回のコンポーネント Checkpoint には、以下のプロパティを用意します。

  • usePhysicsTrigger: boolean
    物理トリガーで判定するかどうか
    • true: Collider2DonTriggerEnter を利用して、プレイヤーとの接触を検知する。
    • false: 指定したプレイヤーノードとの「距離判定」で通過を検知する。
  • playerNode: Node | null
    — 距離判定モードで使用するプレイヤーのノード参照
    • usePhysicsTrigger === false のときにのみ使用。
    • プレイヤーノードをドラッグ&ドロップで設定する。
  • detectRadius: number
    — 距離判定モードで使用する有効半径(ワールド座標系)。
    • プレイヤーとチェックポイントの距離がこの値以下になったら「通過」とみなす。
    • 例: 1.0 ~ 3.0 程度を目安に調整。
  • playerTag: string
    — 物理トリガー判定モードで使用するプレイヤーのタグ名
    • プレイヤーのノードの tag に設定した値と比較する。
    • 例: "Player"
  • isOneTime: boolean
    一度通過したら無効にするかどうか
    • true: 1回通過したら、それ以降はこのチェックポイントは反応しない。
    • false: 何度でも通過可能で、その度に復活地点が更新される。
  • debugLog: boolean
    デバッグログを出力するかどうか
    • true: 通過時やエラー時に console.log / console.warn / console.error を出力。
    • false: 余計なログを出さない。

また、クラス側には外部から参照できるように以下の静的メンバを用意します。

  • Checkpoint.getLastCheckpointPosition(): Vec3 | null
    — 最後に通過したチェックポイントのワールド座標を返す。まだ一度も通過していなければ null
  • Checkpoint.getLastCheckpointNode(): Node | null
    — 最後に通過したチェックポイントのノードを返す。まだ一度も通過していなければ null

TypeScriptコードの実装

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


import { _decorator, Component, Node, Vec3, Collider2D, ITriggerEvent, geometry, PhysicsSystem2D, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * Checkpoint(復活地点)コンポーネント
 *
 * - プレイヤーが触れた/近づいたチェックポイントのワールド座標を静的に保存します。
 * - GameManager 等の外部スクリプトには依存しません。
 * - 他スクリプトからは Checkpoint.getLastCheckpointPosition() で復活地点を取得できます。
 */
@ccclass('Checkpoint')
export class Checkpoint extends Component {

    // ====== インスペクタ設定用プロパティ ======

    @property({
        tooltip: 'true: 2D物理のトリガー接触で判定します。\nfalse: playerNode と detectRadius による距離判定でチェックポイント通過を検知します。'
    })
    public usePhysicsTrigger: boolean = true;

    @property({
        tooltip: '距離判定で使用するプレイヤーノードへの参照です。\nusePhysicsTrigger が false のときのみ使用されます。'
    })
    public playerNode: Node | null = null;

    @property({
        tooltip: '距離判定で使用する半径(ワールド座標系)。\nプレイヤーとの距離がこの値以下になったらチェックポイント通過とみなします。',
        min: 0.1
    })
    public detectRadius: number = 1.5;

    @property({
        tooltip: '物理トリガー判定で使用するプレイヤーのタグ名。\nプレイヤーノードの tag プロパティと一致した場合にチェックポイント通過とみなします。'
    })
    public playerTag: string = 'Player';

    @property({
        tooltip: 'true の場合、このチェックポイントは一度通過すると無効になり、以降は反応しません。'
    })
    public isOneTime: boolean = false;

    @property({
        tooltip: 'true の場合、通過時やエラー時にデバッグログをコンソールに出力します。'
    })
    public debugLog: boolean = true;

    // ====== 内部状態管理 ======

    private _collider2D: Collider2D | null = null;
    private _activated: boolean = false;

    // ====== グローバル(静的)なチェックポイント情報 ======

    private static _lastCheckpointNode: Node | null = null;
    private static _lastCheckpointPosition: Vec3 | null = null;

    /**
     * 最後に通過したチェックポイントのワールド座標を取得します。
     * まだ一度も通過していなければ null を返します。
     */
    public static getLastCheckpointPosition(): Vec3 | null {
        if (!this._lastCheckpointPosition) {
            return null;
        }
        // 外部から変更されないようにクローンを返す
        return this._lastCheckpointPosition.clone();
    }

    /**
     * 最後に通過したチェックポイントのノードを取得します。
     * まだ一度も通過していなければ null を返します。
     */
    public static getLastCheckpointNode(): Node | null {
        return this._lastCheckpointNode;
    }

    // ====== ライフサイクル ======

    onLoad() {
        if (this.usePhysicsTrigger) {
            // 物理トリガー判定モードでは Collider2D が必須
            this._collider2D = this.getComponent(Collider2D);
            if (!this._collider2D) {
                console.error('[Checkpoint] Collider2D コンポーネントが見つかりません。' +
                    'usePhysicsTrigger = true の場合は、チェックポイントノードに Collider2D を追加し、' +
                    '「Is Trigger」を有効にしてください。');
            } else {
                // トリガーイベントを購読
                this._collider2D.on('onTriggerEnter', this._onTriggerEnter, this);
            }
        } else {
            // 距離判定モードでは特に必須コンポーネントはないが、playerNode が設定されているかをチェック
            if (!this.playerNode) {
                console.warn('[Checkpoint] usePhysicsTrigger = false ですが、playerNode が設定されていません。' +
                    '距離判定で使用するプレイヤーノードをインスペクタから指定してください。');
            }
        }
    }

    start() {
        // 特に初期化処理は不要だが、デバッグログを出しておく
        if (this.debugLog) {
            console.log(`[Checkpoint] ノード "${this.node.name}" が初期化されました。` +
                ` モード: ${this.usePhysicsTrigger ? '物理トリガー' : '距離判定'}`);
        }
    }

    update(deltaTime: number) {
        // 距離判定モードのときのみ、毎フレーム判定を行う
        if (!this.usePhysicsTrigger) {
            this._checkDistanceMode();
        }
    }

    onDestroy() {
        // イベント購読を解除
        if (this._collider2D) {
            this._collider2D.off('onTriggerEnter', this._onTriggerEnter, this);
        }
    }

    // ====== 物理トリガー判定ハンドラ ======

    private _onTriggerEnter(event: ITriggerEvent) {
        if (this._activated && this.isOneTime) {
            return;
        }

        const otherNode = event.otherCollider?.node;
        if (!otherNode) {
            return;
        }

        // タグでプレイヤーかどうか判定
        if (this.playerTag !== '' && otherNode.tag !== undefined) {
            if (otherNode.tag !== this.playerTag) {
                return;
            }
        }

        this._activateCheckpoint(otherNode);
    }

    // ====== 距離判定モードの処理 ======

    private _checkDistanceMode() {
        if (this._activated && this.isOneTime) {
            return;
        }
        if (!this.playerNode) {
            return;
        }
        if (!this.node.scene || !this.playerNode.scene) {
            return; // どちらかがシーンに存在しない場合は判定しない
        }

        const checkpointWorldPos = this.node.worldPosition;
        const playerWorldPos = this.playerNode.worldPosition;

        const dist = Vec3.distance(checkpointWorldPos, playerWorldPos);
        if (dist <= this.detectRadius) {
            this._activateCheckpoint(this.playerNode);
        }
    }

    // ====== チェックポイント通過時の共通処理 ======

    private _activateCheckpoint(player: Node) {
        // すでにこのノードが最後のチェックポイントなら何もしない
        if (Checkpoint._lastCheckpointNode === this.node && this._activated) {
            return;
        }

        // 現在のワールド座標を保存
        const worldPos = this.node.worldPosition.clone();
        Checkpoint._lastCheckpointNode = this.node;
        Checkpoint._lastCheckpointPosition = worldPos;
        this._activated = true;

        if (this.debugLog) {
            console.log(
                `[Checkpoint] チェックポイント通過: "${this.node.name}" ` +
                `位置: (${worldPos.x.toFixed(2)}, ${worldPos.y.toFixed(2)}, ${worldPos.z.toFixed(2)}) ` +
                `プレイヤー: "${player.name}"`
            );
        }
    }
}

コードのポイント解説

  • 静的プロパティでグローバル状態を保持
    Checkpoint._lastCheckpointNodeCheckpoint._lastCheckpointPosition に、最後に通過したチェックポイントの情報を保存します。
    外部からは Checkpoint.getLastCheckpointPosition() / getLastCheckpointNode() で参照します。
  • onLoad
    • usePhysicsTrigger === true の場合、Collider2D を取得し、存在しなければ console.error を出します。
    • 存在する場合は on('onTriggerEnter', ...) でトリガーイベントを購読します。
    • usePhysicsTrigger === false の場合、playerNode が未設定なら console.warn を出します。
  • update
    • 距離判定モード (usePhysicsTrigger === false) のときだけ、毎フレーム _checkDistanceMode() を呼びます。
    • プレイヤーとチェックポイントノードのワールド座標の距離を計算し、detectRadius 以内なら通過とみなします。
  • _activateCheckpoint
    • 通過時の共通処理を一箇所にまとめています。
    • チェックポイントノードの worldPosition をクローンして静的プロパティに保存。
    • isOneTimetrue の場合は、_activated フラグでそれ以降の反応を抑制します。
    • debugLogtrue のときだけ詳細なログを出力します。

使用手順と動作確認

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

  1. エディタの Assets パネルで任意のフォルダ(例: scripts)を右クリックします。
  2. Create > TypeScript を選択し、ファイル名を Checkpoint.ts にします。
  3. 自動生成されたコードをすべて削除し、本記事の Checkpoint コードをそのまま貼り付けて保存します。

2. チェックポイント用ノードの作成

ここでは 2D ゲームを想定して説明しますが、3D でも同様に扱えます(Collider2D ではなく距離判定モードを使うなど)。

  1. Hierarchy パネルで右クリックし、Create > 2D Object > Sprite を選択して、新しいノードを作成します。
  2. ノード名を分かりやすく Checkpoint1 などに変更します。
  3. このノードをチェックポイントの位置に移動させます(例: x=5, y=0 など)。

3. Collider2D を追加して物理トリガー判定モードで使う場合

  1. Checkpoint1 ノードを選択し、InspectorAdd Component ボタンをクリックします。
  2. Physics 2D > BoxCollider2D など、任意の Collider2D を追加します。
  3. 追加した Collider2D のプロパティで、Is Trigger にチェックを入れます。
  4. 必要に応じて Size を調整し、プレイヤーが通過するときに必ず重なるようにします。

4. Checkpoint コンポーネントのアタッチ

  1. Checkpoint1 ノードを選択した状態で、InspectorAdd Component ボタンをクリックします。
  2. Custom カテゴリから Checkpoint を選択して追加します。
  3. 追加された Checkpoint コンポーネントのプロパティを設定します。
    • Use Physics Trigger: true(物理トリガーで判定する場合)
    • Player Tag: プレイヤーノードの tag に設定している値(例: Player
    • Is One Time: 一度だけ有効にしたい場合は true、何度も更新したい場合は false
    • Debug Log: 通過確認用に true を推奨(動作確認後は false にしても良い)

5. プレイヤーノード側の設定(物理トリガー判定モード)

  1. プレイヤー用のノード(例: Player)を Hierarchy から選択します。
  2. Inspector で、プレイヤーノードに Collider2D(例: BoxCollider2D)と RigidBody2D を追加します。
    • RigidBody2D の TypeDynamicKinematic など、ゲーム仕様に合わせて設定してください。
  3. プレイヤーノードの tag を、Checkpoint の Player Tag と同じ値(例: Player)に設定します。

これで、プレイヤーが Checkpoint1 のトリガー領域に入ったときに、Checkpoint コンポーネントが自動的に通過を検知し、最後のチェックポイント座標を保存するようになります。

6. 距離判定モードで使う場合

物理 2D を使わない、または簡易的に使いたい場合は、距離判定モードを利用できます。

  1. Checkpoint1 ノードから Collider2D を削除するか、使用しないようにします。
  2. Checkpoint コンポーネントのプロパティを以下のように設定します。
    • Use Physics Trigger: false
    • Player Node: Hierarchy からプレイヤーノードをドラッグ&ドロップ
    • Detect Radius: 例として 1.5 など、プレイヤーが近づいたときに通過とみなしたい距離
    • Is One Time: 必要に応じて設定
    • Debug Log: 動作確認のため true を推奨

この状態でゲームを再生し、プレイヤーを Checkpoint ノードに近づけると、指定した半径以内に入った瞬間にチェックポイントが通過と判定されます。

7. 復活地点を実際に使ってみる(プレイヤースクリプト側の例)

最後に、他のスクリプトから復活地点を取得して使う方法を簡単に示します。
以下は、プレイヤーが何らかの理由で死亡したときに、最後のチェックポイントにワープさせるイメージのコード例です。


import { _decorator, Component, Node, Vec3 } from 'cc';
import { Checkpoint } from './Checkpoint';
const { ccclass, property } = _decorator;

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

    public respawn() {
        const pos = Checkpoint.getLastCheckpointPosition();
        if (pos) {
            // 最後のチェックポイントの位置にプレイヤーを移動
            this.node.setWorldPosition(pos);
        } else {
            // まだチェックポイントを通過していない場合のフォールバック処理
            console.warn('[PlayerRespawnExample] まだチェックポイントを通過していません。初期位置に復帰します。');
            this.node.setWorldPosition(new Vec3(0, 0, 0));
        }
    }
}

このように、GameManager やシングルトンを用意しなくてもCheckpoint クラスの静的メソッドを呼ぶだけで、どこからでも簡単に復活地点を取得できます。


まとめ

  • Checkpoint コンポーネントは、ノードにアタッチするだけで「最後に通過したチェックポイントのワールド座標」を静的に保存し、他のスクリプトから簡単に参照できるようにする汎用スクリプトです。
  • GameManager や外部シングルトンに依存せず、このスクリプト単体で完結しているため、プロジェクトをまたいだ再利用もしやすくなっています。
  • 判定方法は
    • 物理トリガー判定(Collider2D + Is Trigger)
    • 距離判定(playerNode + detectRadius)

    の 2 パターンから選択でき、ゲームの構成に合わせて柔軟に使い分けられます。

  • プレイヤー側からは Checkpoint.getLastCheckpointPosition() を呼ぶだけで復活地点を取得できるため、死亡処理やシーン再読み込み後の復帰処理などを簡潔に実装できます。

このコンポーネントをベースに、チェックポイント通過時のエフェクト再生、SE 再生、UI 表示などを追加すれば、よりリッチなチェックポイントシステムも簡単に構築できます。まずは本記事のコードをそのまま導入し、プロジェクトに合わせてカスタマイズしてみてください。

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