【Cocos Creator 3.8】PlatformDropper の実装:アタッチするだけで「下入力+ジャンプで一方通行床をすり抜ける」汎用スクリプト

2Dアクションゲームでよくある「↓+ジャンプで足場をすり抜ける」挙動を、どのキャラクターノードにもアタッチするだけで実現できるコンポーネントを実装します。
Cocos Creator 3.8 の RigidBody2DPhysicsGroup を利用し、外部の GameManager などに一切依存せず、インスペクタから入力キーと挙動を調整できるようにします。


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

1. 機能要件の整理

  • このコンポーネントは「プレイヤー(などのキャラクター)」側にアタッチする。
  • 下方向入力+ジャンプボタンが押されたとき、一方通行床(OneWayPlatform)を一時的にすり抜けられるようにする。
  • 床の判定無効化は一時的で、一定時間後に自動で元に戻る
  • 外部スクリプトやシングルトンには依存せず、必要な情報はすべてインスペクタのプロパティから設定できるようにする。
  • 物理挙動は RigidBody2DCollider2D を前提とし、未設定の場合はログで警告を出す。
  • 「一方通行床」は PhysicsGroup(物理グループ) で識別し、そのグループとの衝突を一時的に無効にする。

2. 設計アプローチ

一方通行床自体はいろいろな実装がありますが、ここでは「床側の Collider2D が特定の PhysicsGroup に属している」ことを前提にします。
このコンポーネントは、自分(キャラクター)の Collider2D が、そのグループとの衝突を一定時間だけ無効化します。

実装のポイント:

  • 入力処理は Cocos の input.on を使って KeyCode を直接監視。
  • 「下入力中かどうか」「ジャンプボタンが押されたか」を内部フラグで管理。
  • 下入力中にジャンプが押されたら、床グループとの衝突を disable → 一定時間後に enable
  • 物理の衝突制御は Collider2DgroupPhysicsSystem2D.instanceenableCollision を用いる。

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

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

  • downKey (KeyCode)
    • 下方向入力に使うキー。
    • デフォルト: KeyCode.ARROW_DOWN
  • jumpKey (KeyCode)
    • ジャンプ入力に使うキー。
    • デフォルト: KeyCode.SPACE
  • dropDuration (number, 秒)
    • 床判定を無効にしておく時間(秒)。
    • この時間が経過すると、自動的に床との衝突を元に戻す。
    • デフォルト: 0.25 秒程度。
  • platformGroupIndex (number)
    • 「一方通行床」が属している PhysicsGroup のインデックス。
    • Project Settings > Physics > 2D > Group で確認できる番号。
    • このグループとの衝突を一時的に無効化する。
  • requireGroundedToDrop (boolean)
    • 「床抜けをするには接地状態であることが必要かどうか」。
    • 簡易的な接地判定として、RigidBody2D.linearVelocity.y >= 0(上方向に飛んでいない)を利用。
    • より厳密な接地判定が必要な場合は、別途 Ground チェックコンポーネントを併用する前提で false にするなど調整可能。
    • デフォルト: true
  • minDownHoldTime (number, 秒)
    • 床抜けを発動させるために、下キーを押し続ける最小時間。
    • 誤操作防止用。0 にすると「押した瞬間でも可」。
    • デフォルト: 0.05 秒。

内部的には以下を自動取得します(Inspector には出さない):

  • RigidBody2D(任意だが、あると接地判定に使用)
  • Collider2D(必須。なければエラーログ)

TypeScriptコードの実装


import { _decorator, Component, input, Input, KeyCode, EventKeyboard, Collider2D, RigidBody2D, PhysicsSystem2D, log, warn, error } from 'cc';
const { ccclass, property } = _decorator;

/**
 * PlatformDropper
 * - 「下キー + ジャンプキー」で、一方通行床(特定のPhysicsGroup)との衝突を一時的に無効化するコンポーネント
 * - このコンポーネントを「プレイヤーなどのキャラクターノード」にアタッチして使用する
 */
@ccclass('PlatformDropper')
export class PlatformDropper extends Component {

    @property({
        type: KeyCode,
        tooltip: '下方向入力として扱うキー。\n例: ARROW_DOWN, KEY_S など'
    })
    public downKey: KeyCode = KeyCode.ARROW_DOWN;

    @property({
        type: KeyCode,
        tooltip: 'ジャンプ入力として扱うキー。\n例: SPACE, KEY_Z など'
    })
    public jumpKey: KeyCode = KeyCode.SPACE;

    @property({
        tooltip: '床すり抜けを有効にしておく時間(秒)。\nこの時間が経過すると自動的に元の衝突設定に戻します。'
    })
    public dropDuration: number = 0.25;

    @property({
        tooltip: '一方通行床が属している PhysicsGroup のインデックス。\nProject Settings > Physics > 2D > Group で確認した番号を指定してください。'
    })
    public platformGroupIndex: number = 2;

    @property({
        tooltip: 'true の場合、床抜けには「接地状態」であることが必要になります。\n簡易的に、上方向へ移動中(ジャンプ中)は床抜けを無効にします。'
    })
    public requireGroundedToDrop: boolean = true;

    @property({
        tooltip: '床抜けを発動させるために、下キーを押し続ける最小時間(秒)。\n誤操作防止用。0 にすると押した瞬間でも床抜け可能になります。'
    })
    public minDownHoldTime: number = 0.05;

    // --- 内部状態管理用フィールド ---

    private _collider: Collider2D | null = null;
    private _rigidBody: RigidBody2D | null = null;

    private _isDownPressed: boolean = false;
    private _downPressedTime: number = 0;

    private _isDropping: boolean = false;
    private _dropTimer: number = 0;

    // 衝突マトリクスを元に戻すための保持用
    private _originalCollisionMask: number = 0;

    onLoad() {
        // 必要なコンポーネントを取得
        this._collider = this.getComponent(Collider2D);
        if (!this._collider) {
            error('[PlatformDropper] Collider2D が見つかりません。このコンポーネントを使用するノードには Collider2D を追加してください。');
        }

        this._rigidBody = this.getComponent(RigidBody2D);
        if (!this._rigidBody) {
            warn('[PlatformDropper] RigidBody2D が見つかりません。接地判定(requireGroundedToDrop)が正確でなくなる可能性があります。');
        }

        // 衝突マスクの初期値を保持
        if (this._collider) {
            this._originalCollisionMask = this._collider.mask;
        }

        // キーボード入力イベント登録
        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);
    }

    start() {
        if (!PhysicsSystem2D.instance.enable) {
            warn('[PlatformDropper] 2D PhysicsSystem が無効になっています。Project Settings > Physics > 2D で有効化してください。');
        }

        if (this.dropDuration <= 0) {
            warn('[PlatformDropper] dropDuration が 0 以下に設定されています。床抜け時間がゼロになるため、意味のある挙動にならない可能性があります。');
        }
    }

    update(deltaTime: number) {
        // 下キーが押されている間、保持時間を計測
        if (this._isDownPressed) {
            this._downPressedTime += deltaTime;
        } else {
            this._downPressedTime = 0;
        }

        // 床抜け中のタイマー管理
        if (this._isDropping) {
            this._dropTimer += deltaTime;
            if (this._dropTimer >= this.dropDuration) {
                this._endDropThrough();
            }
        }
    }

    // --- 入力イベントハンドラ ---

    private _onKeyDown(event: EventKeyboard) {
        const key = event.keyCode;

        if (key === this.downKey) {
            if (!this._isDownPressed) {
                this._isDownPressed = true;
                this._downPressedTime = 0;
            }
        }

        if (key === this.jumpKey) {
            this._tryStartDropThrough();
        }
    }

    private _onKeyUp(event: EventKeyboard) {
        const key = event.keyCode;

        if (key === this.downKey) {
            this._isDownPressed = false;
            this._downPressedTime = 0;
        }
    }

    // --- 床抜けロジック ---

    /**
     * 下入力 + ジャンプ入力 が条件を満たしたときに呼ばれる
     */
    private _tryStartDropThrough() {
        if (!this._collider) {
            // 必須コンポーネントがなければ何もしない
            return;
        }

        if (this._isDropping) {
            // すでに床抜け中なら再度開始しない
            return;
        }

        // 下キーが十分に押されているか
        if (!this._isDownPressed || this._downPressedTime < this.minDownHoldTime) {
            return;
        }

        // 接地状態を要求する場合はチェック
        if (this.requireGroundedToDrop) {
            if (!this._isGroundedApprox()) {
                return;
            }
        }

        // 実際に床抜けを開始
        this._startDropThrough();
    }

    /**
     * 簡易的な接地判定
     * - RigidBody2D が存在する場合、上方向速度が正(上昇中)なら「空中」とみなす
     * - より厳密な接地判定が必要であれば、別途 Ground チェックコンポーネントと組み合わせて使用してください
     */
    private _isGroundedApprox(): boolean {
        if (!this._rigidBody) {
            // RigidBody2D がなければ常に true とする(ユーザ側で制御する前提)
            return true;
        }

        const vy = this._rigidBody.linearVelocity.y;
        // 上方向に明確に移動している場合は未接地とみなす
        return vy <= 0.1;
    }

    /**
     * 床抜け開始処理
     * - 指定した PhysicsGroup(一方通行床)との衝突を一時的に無効化する
     */
    private _startDropThrough() {
        if (!this._collider) {
            return;
        }

        // 現在のマスクを保存
        this._originalCollisionMask = this._collider.mask;

        // 指定グループとの衝突ビットをオフにする
        const platformMask = 1 << this.platformGroupIndex;
        const newMask = this._collider.mask & ~platformMask;
        this._collider.mask = newMask;

        // 物理システムの衝突マトリクスも更新(念のため)
        // ここでは「自分の group と platformGroupIndex の組み合わせ」を一時的に disable にする
        const myGroupIndex = this._collider.group;
        PhysicsSystem2D.instance.enableCollision(myGroupIndex, this.platformGroupIndex, false);

        this._isDropping = true;
        this._dropTimer = 0;

        log(`[PlatformDropper] Start drop-through: disabled collision with group ${this.platformGroupIndex}`);
    }

    /**
     * 床抜け終了処理
     * - 衝突設定を元に戻す
     */
    private _endDropThrough() {
        if (!this._collider) {
            this._isDropping = false;
            return;
        }

        // 衝突マスクを元に戻す
        this._collider.mask = this._originalCollisionMask;

        // 物理システムの衝突マトリクスも元に戻す
        const myGroupIndex = this._collider.group;
        PhysicsSystem2D.instance.enableCollision(myGroupIndex, this.platformGroupIndex, true);

        this._isDropping = false;
        this._dropTimer = 0;

        log(`[PlatformDropper] End drop-through: restored collision with group ${this.platformGroupIndex}`);
    }
}

コードのポイント解説

  • onLoad
    • Collider2DRigidBody2D を取得。
    • Collider2D が見つからない場合は error ログを出し、実行時には何もしないようにして防御的に実装。
    • キーボード入力イベント (KEY_DOWN, KEY_UP) を登録。
  • update
    • 下キーを押している時間を _downPressedTime で計測。
    • 床抜け中 (_isDropping true) は _dropTimer を進め、dropDuration を超えたら自動で _endDropThrough() を呼ぶ。
  • _onKeyDown / _onKeyUp
    • 指定された downKeyjumpKey の押下状態を管理。
    • ジャンプキー押下時に _tryStartDropThrough() を呼び出し、条件を満たしたら床抜け開始。
  • _tryStartDropThrough
    • すでに床抜け中でないかチェック。
    • 下キーが押されているかつ、minDownHoldTime 以上押されているか確認。
    • requireGroundedToDrop が true の場合は、_isGroundedApprox() で簡易接地判定。
    • 条件を満たしていれば _startDropThrough() を呼ぶ。
  • _startDropThrough / _endDropThrough
    • _startDropThrough では、自身の Collider2D.mask から「一方通行床グループ」のビットを削除し、物理システムの enableCollision でも該当組み合わせを false に設定。
    • _endDropThrough では、保存しておいた _originalCollisionMask を復元し、enableCollision も true に戻す。
    • これにより、一定時間だけ一方通行床をすり抜ける挙動を実現。

使用手順と動作確認

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

  1. Editor の Assets パネルで任意のフォルダを右クリックします。
  2. Create > TypeScript を選択し、ファイル名を PlatformDropper.ts にします。
  3. 自動生成されたファイルをダブルクリックして開き、内容をすべて削除して、上記の TypeScript コードをそのまま貼り付けます。
  4. 保存します(Ctrl+S / Cmd+S)。

2. 一方通行床(OneWayPlatform)の準備

このコンポーネントは「特定の PhysicsGroup に属する床」を一時的にすり抜ける設計です。まずは床側を準備します。

  1. メニューから Project > Project Settings… を開きます。
  2. 左側メニューで Physics > 2D を選択します。
  3. Group セクションで、一方通行床用のグループを一つ用意します。
    • 例: index 2 の行に OneWayPlatform という名前を設定。
    • このときの インデックス番号 (例: 2) を後で platformGroupIndex に設定します。
  4. Hierarchy で床用のノード(例: OneWayPlatformNode)を作成し、Sprite などで見た目を設定します。
  5. 床ノードに Collider2D(例: BoxCollider2D)を追加します。
    • Inspector の Add Component ボタン → Physics2D > BoxCollider2D など。
  6. 床ノードの Collider2DGroup を、先ほど作成した OneWayPlatform グループ(例: index 2)に設定します。
  7. 一方通行の挙動(上からだけ乗れる)は、プロジェクトの仕様に応じて別途設定してください(例: Custom Script で onBeginContact を制御するなど)。
    この PlatformDropper は「その床との衝突を一時的に無効化する」役割のみを担います。

3. プレイヤーノードの準備

  1. Hierarchy でプレイヤー用のノード(例: Player)を作成します。
  2. プレイヤーノードに Sprite などで見た目を設定します。
  3. プレイヤーノードに RigidBody2D を追加します。
    • Inspector の Add ComponentPhysics2D > RigidBody2D
    • BodyType を Dynamic にしておくと、重力で落下するようになります。
  4. プレイヤーノードに Collider2D(例: CapsuleCollider2DBoxCollider2D)を追加します。
  5. Collider2D の Group は、プレイヤー用に任意のグループ(例: Player)を設定しておきます。

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

  1. Hierarchy で、先ほど作成した Player ノードを選択します。
  2. Inspector の一番下にある Add Component ボタンをクリックします。
  3. Custom カテゴリから PlatformDropper を選択して追加します。
  4. Inspector に PlatformDropper のプロパティが表示されるので、以下のように設定します(例):
    • Down Key: ARROW_DOWN(既定のまま)
    • Jump Key: SPACE(既定のまま)
    • Drop Duration: 0.25(0.2〜0.3 あたりが扱いやすいです)
    • Platform Group Index: 一方通行床に設定した Group のインデックス(例: 2
    • Require Grounded To Drop: true(接地中のみ床抜け可)
    • Min Down Hold Time: 0.05(誤操作を少し防ぐ程度)

5. 動作確認

  1. Scene を保存します。
  2. 上部ツールバーの Play ボタンを押してゲームを実行します。
  3. プレイヤーが一方通行床の上に乗るように配置しておきます(またはジャンプ等で乗れるようにしておきます)。
  4. テストの流れ:
    • プレイヤーを床の上に立たせる。
    • ↓キー(Down Key)を押し続ける
    • その状態で スペースキー(Jump Key)を押す
    • 設定が正しければ、プレイヤーが床をすり抜けて下に落下します。
    • 一定時間(Drop Duration)が経過すると、床との衝突が自動的に復帰するので、その後は再び床に乗れるようになります。
  5. もし床をすり抜けない場合:
    • 床の Collider2D の GroupplatformGroupIndex と一致しているか確認。
    • プレイヤーの Collider2D が正しく追加されているか確認。
    • Console に [PlatformDropper] からのログやエラーが出ていないか確認。
    • Project Settings > Physics > 2D で PhysicsSystem2D が有効になっているか確認。

まとめ

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

  • プレイヤーノードにアタッチするだけで、「下入力+ジャンプで一方通行床をすり抜ける」挙動を実現できる。
  • 入力キーや床抜け時間、対象となる PhysicsGroup などを すべてインスペクタから設定可能
  • 外部の GameManager やシングルトンに依存せず、このスクリプト単体で完結している。
  • Collider2D / RigidBody2D の存在チェックや PhysicsSystem2D の有効性チェックなど、防御的な実装になっている。

応用例としては:

  • プレイヤーキャラだけでなく、特定の敵キャラにだけ床抜けを許可する。
  • 一方通行床を複数のグループに分け、platformGroupIndex を切り替えることで、「この床だけすり抜け可能」といったギミックを作る。
  • 別の入力体系(ゲームパッドの下方向+ボタン)にマッピングしたい場合は、KeyCode の代わりにカスタム入力処理を追加するなど、コードを拡張する。

まずはこの記事のコードをそのまま貼り付けて動かし、期待通りに床抜けができることを確認してから、プロジェクトの入力仕様や物理設定に合わせてパラメータを調整してみてください。