【Cocos Creator】アタッチするだけ!GravityComponent (重力付与)の実装方法【TypeScript】

【Cocos Creator 3.8】GravityComponent の実装:アタッチするだけで「任意ノードに簡易重力挙動」を付与する汎用スクリプト

このガイドでは、任意のノードに「簡易な重力挙動」を与える汎用コンポーネントを実装します。
Rigidbody2D などの物理コンポーネントに依存せず、自前の velocity(速度ベクトル)を持ち、親ノードが空中にいる間だけ重力を加算して落下させるシンプルな挙動です。

プラットフォーマー、落下アニメーション、UI オブジェクトのふわっと落ちる演出など、「とりあえず重力で落ちる」挙動を素早く試したい場面で、ノードにアタッチしてパラメータを設定するだけで使えるように設計します。


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

1. 要件整理

  • このコンポーネント自身が velocity(速度)を内部で持つ
  • 毎フレーム velocity.y に重力を加算し、その速度でノードの位置を更新する。
  • 「親が空中にいる場合」だけ重力を適用する。
  • 他のカスタムスクリプトや GameManager に依存しない。
  • エディタのインスペクタから以下を調整できる:
    • 重力の強さ
    • 最大落下速度
    • 初期速度
    • 「空中判定」の基準となる高さ
    • 座標系の選択(親ローカル座標 or ワールド座標)
    • 動作の有効/無効

2. 「親が空中にいる」の設計

「地面コライダー」や「タイルマップ」などに依存せず、コンポーネント単体で完結させるために、ここでは次のように定義します。

  • 親ノードの Y 座標が、指定した「接地ライン」より高い(> groundY)場合を「空中」とみなす

この groundY はインスペクタから指定できるようにし、用途に応じて:

  • 親ノードのローカル Y = 0 を地面とみなす → useLocalPosition = true, groundY = 0
  • ワールド座標で指定の高さを地面とみなす → useLocalPosition = false, groundY = -300 など

これにより、外部の地面情報に依存せず「この高さより上にいたら空中」と判断できます。

3. インスペクタプロパティ設計

GravityComponent に用意するプロパティと役割は以下の通りです。

  • enabledGravity: boolean
    • 重力処理のオン/オフ。
    • テスト時に簡単に挙動を止めたいときなどに使用。
  • gravity: number
    • 1 秒あたりの加速度(単位: 単位/秒²)
    • 負の値で「下方向」に加速する想定(例: -1200)。
    • 正の値にすれば上向きの「浮力」的な挙動にも使える。
  • maxFallSpeed: number
    • 下方向への最大速度(負の値)。
    • 例: -2000 としておくと、それ以上速く落下しない。
    • 0 以下の値を推奨。0 にすると落下しなくなるので注意。
  • initialVelocityY: number
    • 開始時の縦方向速度。
    • 例: 300 とすると、最初は上に跳ね上がってから重力で落ちる。
  • useLocalPosition: boolean
    • true: 親ローカル座標で位置計算。
    • false: ワールド座標で位置計算。
    • UI や親オブジェクト内での相対移動なら true、シーン全体での高さ基準なら false が便利。
  • groundY: number
    • 「地面」とみなす Y 座標。
    • 親がこの値より上にいるときだけ重力を適用する。
    • 例:
      • ローカル座標で使う場合: 0
      • ワールド座標で地面が少し下にある場合: -200 など
  • stopOnGround: boolean
    • 親が groundY 以下に到達したときに、位置を groundY に固定し、速度を 0 にするかどうか
    • true の場合:
      • 親が groundY を下回ったら、Y 座標を groundY に戻し、velocityY を 0 にリセット。
      • その後は重力による移動が止まり、「地面に着地」した状態になる。
    • false の場合:
      • groundY を「空中判定の閾値」として使うだけで、地面で止めない。
  • debugLog: boolean
    • オンにすると、重力適用状態や位置・速度を console.log に出力。
    • 挙動確認やデバッグ用。リリース時はオフ推奨。

内部状態(インスペクタには出さない):

  • _velocityY: number
    • 現在の縦方向速度。

TypeScriptコードの実装

以下が完成した GravityComponent の実装コードです。


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

/**
 * GravityComponent
 * 親ノードが「指定した groundY より上にいる間」velocity.y に重力を加算し、
 * その速度に応じてノードの位置を更新する簡易重力コンポーネント。
 */
@ccclass('GravityComponent')
export class GravityComponent extends Component {

    @property({
        tooltip: '重力処理を有効にするかどうか。\nチェックを外すと一切の処理を行いません。'
    })
    public enabledGravity: boolean = true;

    @property({
        tooltip: '1秒あたりに加算される重力加速度(単位/秒^2)。\n通常は負の値で下方向に加速させます。\n例: -1200'
    })
    public gravity: number = -1200;

    @property({
        tooltip: '下方向(負のY)の最大落下速度。\nこれ以上の速さにはなりません。\n例: -2000'
    })
    public maxFallSpeed: number = -2000;

    @property({
        tooltip: '開始時の縦方向速度。\n正の値で上向きに打ち上がるような挙動になります。'
    })
    public initialVelocityY: number = 0;

    @property({
        tooltip: 'true: 親ノードのローカル座標で位置を更新します。\nfalse: ワールド座標で位置を更新します。'
    })
    public useLocalPosition: boolean = true;

    @property({
        tooltip: '「地面」とみなすY座標。\n親がこの値より上にいる間だけ重力を適用します。\nuseLocalPosition=true の場合はローカルY、false の場合はワールドYで解釈されます。'
    })
    public groundY: number = 0;

    @property({
        tooltip: '親が groundY 以下に到達したときに、\n位置を groundY に固定し、速度を0にするかどうか。'
    })
    public stopOnGround: boolean = true;

    @property({
        tooltip: 'オンにすると、毎フレームの位置や速度をコンソールに出力します(デバッグ用)。'
    })
    public debugLog: boolean = false;

    // 現在の縦方向速度(内部管理用)
    private _velocityY: number = 0;

    onLoad() {
        // 初期速度を設定
        this._velocityY = this.initialVelocityY;

        if (!this.node) {
            console.error('[GravityComponent] node が存在しません。コンポーネントのアタッチ先を確認してください。');
        }

        // maxFallSpeed の簡易チェック(正の値の場合は警告)
        if (this.maxFallSpeed > 0) {
            console.warn('[GravityComponent] maxFallSpeed は通常負の値を推奨します。現在の値:', this.maxFallSpeed);
        }

        if (this.debugLog) {
            console.log('[GravityComponent] onLoad: 初期化完了', {
                initialVelocityY: this.initialVelocityY,
                gravity: this.gravity,
                maxFallSpeed: this.maxFallSpeed,
                useLocalPosition: this.useLocalPosition,
                groundY: this.groundY,
                stopOnGround: this.stopOnGround,
            });
        }
    }

    start() {
        // ここでは特別な初期化は行わないが、
        // 将来的に「開始時に位置を補正する」などの処理を追加したい場合に使用できる。
    }

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

        const targetNode = this.node;
        if (!targetNode) {
            console.error('[GravityComponent] update 中に node が無効です。');
            return;
        }

        // 現在位置の取得
        const currentPos = this.useLocalPosition ? targetNode.position.clone() : targetNode.worldPosition.clone();
        const currentY = currentPos.y;

        // 親が「空中」にいるかどうかを判定
        const isInAir = currentY > this.groundY;

        if (isInAir) {
            // 空中にいる間は重力を適用
            this._applyGravity(deltaTime);
            // 位置の更新
            const newY = currentY + this._velocityY * deltaTime;
            currentPos.y = newY;

            if (this.useLocalPosition) {
                targetNode.setPosition(currentPos);
            } else {
                targetNode.setWorldPosition(currentPos);
            }

            if (this.debugLog) {
                console.log('[GravityComponent] InAir', {
                    y: currentY.toFixed(2),
                    newY: newY.toFixed(2),
                    velY: this._velocityY.toFixed(2),
                });
            }
        } else {
            // 空中ではない(groundY 以下)
            if (this.stopOnGround) {
                // 地面で停止させる
                if (currentY !== this.groundY) {
                    currentPos.y = this.groundY;
                    if (this.useLocalPosition) {
                        targetNode.setPosition(currentPos);
                    } else {
                        targetNode.setWorldPosition(currentPos);
                    }
                }
                // 速度を0にリセット
                if (this._velocityY !== 0) {
                    this._velocityY = 0;
                }

                if (this.debugLog) {
                    console.log('[GravityComponent] OnGround (stopped)', {
                        y: currentY.toFixed(2),
                        fixedY: this.groundY.toFixed(2),
                        velY: this._velocityY.toFixed(2),
                    });
                }
            } else {
                // stopOnGround = false の場合は、groundY を「空中判定の閾値」として
                // 使うだけで、地面で止める処理は行わない。
                if (this.debugLog) {
                    console.log('[GravityComponent] BelowGround (no stop)', {
                        y: currentY.toFixed(2),
                        velY: this._velocityY.toFixed(2),
                    });
                }
            }
        }
    }

    /**
     * 重力を速度に反映し、最大落下速度を制限する。
     */
    private _applyGravity(deltaTime: number) {
        // v = v + a * dt
        this._velocityY += this.gravity * deltaTime;

        // 最大落下速度の制限(gravity が負の前提で下方向が負)
        if (this.gravity 

コードのポイント解説

  • onLoad
    • initialVelocityY を内部変数 _velocityY にセット。
    • maxFallSpeed が正の場合に警告ログを出して、防御的にチェック。
    • debugLog が true なら初期設定をログ出力。
  • update(deltaTime)
    • enabledGravity が false のときは何もしない。
    • useLocalPosition に応じて node.positionnode.worldPosition を読み書き。
    • 現在の Y 座標と groundY を比較し、「空中かどうか」を判定。
    • 空中の場合:
      • _applyGravity_velocityY に重力を加算。
      • _velocityY * deltaTime だけ Y 座標を移動。
    • 空中でない場合:
      • stopOnGround が true なら、Y 座標を groundY に固定し、速度を 0 にリセット。
      • false なら何もしない(ただし空中判定にのみ groundY を利用)。
    • debugLog が true のときは、状態をコンソールに出力。
  • _applyGravity(deltaTime)
    • v = v + a * dt で速度に重力加速度を加算。
    • gravity < 0(下方向)を前提に、_velocityY < maxFallSpeed のとき _velocityYmaxFallSpeed にクランプ。
  • getVelocityY / setVelocityY
    • 将来、別コンポーネントからジャンプや吹き飛び演出を加えたいときに使えるユーティリティ。
    • このコンポーネント単体でも完結するが、拡張性を持たせるために用意。

使用手順と動作確認

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

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

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

まずはシンプルに、2D シーンで Sprite を落下させてみます。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択します。
  2. 作成された Sprite ノードの名前を FallingSprite などに変更しておきます。
  3. Canvas 配下に自動で作成されている場合はそのままで構いません。

3. GravityComponent をアタッチ

  1. Hierarchy で FallingSprite ノードを選択します。
  2. Inspector の下部にある Add Component ボタンをクリックします。
  3. Custom カテゴリの中にある GravityComponent を選択します。
    • 見つからない場合は、スクリプトの保存やビルドが終わっているか確認してください。

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

Inspector で GravityComponent の各プロパティを次のように設定してみます。

  • Enabled Gravity: チェック(オン)
  • Gravity: -1200
  • Max Fall Speed: -2000
  • Initial Velocity Y: 0
  • Use Local Position: チェック(true)
  • Ground Y: 0
  • Stop On Ground: チェック(true)
  • Debug Log: 必要に応じてチェック(オンにするとログが多く出ます)

次に、FallingSprite の初期位置を「空中」に置きます。

  1. FallingSprite を選択した状態で、Inspector の Position を確認します。
  2. Y300 など、Ground Y (0) より大きい値に設定します。
    • この状態だと、「親のローカルYが 0 より上」なので空中と判定されます。

5. シミュレーションで挙動を確認

  1. エディタ上部の Play(再生)ボタンを押して、Game ビューでシミュレーションを開始します。
  2. FallingSprite が上から下に向かって加速しながら落ち、Y = 0 の位置でピタッと止まることを確認します。
    • 落下が速すぎる/遅すぎる場合は、Gravity の値を調整してみてください(例: -600 など)。
    • 落下速度の上限を変えたい場合は、Max Fall Speed-1000 などに変更してみてください。

6. いくつかのバリエーションテスト

ケースA: 上方向に一度跳ね上がってから落ちる

  • Initial Velocity Y: 400
  • FallingSprite の初期 Y: 0

この設定で再生すると、最初に少し上に飛び上がり、その後重力で落下して Y=0 に戻ってきます。
ジャンプやポップアップ UI の簡易な演出に使えます。

ケースB: groundY をワールド座標で扱う

  • Use Local Position: チェックを外す(false)
  • Ground Y: -200
  • FallingSpriteワールド座標 Y: 200 くらいに配置

この場合、FallingSprite がワールド Y = -200 に到達したところで停止します。
親ノードの構造に関係なく、「画面全体での高さ」を基準にしたいときに便利です。

ケースC: 地面で止まらず、ずっと落下させる

  • Stop On Ground: チェックを外す(false)
  • その他の値はケースBと同様

この設定では、groundY は「空中判定」の閾値としてだけ使われ、
groundY を下回っても停止せず、ずっと落下し続けます
エフェクトや一度きりの落下演出など、「画面外まで落ちて消える」ような用途で使えます。


まとめ

この GravityComponent は、

  • Rigidbody2D や物理エンジンに頼らず、簡易な重力挙動だけを素早く付けたいとき
  • UI や 2D スプライトに「落下」「ふわっと浮く」といった演出を加えたいとき
  • ノード構造や他スクリプトに依存せず、アタッチするだけで完結するコンポーネントが欲しいとき

に特に有効です。

インスペクタから調整できるプロパティを豊富に用意したことで、

  • 重力の強さ・最大落下速度
  • 初期速度(ジャンプ/ポップ演出)
  • ローカル座標/ワールド座標の切り替え
  • groundY による「簡易な地面ライン」の定義
  • 地面で止めるかどうか(stopOnGround)

をコードを書き換えずに調整できます。

このような独立した汎用コンポーネントを積み重ねていくことで、
「とりあえず動かしてみる」「パラメータを触って気持ちよさを探る」といった試行が高速になり、
ゲーム全体の開発効率も大きく向上します。

この GravityComponent をベースに、横方向の速度(velocityX)や、
ジャンプ入力を受け付けるコンポーネントなどを組み合わせていくと、
シンプルな 2D アクションのプロトタイプもすぐに構築できるはずです。

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

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

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

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

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

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

URLをコピーしました!