【Cocos Creator】アタッチするだけ!FallingPlatform (落ちる床)の実装方法【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】FallingPlatform の実装:アタッチするだけで「乗ると揺れて落ちる床」を実現する汎用スクリプト

このガイドでは、任意の床(プラットフォーム)ノードにアタッチするだけで、プレイヤーなどのオブジェクトが乗ってから一定時間後に「揺れ始め → 落下(物理挙動)」する FallingPlatform コンポーネントを実装します。

プレイヤー側のスクリプトや専用 GameManager に依存せず、床ノード単体に完結したコンポーネントとして設計するため、どのプロジェクトにも簡単に持ち込んで再利用できます。


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

機能要件の整理

  • 床ノードに FallingPlatform をアタッチするだけで動作する。
  • 「誰か(RigidBody2D を持つ物体)」が床の上に乗ったときにカウント開始。
  • 乗ってから 0.5 秒後に床が小刻みに揺れ始める。
  • さらに 1 秒後(乗ってから合計 1.5 秒後)に床が落下を開始する。
  • 落下は 物理挙動(RigidBody2D の Dynamic 化)で行う。
  • 床に乗っているオブジェクトが離れた場合でも、一度カウントが始まったら中断せずに最後まで進む。
  • 外部スクリプトやシングルトンには一切依存しない。

必要な標準コンポーネント

このコンポーネントは以下の 2D 物理コンポーネントを利用します。

  • RigidBody2D:床の物理挙動(落下)を制御する。
  • Collider2D(BoxCollider2D 推奨):他オブジェクトが「乗った」ことを検知する。

防御的実装として、これらがアタッチされていない場合は onLoadgetComponent により取得を試み、見つからなければ error ログを出して動作を停止します。

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

コンポーネントを汎用的にするため、以下のようなパラメータを @property で公開します。

  • triggerTag (string)
    • 床に乗ったと判定するオブジェクトの「タグ」。
    • 空文字の場合は「タグを問わず、どのオブジェクトが乗っても反応」させる。
    • プレイヤーの Collider2D に設定した tag と一致させることで、特定のオブジェクトのみに反応させられる。
  • delayBeforeShake (number)
    • 乗ってから揺れ始めるまでの時間(秒)。
    • デフォルト:0.5 秒。
  • delayBeforeFall (number)
    • 揺れ始めてから落下開始までの時間(秒)。
    • デフォルト:1.0 秒。
  • shakeAngle (number)
    • 揺れの最大角度(度)。
    • この角度を中心に左右に振動する。
    • デフォルト:5 度。
  • shakeFrequency (number)
    • 揺れの周波数(1 秒間に何回揺れるか)。
    • デフォルト:20(かなり細かく震える感じ)。
  • autoDisableColliderOnFall (boolean)
    • 落下開始時に床の Collider2D を無効化するかどうか。
    • 有効にすると、床が落ちた後にプレイヤーが引っかからなくなる。
    • デフォルト:true
  • destroyAfterFall (boolean)
    • 落下開始後、一定時間経過したら床ノードを自動で破棄するかどうか。
    • ステージから消えてほしい場合に有効。
    • デフォルト:false
  • destroyDelay (number)
    • 落下開始からノード破棄までの時間(秒)。
    • destroyAfterFalltrue のときのみ有効。
    • デフォルト:3.0 秒。

また、物理挙動制御のために以下の内部状態を持ちます(インスペクタには出さない)。

  • 初期の RigidBody2D.type(Static など)
  • 初期の回転(Node.eulerAngles
  • 現在の状態(待機中 / カウント中 / 揺れ中 / 落下済み)
  • 経過時間カウンタ

TypeScriptコードの実装


import { _decorator, Component, Node, RigidBody2D, ERigidBody2DType, Collider2D, IPhysics2DContact, Contact2DType, Vec3, math, warn, error, tween } from 'cc';
const { ccclass, property } = _decorator;

/**
 * FallingPlatform
 * 乗ってから一定時間後に揺れ始め、さらに一定時間後に落下する床コンポーネント
 */
@ccclass('FallingPlatform')
export class FallingPlatform extends Component {

    @property({
        tooltip: 'このタグを持つ Collider2D が乗ったときに作動します。\n空文字の場合はタグを問わず反応します。'
    })
    public triggerTag: string = '';

    @property({
        tooltip: '乗ってから揺れ始めるまでの時間(秒)。'
    })
    public delayBeforeShake: number = 0.5;

    @property({
        tooltip: '揺れ始めてから落下開始までの時間(秒)。'
    })
    public delayBeforeFall: number = 1.0;

    @property({
        tooltip: '揺れの最大角度(度)。この角度を中心に左右に振動します。'
    })
    public shakeAngle: number = 5;

    @property({
        tooltip: '揺れの周波数(1秒あたりの振動回数)。'
    })
    public shakeFrequency: number = 20;

    @property({
        tooltip: '落下開始時にこのノードの Collider2D を無効化するかどうか。'
    })
    public autoDisableColliderOnFall: boolean = true;

    @property({
        tooltip: '落下開始後、一定時間でノードを自動破棄するかどうか。'
    })
    public destroyAfterFall: boolean = false;

    @property({
        tooltip: '落下開始からノードを破棄するまでの時間(秒)。\n「自動破棄」が有効な場合のみ使用されます。'
    })
    public destroyDelay: number = 3.0;

    // 内部用プロパティ
    private _rigidBody: RigidBody2D | null = null;
    private _collider: Collider2D | null = null;

    private _originalBodyType: ERigidBody2DType | null = null;
    private _originalRotation: Vec3 = new Vec3();

    private _isTriggered: boolean = false;
    private _isShaking: boolean = false;
    private _hasFallen: boolean = false;

    private _elapsedSinceTrigger: number = 0;

    onLoad() {
        // 必要なコンポーネントを取得
        this._rigidBody = this.getComponent(RigidBody2D);
        if (!this._rigidBody) {
            error('[FallingPlatform] このノードに RigidBody2D がアタッチされていません。動作しません。');
            return;
        }

        this._collider = this.getComponent(Collider2D);
        if (!this._collider) {
            error('[FallingPlatform] このノードに Collider2D (例: BoxCollider2D) がアタッチされていません。動作しません。');
            return;
        }

        // 初期設定を記録
        this._originalBodyType = this._rigidBody.type;
        this._originalRotation.set(this.node.eulerAngles);

        // 初期状態では落下しないように Static か Kinematic を推奨
        // ここでは、ユーザー設定を尊重し、type は変更しない。
        // ただし Dynamic の場合、落ちないようにすることはできないので警告を出す。
        if (this._rigidBody.type === ERigidBody2DType.Dynamic) {
            warn('[FallingPlatform] RigidBody2D のタイプが Dynamic に設定されています。開始時から落下する可能性があります。通常は Static か Kinematic を推奨します。');
        }

        // 衝突コールバック登録
        this._collider.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
    }

    start() {
        // パラメータの防御的チェック
        if (this.delayBeforeShake < 0) {
            warn('[FallingPlatform] delayBeforeShake が負の値です。0 に補正します。');
            this.delayBeforeShake = 0;
        }
        if (this.delayBeforeFall < 0) {
            warn('[FallingPlatform] delayBeforeFall が負の値です。0 に補正します。');
            this.delayBeforeFall = 0;
        }
        if (this.destroyDelay < 0) {
            warn('[FallingPlatform] destroyDelay が負の値です。0 に補正します。');
            this.destroyDelay = 0;
        }
    }

    update(deltaTime: number) {
        if (!this._rigidBody || !this._collider) {
            return;
        }

        // 既に落下済みなら何もしない
        if (this._hasFallen) {
            return;
        }

        if (this._isTriggered) {
            this._elapsedSinceTrigger += deltaTime;

            // 揺れ開始判定
            if (!this._isShaking && this._elapsedSinceTrigger >= this.delayBeforeShake) {
                this._startShaking();
            }

            // 落下開始判定
            const totalDelayBeforeFall = this.delayBeforeShake + this.delayBeforeFall;
            if (this._elapsedSinceTrigger >= totalDelayBeforeFall) {
                this._startFalling();
            }
        }

        // 揺れ中は角度を更新
        if (this._isShaking && !this._hasFallen) {
            this._updateShake();
        }
    }

    private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        if (this._isTriggered) {
            // 既にトリガー済みなら無視
            return;
        }

        // タグによるフィルタリング
        if (this.triggerTag !== '') {
            if (otherCollider.tag.toString() !== this.triggerTag) {
                // タグが一致しない場合は無視
                return;
            }
        }

        // ここでは「上から乗ったかどうか」の厳密な判定は行わず、
        // 指定タグ(または任意オブジェクト)との接触でトリガーとする。
        this._isTriggered = true;
        this._elapsedSinceTrigger = 0;

        // 念のため、初期回転を保持
        this._originalRotation.set(this.node.eulerAngles);
    }

    private _startShaking() {
        if (this._isShaking) {
            return;
        }
        this._isShaking = true;
    }

    private _startFalling() {
        if (this._hasFallen) {
            return;
        }
        this._hasFallen = true;
        this._isShaking = false;

        // 回転を初期値に戻す(揺れをリセット)
        this.node.eulerAngles = this._originalRotation.clone();

        // Rigidbody を Dynamic にして物理落下させる
        if (this._rigidBody) {
            this._rigidBody.type = ERigidBody2DType.Dynamic;
        }

        // コライダを無効化して、落下中にプレイヤーが引っかからないようにする(オプション)
        if (this.autoDisableColliderOnFall && this._collider) {
            this._collider.enabled = false;
        }

        // 自動破棄(オプション)
        if (this.destroyAfterFall) {
            tween(this.node)
                .delay(this.destroyDelay)
                .call(() => {
                    if (this.node && this.node.isValid) {
                        this.node.destroy();
                    }
                })
                .start();
        }
    }

    private _updateShake() {
        // 経過時間から揺れ角度を算出する
        // 角速度 = 2π * 周波数
        const t = this._elapsedSinceTrigger - this.delayBeforeShake;
        const omega = 2 * Math.PI * this.shakeFrequency;
        const angleOffset = Math.sin(omega * t) * this.shakeAngle;

        const newEuler = this._originalRotation.clone();
        // Z軸回転のみを揺らす(2Dなので)
        newEuler.z += angleOffset;
        this.node.eulerAngles = newEuler;
    }

    onDestroy() {
        // イベント登録解除
        if (this._collider) {
            this._collider.off(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
        }
    }
}

主要な処理の解説

  • onLoad
    • RigidBody2DCollider2D を取得し、存在しなければ error ログを出して以降の処理を事実上無効にします。
    • 初期の RigidBody2D.typenode.eulerAngles を保存します。
    • Collider2DBEGIN_CONTACT イベントに _onBeginContact を登録します。
  • start
    • タイミング系プロパティが負の値になっていた場合、0 に補正して警告を出します。
  • _onBeginContact
    • 誰かが床に接触したときに呼ばれます。
    • triggerTag が設定されている場合、そのタグと一致する otherCollider.tag のときだけトリガーします。
    • トリガー済みでなければ _isTriggered = true とし、経過時間カウンタをリセットします。
  • update
    • トリガー後の経過時間を積算し、delayBeforeShake を過ぎたら _startShaking() を呼びます。
    • delayBeforeShake + delayBeforeFall を過ぎたら _startFalling() を呼びます。
    • 揺れ中であれば _updateShake() でフレームごとに角度を更新します。
  • _updateShake
    • シンプルなサイン波(sin)を使って Z 軸回転を揺らしています。
    • shakeFrequency により 1 秒あたりの揺れ回数、shakeAngle により最大振れ角を制御します。
  • _startFalling
    • 揺れを停止し、回転を初期角度に戻します。
    • RigidBody2D.typeDynamic に変えて物理落下を開始します。
    • autoDisableColliderOnFall が有効なら Collider2D.enabled = false にして、落下中にプレイヤーが引っかからないようにします。
    • destroyAfterFall が有効なら、tween を使って destroyDelay 秒後に node.destroy() します。

使用手順と動作確認

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

  1. エディタの Assets パネルで任意のフォルダ(例:assets/scripts)を右クリックします。
  2. Create > TypeScript を選択し、ファイル名を FallingPlatform.ts にします。
  3. 作成された FallingPlatform.ts をダブルクリックしてエディタで開き、先ほどのコード全文を貼り付けて保存します。

2. テスト用の床ノードを作成

  1. Hierarchy パネルで右クリックし、Create > 2D Object > Sprite を選択します。
    • 名前を FallingPlatformTest などに変更しておきます。
  2. 作成したノードを選択し、Inspector で以下を設定します。
    • Sprite の SpriteFrame に適当な床画像を設定するか、単色でも構いません。
    • サイズ(ContentSize)をお好みの床サイズに調整します。

3. 物理コンポーネントの追加

FallingPlatform が正しく動作するために、床ノードには RigidBody2DCollider2D が必要です。

  1. 床ノード(FallingPlatformTest)を選択した状態で、Inspector > Add Component をクリックします。
  2. Physics 2D > RigidBody2D を追加します。
    • TypeStatic または Kinematic を推奨します(初期状態で落ちないようにするため)。
  3. 再度 Add Component をクリックし、Physics 2D > BoxCollider2D を追加します。
    • Size を床スプライトの大きさに合わせて調整してください。
    • プレイヤーのタグで反応させたい場合は Tag に任意の数値(例:1)を設定し、後でプレイヤー側の Collider2D にも同じタグを設定します。

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

  1. 床ノードを選択したまま、Inspector > Add Component > Custom > FallingPlatform を選択して追加します。
  2. Inspector で FallingPlatform の各プロパティを設定します。
    • Trigger Tag
      • プレイヤーの Collider2D.tag1 にしている場合、ここも 1 と入力します(文字列ですが数値をそのまま書いて構いません)。
      • 「誰が乗っても落ちてよい」場合は空のままにしておきます。
    • Delay Before Shake0.5(仕様通り 0.5 秒後に揺れ開始)。
    • Delay Before Fall1.0(揺れ始めてから 1 秒後に落下)。
    • Shake Angle5(揺れが小さすぎる場合は 10〜15 に上げてみてください)。
    • Shake Frequency20(細かく震える感じ。ゆっくり揺らしたいなら 5〜10 に)。
    • Auto Disable Collider On Falltrue(落下後に床に引っかからないようにする)。
    • Destroy After Fall:テスト時は false のままで構いません。
    • Destroy Delay3.0Destroy After Fall を有効にしたときに使用されます)。

5. プレイヤー(またはテスト用オブジェクト)の準備

すでにプレイヤーキャラクターがいる場合は、そのノードに RigidBody2DCollider2D が付いていることを確認し、必要なら tag を設定してください。

簡易テストだけしたい場合は、次のようにします。

  1. Hierarchy で右クリック → Create > 2D Object > Sprite を選択し、TestPlayer などの名前を付けます。
  2. Inspector で Add Component > Physics 2D > RigidBody2D を追加し、TypeDynamic に設定します。
  3. 同じく Add Component > Physics 2D > CircleCollider2D などを追加し、サイズを調整します。
  4. 床の上に乗るように、TestPlayer の位置を少し上(Y 座標を高く)に配置します。
  5. 床の FallingPlatform.triggerTag に何も入れていない場合はそのままで OK です。
    • タグで制限したい場合は、プレイヤー側の Collider2D の Tag1 にし、床側の Trigger Tag にも 1 を入力します。

6. 再生して動作確認

  1. エディタ右上の Play ボタンを押してシミュレーションを開始します。
  2. プレイヤー(またはテストオブジェクト)が床に落ちて接触すると、内部的にカウントが始まります。
  3. 約 0.5 秒後:床が小刻みに揺れ始めるのを確認します。
  4. さらに 1 秒後(合計 1.5 秒後):床の RigidBody2D.typeDynamic になり、重力で落下し始めます。
  5. Auto Disable Collider On Falltrue の場合、落下後は床に乗れなくなります。
  6. Destroy After Falltrue にしている場合、Destroy Delay 秒後に床ノードが自動で削除されます。

揺れが目立たない場合は Shake Angle を 10〜15 程度に上げてみてください。揺れが速すぎる / 遅すぎる場合は Shake Frequency を調整します。


まとめ

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

  • 床ノードにアタッチ
  • RigidBody2D と Collider2D を付ける
  • 必要なら triggerTag を合わせる

という最小限の手順だけで、「乗ってしばらくすると揺れて落ちる床」を実現できます。外部の GameManager やプレイヤースクリプトに依存しないため、

  • 別プロジェクトへのコピペ導入
  • ステージエディタ的なシーン上での量産
  • プロトタイプ制作時の素早いギミック追加

といった場面で非常に扱いやすくなっています。

応用としては、

  • タグを「敵専用」にして、「敵が乗ったときだけ崩れる床」にする。
  • destroyAfterFall を有効にして、落ちた床が一定時間後に消える「一度きりの足場」を作る。
  • 揺れの角度や周波数を変えたプリセットを複数用意し、ステージごとにバリエーションを持たせる。

といった拡張も簡単です。プロパティだけで挙動を調整できる「独立コンポーネント」として設計しておくことで、ゲーム全体の設計に縛られず、必要な場所に必要なギミックを素早く追加できるようになります。

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