【Cocos Creator 3.8】Glider(滑空)コンポーネントの実装:アタッチするだけで「空中でボタン押しっぱなしで落下速度を制限し、横移動速度を強化する」汎用スクリプト

このコンポーネントは、2Dアクションゲームなどでよくある「ジャンプ後にボタンを押し続けると、ゆっくり落下しながら横にスーッと滑空する」挙動を、Rigidbody2D を持つキャラクターにアタッチするだけで実現します。外部の GameManager や入力管理スクリプトには一切依存せず、インスペクタ上の設定と標準の Input システムだけで動作します。

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

まずは、Glider コンポーネントの要件を整理し、外部依存をなくすための設計を明確にします。

機能要件

  • 対象ノードは Rigidbody2D を持つ 2D キャラクターを想定。
  • 空中にいる間のみ滑空を有効にする(地上では無効)。
  • 指定した入力ボタン(例: Space キー)を押しっぱなしの間だけ滑空状態になる。
  • 滑空中は:
    • 落下速度(下向きの velocity.y)を 最大落下速度で制限する。
    • 現在の横方向入力に応じて、横移動速度を強化(通常より速く)する。
  • 滑空開始時・終了時にイベントを発行するなどの外部連携は行わず、コンポーネント単体で完結させる。

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

  • 入力は cc.input のキーボードイベントを直接購読し、他の入力管理クラスには依存しない。
  • 地上判定は、Rigidbody2D の 線形速度「地上判定フラグ」を利用し、必要に応じてユーザーが外部から切り替えられるようにする。
    • 完全な地面コリジョン判定はプロジェクトごとに大きく異なるため、汎用コンポーネントでは「Y 速度が小さい+ユーザー任意のフラグ」で簡易判定とする。
    • より厳密な地上判定をしたい場合は、isGrounded プロパティを他のスクリプトから更新して使えるようにする。
  • 必要な標準コンポーネント(Rigidbody2D)が見つからない場合は、エラーログを出して動作を中断する。

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

Glider コンポーネントに用意する @property を列挙します。

  • glideKeyCode: KeyCode
    • 滑空を有効にするために押し続けるキー。
    • デフォルトは KeyCode.SPACE
  • horizontalLeftKey: KeyCode
    • 左移動入力に使用するキー。
    • デフォルトは KeyCode.KEY_A
  • horizontalRightKey: KeyCode
    • 右移動入力に使用するキー。
    • デフォルトは KeyCode.KEY_D
  • maxFallSpeed: number
    • 滑空中に許容する 最大落下速度(負の Y 速度の下限)
    • 例: -3 の場合、velocity.y-3 より小さくならないように制限。
    • 負の値で指定(下向きがマイナス)。
  • horizontalGlideSpeed: number
    • 滑空中に適用する 横移動速度
    • 例: 6 にすると、左/右キー入力時に velocity.x-6 または 6 に設定。
  • enableInAirOnly: boolean
    • オンの場合、キャラクターが「空中」にいるときだけ滑空を有効にする。
    • オフの場合は、地上でも滑空キー押しっぱなしで横速度強化&落下制限がかかる(特殊なゲーム用)。
  • groundCheckByVelocity: boolean
    • オンの場合、「Y 速度の絶対値が閾値以下なら地上」とみなす簡易判定を行う。
    • オフの場合は、この判定を使わず、isGrounded フラグのみで地上判定を行う。
  • groundVelocityThreshold: number
    • groundCheckByVelocity が true のときに使う、地上判定の Y 速度閾値。
    • 例: 0.1 なら、|velocity.y| <= 0.1 で地上とみなす。
  • isGrounded: boolean
    • 外部スクリプトから更新可能な「地上にいるかどうか」のフラグ。
    • デフォルトは false。地面コリジョンを自前で判定している場合は、このプロパティを更新して使う。
  • debugLog: boolean
    • オンにすると、滑空開始/終了などのデバッグログをコンソールに出力する。

TypeScriptコードの実装

以下が完成した Glider コンポーネントの全コードです。


import { _decorator, Component, Node, input, Input, EventKeyboard, KeyCode, RigidBody2D, Vec2, CCBoolean, CCFloat, CCInteger } from 'cc';
const { ccclass, property } = _decorator;

/**
 * Glider
 * 空中で指定キーを押しっぱなしにしている間、
 * ・落下速度を制限(ゆっくり落ちる)
 * ・横移動速度を強化(左右キーに応じて速度付与)
 * を行う汎用コンポーネント。
 *
 * 必須コンポーネント:
 *  - RigidBody2D (同じノードにアタッチ)
 */
@ccclass('Glider')
export class Glider extends Component {

    // --- 入力設定 ---

    @property({
        type: KeyCode,
        tooltip: '滑空を有効にするために押し続けるキー。\nデフォルト: Space キー'
    })
    public glideKeyCode: KeyCode = KeyCode.SPACE;

    @property({
        type: KeyCode,
        tooltip: '左移動入力に使用するキー。\nデフォルト: A キー'
    })
    public horizontalLeftKey: KeyCode = KeyCode.KEY_A;

    @property({
        type: KeyCode,
        tooltip: '右移動入力に使用するキー。\nデフォルト: D キー'
    })
    public horizontalRightKey: KeyCode = KeyCode.KEY_D;

    // --- 物理挙動設定 ---

    @property({
        type: CCFloat,
        tooltip: '滑空中に許容する最大落下速度(負の Y 速度の下限)。\n例: -3 とすると、velocity.y が -3 より小さくならないように制限します。\n※負の値で指定してください。'
    })
    public maxFallSpeed: number = -3.0;

    @property({
        type: CCFloat,
        tooltip: '滑空中に適用する横移動速度。\n左右キー入力時に、velocity.x を ±この値 に設定します。'
    })
    public horizontalGlideSpeed: number = 6.0;

    @property({
        type: CCBoolean,
        tooltip: 'オンの場合、キャラクターが空中にいるときだけ滑空を有効にします。\nオフの場合、地上でも滑空キー押しっぱなしで効果が適用されます。'
    })
    public enableInAirOnly: boolean = true;

    @property({
        type: CCBoolean,
        tooltip: 'オンの場合、Y 速度の絶対値が groundVelocityThreshold 以下なら地上とみなす簡易判定を行います。\nオフの場合は isGrounded フラグのみで地上判定を行います。'
    })
    public groundCheckByVelocity: boolean = true;

    @property({
        type: CCFloat,
        tooltip: 'groundCheckByVelocity が true のときに使用する、地上判定の Y 速度閾値。\n|velocity.y| がこの値以下なら地上とみなします。'
    })
    public groundVelocityThreshold: number = 0.1;

    @property({
        type: CCBoolean,
        tooltip: '外部スクリプトから更新可能な地上フラグ。\ngroundCheckByVelocity が false の場合、この値のみで地上判定を行います。'
    })
    public isGrounded: boolean = false;

    @property({
        type: CCBoolean,
        tooltip: 'オンにすると、滑空開始・終了などのデバッグログをコンソールに出力します。'
    })
    public debugLog: boolean = false;

    // --- 内部状態 ---

    private _rigidBody2D: RigidBody2D | null = null;
    private _isGlideKeyPressed: boolean = false;
    private _isLeftKeyPressed: boolean = false;
    private _isRightKeyPressed: boolean = false;
    private _isGliding: boolean = false;

    onLoad() {
        // Rigidbody2D の取得
        this._rigidBody2D = this.getComponent(RigidBody2D);
        if (!this._rigidBody2D) {
            console.error('[Glider] RigidBody2D コンポーネントが見つかりません。このノードに RigidBody2D を追加してください。');
        }

        // キーボード入力イベント登録
        input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        input.on(Input.EventType.KEY_UP, this._onKeyUp, this);
    }

    onDestroy() {
        // 入力イベント登録解除
        input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        input.off(Input.EventType.KEY_UP, this._onKeyUp, this);
    }

    update(deltaTime: number) {
        if (!this._rigidBody2D) {
            return;
        }

        // 地上判定
        const grounded = this._checkGrounded();

        // 滑空キーが押されていて、条件を満たしているか
        const canGlide = this._isGlideKeyPressed && (!this.enableInAirOnly || !grounded);

        if (canGlide) {
            this._applyGlide();
            if (!this._isGliding) {
                this._isGliding = true;
                if (this.debugLog) {
                    console.log('[Glider] Glide start');
                }
            }
        } else {
            if (this._isGliding && this.debugLog) {
                console.log('[Glider] Glide end');
            }
            this._isGliding = false;
        }
    }

    // --- 内部ロジック ---

    /**
     * キーが押されたときの処理
     */
    private _onKeyDown(event: EventKeyboard) {
        const keyCode = event.keyCode;

        if (keyCode === this.glideKeyCode) {
            this._isGlideKeyPressed = true;
        }

        if (keyCode === this.horizontalLeftKey) {
            this._isLeftKeyPressed = true;
        }

        if (keyCode === this.horizontalRightKey) {
            this._isRightKeyPressed = true;
        }
    }

    /**
     * キーが離されたときの処理
     */
    private _onKeyUp(event: EventKeyboard) {
        const keyCode = event.keyCode;

        if (keyCode === this.glideKeyCode) {
            this._isGlideKeyPressed = false;
        }

        if (keyCode === this.horizontalLeftKey) {
            this._isLeftKeyPressed = false;
        }

        if (keyCode === this.horizontalRightKey) {
            this._isRightKeyPressed = false;
        }
    }

    /**
     * 地上にいるかどうかを判定します。
     * groundCheckByVelocity が true の場合:
     *   - Y 速度の絶対値が groundVelocityThreshold 以下なら地上とみなす
     *   - さらに isGrounded が true なら、常に地上とみなす(OR 条件)
     * groundCheckByVelocity が false の場合:
     *   - isGrounded の値のみを使用
     */
    private _checkGrounded(): boolean {
        let grounded = this.isGrounded;

        if (this.groundCheckByVelocity && this._rigidBody2D) {
            const v = this._rigidBody2D.linearVelocity;
            if (Math.abs(v.y) <= this.groundVelocityThreshold) {
                grounded = true;
            }
        }

        return grounded;
    }

    /**
     * 滑空中の挙動を適用します。
     * - 落下速度の制限
     * - 横移動速度の付与
     */
    private _applyGlide() {
        if (!this._rigidBody2D) {
            return;
        }

        const velocity = this._rigidBody2D.linearVelocity;

        // 落下速度制限(下向きが負)
        if (velocity.y < this.maxFallSpeed) {
            velocity.y = this.maxFallSpeed;
        }

        // 横方向入力に応じて速度を設定
        let horizontal = 0;
        if (this._isLeftKeyPressed && !this._isRightKeyPressed) {
            horizontal = -1;
        } else if (this._isRightKeyPressed && !this._isLeftKeyPressed) {
            horizontal = 1;
        } else {
            // 両方押し or どちらも押していない場合は横速度を変更しない
            horizontal = 0;
        }

        if (horizontal !== 0) {
            velocity.x = horizontal * this.horizontalGlideSpeed;
        }

        // 変更を Rigidbody2D に反映
        this._rigidBody2D.linearVelocity = velocity;
    }
}

コードの主要部分の解説

  • onLoad()
    • this.getComponent(RigidBody2D) で同じノード上の Rigidbody2D を取得。
    • 見つからない場合は console.error でエラーログを出し、以降の処理では null チェックで防御。
    • Input システムに対して KEY_DOWN / KEY_UP イベントを登録し、キー状態を自前で管理。
  • update(deltaTime)
    • 毎フレーム呼ばれ、_checkGrounded() で地上かどうかを判定。
    • 滑空キーが押されていて、かつ enableInAirOnly の条件を満たす場合に _applyGlide() を実行。
    • 滑空の開始/終了タイミングで _isGliding を更新し、debugLog が true ならログ出力。
  • _checkGrounded()
    • groundCheckByVelocity が true のときは、|velocity.y| <= groundVelocityThreshold なら地上とみなす簡易判定。
    • isGrounded プロパティも OR 条件で利用できるので、より厳密な判定を外部から与えることも可能。
  • _applyGlide()
    • 落下速度制限:
      • velocity.y < maxFallSpeed(より速く落ちている)場合に velocity.y = maxFallSpeed として制限。
    • 横移動:
      • 左キーのみ押下 → velocity.x = -horizontalGlideSpeed
      • 右キーのみ押下 → velocity.x = horizontalGlideSpeed
      • 両方押し or どちらも押していない → 横速度は変更しない。

使用手順と動作確認

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

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

  1. エディタ左下の Assets パネルで、任意のフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択します。
  3. 新しく作成されたスクリプトに Glider.ts という名前を付けます。
  4. 作成された Glider.ts をダブルクリックしてエディタ(VS Code など)で開き、先ほどの TypeScript コードを 全て貼り付けて保存します。

2. テスト用キャラクターノードの作成

ここでは簡単な 2D スプライトキャラクターを例に説明します。

  1. 上部メニューから File → New Scene で新しいシーンを作るか、既存シーンを開きます。
  2. Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、Player という名前のノードを作成します。
  3. Player ノードを選択し、Inspector を確認します。
  4. まだ Rigidbody2D が付いていない場合:
    1. Inspector 下部の Add Component ボタンをクリック。
    2. Physics 2D → RigidBody2D を選択して追加します。
    3. Rigidbody2D の Body TypeDynamic に設定します。
  5. 必要に応じて BoxCollider2D などのコライダーも追加しておくと、地面との当たり判定が取りやすくなります。

3. Glider コンポーネントのアタッチ

  1. 再度、Player ノードを Hierarchy で選択します。
  2. Inspector 下部の Add Component ボタンをクリック。
  3. Custom カテゴリの中から Glider を選択します。
    • もし Glider が見つからない場合は、一度シーンを保存してエディタをリロードするか、スクリプトのコンパイルエラーが出ていないか確認してください。

4. プロパティの設定例

Inspector 上で Glider コンポーネントの各プロパティを調整します。

  • glideKeyCode: SPACE(デフォルトのままで OK)
  • horizontalLeftKey: KEY_A(デフォルト)
  • horizontalRightKey: KEY_D(デフォルト)
  • maxFallSpeed: -3-5 あたり(値を小さくするとゆっくり落ちる)
  • horizontalGlideSpeed: 610(滑空中の横移動速度、ゲームに合わせて調整)
  • enableInAirOnly: チェックを入れる(通常はオン推奨)
  • groundCheckByVelocity: とりあえずオンにしておく
  • groundVelocityThreshold: 0.10.2
  • isGrounded: とりあえず false のままで OK(後で外部から更新する場合に使用)
  • debugLog: 動作を確認したい場合はオンにしておくとログが出て便利

5. 簡易テスト環境の作成

滑空を確認するために、簡単な地面を用意します。

  1. Hierarchy で右クリック → Create → 2D Object → Sprite を選択し、Ground という名前のノードを作ります。
  2. Inspector で GroundScale X を大きめ(例: 10)にして横長の足場にします。
  3. GroundBoxCollider2D を追加し、キャラクターと接触できるようにします。
  4. GroundRigidBody2D を追加し、Body Type を Static に設定します。
  5. Player を Ground の少し上(Y 座標を高め)に配置します。

この状態で、Play ボタンを押してゲームを再生します。

6. 動作確認

ゲーム再生中に以下を試してみてください。

  1. Player が落下して Ground に着地することを確認します。
  2. ジャンプ機能を別途実装している場合は、ジャンプで空中に出てから:
    • Space キーを押しっぱなしにすると、落下速度が遅くなり、ふわっとした下降になる。
    • Space を押しっぱなしのまま A / D キーを押すと、左右にスーッと滑空するように動く。
  3. ジャンプ機能がまだ無い場合は、Player の初期位置を高くしておき、落下中に Space + A / D を押して挙動を確認します。

もし「地上にいても滑空が有効になってしまう/ならない」など挙動が気になる場合は、

  • groundCheckByVelocity のオン/オフ
  • groundVelocityThreshold の値
  • 必要に応じて、外部スクリプトから Glider.isGrounded を明示的に切り替える

といった調整で、プロジェクトに合わせた挙動にできます。

まとめ

この Glider コンポーネントは、

  • Rigidbody2D を持つノードにアタッチするだけで、
  • 空中での滑空(落下速度制限+横移動強化)を簡単に追加できる

汎用スクリプトです。外部の GameManager や入力管理クラスに依存せず、すべての設定をインスペクタの @property から行えるように設計しているため、

  • プロトタイプ段階で素早く「グライダーアクション」を試したいとき
  • 複数キャラクターに同じ滑空挙動を共有したいとき
  • プロジェクトごとに異なる地上判定ロジックに合わせて柔軟に調整したいとき

といった場面で特に役立ちます。

また、maxFallSpeedhorizontalGlideSpeed を変えるだけで、

  • ふわっとした長時間の滑空(落下速度をかなり小さく、横速度も控えめ)
  • 急降下に近い短い滑空(落下速度をあまり制限せず、横速度を高く)

など、さまざまなゲーム性を簡単に試すことができます。
このコンポーネントをベースに、アニメーション再生やエフェクト再生を組み合わせれば、よりリッチな滑空アクションへと発展させることも可能です。