【Cocos Creator】アタッチするだけ!ConveyorBelt (ベルトコンベア)の実装方法【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】ConveyorBelt(ベルトコンベア)の実装:アタッチするだけで「上に乗った物体を一定方向に運ぶ」汎用スクリプト

このガイドでは、ノードにアタッチするだけでその上に乗った物体を一定方向に流してくれる「ベルトコンベア」コンポーネントを実装します。
2Dアクションゲームの動く床や、工場ステージのベルトコンベアなど、「上に乗っている間だけオブジェクトを滑らせたい」場面でそのまま使える汎用スクリプトです。

外部の GameManager などには一切依存せず、このコンポーネント単体をノードにアタッチし、インスペクタから数値を調整するだけで動作するように設計します。


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

1. 機能要件の整理

  • このコンポーネントをアタッチしたノードは「ベルトコンベア」として振る舞う。
  • ベルトコンベア上に「乗っている」物体に対して、一定方向・一定強さの速度を加算する。
  • 対象は Rigidbody2D を持つノード とする(2D物理ベース)。
  • 「乗っている」判定は、Physics2D の接触イベント(BeginContact / EndContact)で行う。
  • ベルトコンベアの方向・速度・適用方法などは すべてインスペクタから設定可能にする。
  • このスクリプト単体で完結し、他のカスタムスクリプトには依存しない。

2. 必要な標準コンポーネントと防御的実装

このコンポーネントが正しく動作するためには、以下の標準コンポーネントが必要になります。

  • Physics2D の有効化: プロジェクト設定で 2D 物理が有効になっていること。
  • ベルト側ノード:
    • RigidBody2D(通常は Static または Kinematic)
    • Collider2D(BoxCollider2D など)
  • 乗る側(プレイヤーなど):
    • RigidBody2D
    • Collider2D

スクリプト内では、RigidBody2D / Collider2DgetComponent で取得し、存在しない場合は error ログを出して開発時に気づけるようにします。

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

ベルトコンベアとして汎用的に使えるよう、以下のプロパティを用意します。

  • direction : Vec2
    • ベルトが物体に与える「移動方向」。
    • 例: (1, 0) で右方向、(-1, 0) で左方向、(0, 1) で上方向。
    • 内部で正規化(normalize)して扱うので、長さは気にしなくてよい。
  • speed : number
    • ベルトの「速度」(単位: m/s 相当)。
    • 正の値で direction 方向に移動させる。
    • 例: 2.0 〜 5.0 程度が一般的なベルト速度。
  • forceMode : enumVelocity / AddVelocity
    • Velocity: 接触中の Rigidbody2D の linearVelocity を「ベルト方向成分だけ上書き」するイメージ。ベルトの上では常に同じ速度で流れる。
    • AddVelocity: 毎フレーム direction * speed を加算する。外力と合成されて加速するタイプのベルト。
  • maxAffectedSpeed : number
    • ベルトが与える速度の上限。0 以下なら「制限なし」。
    • AddVelocity モード時に、暴走的な加速を防ぐために使用。
  • onlyAffectTag : string
    • 特定のタグを持つノードだけをベルトの対象にしたい場合に使用。
    • 空文字の場合は「すべての Rigidbody2D を対象」とする。
    • タグは Node.nameNode.layer などの運用に合わせて自由に使えるが、ここでは簡単に node.name で判定する実装例を示す。
  • debugLog : boolean
    • 接触開始・終了や速度適用時に log を出すかどうか。
    • 挙動確認中だけ ON にし、普段は OFF にしておくとよい。

TypeScriptコードの実装

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


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

/**
 * ConveyorBelt
 * ベルトコンベア上に乗っている Rigidbody2D に対して、
 * 一定方向の速度を与える汎用コンポーネント。
 */
enum ConveyorForceMode {
    Velocity = 0,      // ベルト方向の速度を一定に保つ
    AddVelocity = 1,   // ベルト方向に速度を加算していく
}

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

    @property({
        tooltip: 'ベルトが物体に与える方向ベクトル。\n(1,0)=右, (-1,0)=左, (0,1)=上, (0,-1)=下。\n長さは自動で正規化されます。',
    })
    public direction: Vec2 = new Vec2(1, 0);

    @property({
        tooltip: 'ベルトの速度(強さ)。\n単位は m/s 相当。正の値で direction 方向に移動させます。',
        min: 0,
    })
    public speed: number = 2;

    @property({
        type: Number,
        tooltip: 'ベルトの速度の適用モード。\n0: Velocity - ベルト方向の速度を一定に保つ\n1: AddVelocity - ベルト方向に速度を加算する',
    })
    public forceMode: ConveyorForceMode = ConveyorForceMode.Velocity;

    @property({
        tooltip: 'ベルトが与える速度の上限(ベルト方向成分)。\n0 以下なら無制限。\nAddVelocity モード時の暴走防止に使用します。',
    })
    public maxAffectedSpeed: number = 0;

    @property({
        tooltip: 'この文字列と同じ name を持つノードだけを対象にします。\n空文字の場合は、すべての Rigidbody2D を対象とします。',
    })
    public onlyAffectTag: string = '';

    @property({
        tooltip: 'true にすると、接触開始・終了や速度適用時にログを出力します。',
    })
    public debugLog: boolean = false;

    // 内部で正規化して使う方向
    private _normalizedDir: Vec2 = new Vec2(1, 0);

    // 現在ベルト上に乗っている Rigidbody2D の集合
    private _rbsOnBelt: Set<RigidBody2D> = new Set<RigidBody2D>();

    // このノードの Collider2D(接触イベント取得用)
    private _collider: Collider2D | null = null;

    onLoad() {
        // 方向ベクトルを正規化(ゼロベクトル対策)
        if (this.direction.lengthSqr() <= 0.0001) {
            error('[ConveyorBelt] direction がゼロベクトルです。デフォルトの (1,0) を使用します。');
            this._normalizedDir.set(1, 0);
        } else {
            this._normalizedDir = this.direction.clone().normalize();
        }

        // Collider2D を取得
        this._collider = this.getComponent(Collider2D);
        if (!this._collider) {
            error('[ConveyorBelt] このノードに Collider2D がありません。ベルトコンベアとして機能させるには Collider2D を追加してください。');
            return;
        }

        // 接触イベントの登録
        this._collider.on('onBeginContact', this._onBeginContact, this);
        this._collider.on('onEndContact', this._onEndContact, this);

        if (this.debugLog) {
            log('[ConveyorBelt] onLoad: direction =', this._normalizedDir, 'speed =', this.speed);
        }
    }

    onDestroy() {
        // イベント登録解除
        if (this._collider) {
            this._collider.off('onBeginContact', this._onBeginContact, this);
            this._collider.off('onEndContact', this._onEndContact, this);
        }
        this._rbsOnBelt.clear();
    }

    /**
     * 接触開始イベント
     */
    private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        const rb = otherCollider.getComponent(RigidBody2D);
        if (!rb) {
            return;
        }

        // タグ(name)フィルタ
        if (this.onlyAffectTag && otherCollider.node.name !== this.onlyAffectTag) {
            return;
        }

        this._rbsOnBelt.add(rb);

        if (this.debugLog) {
            log('[ConveyorBelt] onBeginContact: node =', otherCollider.node.name);
        }
    }

    /**
     * 接触終了イベント
     */
    private _onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        const rb = otherCollider.getComponent(RigidBody2D);
        if (!rb) {
            return;
        }

        this._rbsOnBelt.delete(rb);

        if (this.debugLog) {
            log('[ConveyorBelt] onEndContact: node =', otherCollider.node.name);
        }
    }

    update(deltaTime: number) {
        if (this._rbsOnBelt.size === 0) {
            return;
        }

        // ベルト方向の速度ベクトル
        const beltVelocity = new Vec2(
            this._normalizedDir.x * this.speed,
            this._normalizedDir.y * this.speed
        );

        this._rbsOnBelt.forEach((rb) => {
            // すでに破棄されている場合などを防御的にチェック
            if (!rb.node || !rb.node.isValid) {
                return;
            }

            let v = rb.linearVelocity;

            if (this.forceMode === ConveyorForceMode.Velocity) {
                // --- Velocity モード ---
                // ベルト方向成分を beltVelocity に合わせる。
                // 他方向の速度(垂直方向など)は維持する。
                const dir = this._normalizedDir;
                const currentAlong = v.x * dir.x + v.y * dir.y; // 現在のベルト方向成分(スカラー)
                const targetAlong = this.speed;                 // 目標のベルト方向成分
                const diff = targetAlong - currentAlong;        // 足りない分

                // diff * dir を v に加算することで、ベルト方向の成分を targetAlong にする
                v = new Vec2(
                    v.x + diff * dir.x,
                    v.y + diff * dir.y
                );

                rb.linearVelocity = v;

                if (this.debugLog) {
                    log('[ConveyorBelt][Velocity] applied to', rb.node.name, 'velocity =', v);
                }
            } else {
                // --- AddVelocity モード ---
                // 毎フレーム beltVelocity を加算。
                v = new Vec2(
                    v.x + beltVelocity.x,
                    v.y + beltVelocity.y
                );

                // ベルト方向成分の速度上限が設定されている場合はクランプ
                if (this.maxAffectedSpeed > 0) {
                    const dir = this._normalizedDir;
                    const along = v.x * dir.x + v.y * dir.y; // ベルト方向成分
                    const max = this.maxAffectedSpeed;

                    if (along > max) {
                        // 超えた分を削る
                        const excess = along - max;
                        v = new Vec2(
                            v.x - excess * dir.x,
                            v.y - excess * dir.y
                        );
                    } else if (along < -max) {
                        // 逆方向にも制限をかけたい場合
                        const excess = along + max;
                        v = new Vec2(
                            v.x - excess * dir.x,
                            v.y - excess * dir.y
                        );
                    }
                }

                rb.linearVelocity = v;

                if (this.debugLog) {
                    log('[ConveyorBelt][AddVelocity] applied to', rb.node.name, 'velocity =', v);
                }
            }
        });
    }
}

コードのポイント解説

  • onLoad
    • direction を正規化し、ゼロベクトルだった場合は (1, 0) にフォールバック。
    • 同一ノード上から Collider2D を取得し、存在しなければ error ログを出す。
    • Collider2DonBeginContact / onEndContact にイベントハンドラを登録。
  • _onBeginContact / _onEndContact
    • 接触した相手の RigidBody2D を取得し、存在すれば Set に追加 / 削除。
    • onlyAffectTag が設定されている場合は、otherCollider.node.name でフィルタリング。
  • update
    • 接触中の Rigidbody2D それぞれに対して、forceMode に応じて速度を操作。
    • Velocity モード:
      • 現在の速度ベクトルを分解し、「ベルト方向成分」が speed になるように調整。
      • 垂直方向など、ベルトと直交する成分は維持されるため、ジャンプなどと共存しやすい。
    • AddVelocity モード:
      • 毎フレーム beltVelocity を加算し、ベルト方向に加速させる。
      • maxAffectedSpeed が正の値の場合、ベルト方向成分をその値以内にクランプ。
    • debugLog が true のとき、適用された速度をログ出力。

使用手順と動作確認

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

  1. エディタの Assets パネルで任意のフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択し、ファイル名を ConveyorBelt.ts にします。
  3. 作成された ConveyorBelt.ts をダブルクリックし、上記の TypeScript コードを丸ごと貼り付けて保存します。

2. ベルトコンベア用ノードの準備

  1. Hierarchy パネルで右クリックし、Create → 2D Object → Sprite などを選んで、ベルトの見た目用ノードを作成します。
    • ノード名の例: Conveyor
  2. 作成したノードを選択し、Inspector で以下のコンポーネントを追加・設定します。
    1. Add Component → Physics 2D → RigidBody2D
      • Type: Static または Kinematic を推奨(動かない床として扱う場合は Static)。
    2. Add Component → Physics 2D → BoxCollider2D など、コライダーを追加
      • ベルトの見た目に合わせて Size を調整します。
    3. Add Component → Custom → ConveyorBelt を追加します。

3. ConveyorBelt コンポーネントのプロパティ設定

ベルトノードの Inspector に表示される ConveyorBelt の各プロパティを設定します。

  • direction
    • 右に流したい場合: (1, 0)
    • 左に流したい場合: (-1, 0)
    • 上に流したい場合: (0, 1)
    • 下に流したい場合: (0, -1)
  • speed
    • 例: 25 程度から試してみてください。
  • forceMode
    • 0 (Velocity):
      • ベルトの上にいる間、常に一定の速度で流れてほしい場合。
    • 1 (AddVelocity):
      • ベルトが加速床のように徐々にスピードを乗せていく演出にしたい場合。
      • この場合は maxAffectedSpeed を 5〜10 などに設定しておくと暴走しにくいです。
  • maxAffectedSpeed
    • 0 のままなら制限なし。
    • 例: 5 にすると、ベルト方向の速度成分が ±5 を超えないように制御されます(AddVelocity モード時)。
  • onlyAffectTag
    • 特定のノード名だけを対象にしたい場合に設定します。
    • 例: プレイヤーのノード名が Player の場合、ここに Player と入力すると、プレイヤーだけがベルトの影響を受けます。
    • 空欄の場合は、すべての Rigidbody2D を持つノード が対象になります。
  • debugLog
    • 挙動確認中は true にすると、接触開始・終了や速度適用が Console に表示されて分かりやすくなります。
    • 本番では false にしておくことを推奨します。

4. テスト用プレイヤーノードの作成

ベルトの動作を確認するために、簡単なテスト用オブジェクトを用意します。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、プレイヤー用ノードを作成します。
    • ノード名の例: Player
  2. プレイヤーノードを選択し、Inspector で以下を追加します。
    1. Add Component → Physics 2D → RigidBody2D
      • Type: Dynamic を選択。
    2. Add Component → Physics 2D → BoxCollider2D などを追加し、プレイヤーの見た目に合わせてサイズを調整します。
  3. 必要に応じて、地面用の Static RigidBody2D + Collider2D を作成し、その上にベルトとプレイヤーを配置します。

5. 動作確認

  1. シーンビューで、Conveyor ノードの上に Player ノードを少し浮かせて配置します(落ちてきてベルトに乗るように)。
  2. メニューから Project → Project Settings → Physics 2D を開き、2D 物理が有効になっていることを確認します。
  3. エディタ右上の ▶︎(再生)ボタン でプレビューを開始します。
  4. プレイヤーが落下してベルトに乗ったタイミングで、設定した directionspeed に従って横方向(または指定した方向)に流れていれば成功です。
  5. forceMode を切り替えたり、speedmaxAffectedSpeed を変えたりして挙動の違いを確認してみてください。

まとめ

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

  • 任意のノードにアタッチするだけで「ベルトコンベア」や「動く床」を実現できる。
  • 方向・速度・適用モード・対象フィルタをすべてインスペクタから設定できる。
  • 他のカスタムスクリプトやシングルトンに一切依存しない単体完結型。

という特徴を持つ、汎用性の高い物理補助コンポーネントです。

応用例としては、

  • ステージ内に複数のベルトを配置し、それぞれ違う方向・速度でプレイヤーを運ぶギミック。
  • 敵やアイテムだけに影響するベルト(onlyAffectTag を活用)。
  • ジャンプ台や風エリアと組み合わせた、複雑な移動パズル。

などが考えられます。

このコンポーネントをベースに、「一定方向に力を加える風エリア」「水流」「エスカレーター」なども簡単に派生実装できるので、プロジェクトの物理ギミックをまとめて扱う基盤としても活用してみてください。

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