【Cocos Creator】アタッチするだけ!WallClimber (壁登り)の実装方法【TypeScript】

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

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

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

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

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

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

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

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

【Cocos Creator 3.8】WallClimberの実装:アタッチするだけで「壁にくっついてよじ登る」動きを実現する汎用スクリプト

このガイドでは、2Dアクションゲームでよくある「キャラクターが壁に接している間だけ、重力が消えて上下に移動できる」挙動を、1コンポーネントだけで実現する WallClimber を実装します。
Rigidbody2D と Collider2D を持つ任意のノードにアタッチするだけで、外部スクリプトや GameManager に一切依存せず、インスペクタからキー設定や移動速度を調整できるようにします。


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

実現したい挙動

  • 通常時は Rigidbody2D の重力によって落下する。
  • ノードが「壁」と接触している間だけ、
    • 重力スケールを 0 にして落下を止める。
    • 上下入力(キーボード)で、壁に沿って上昇・下降できる。
  • 壁から離れた瞬間に、重力スケールを元に戻して通常の挙動に戻る。

ここでいう「壁」は、指定したレイヤーの Collider2D を持つオブジェクトとして扱います。
Cocos Creator の 物理レイヤー(group) を利用し、インスペクタから「どのグループを壁とみなすか」を選べるようにします。

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

  • 入力は input.on を使い、このコンポーネント自身でキー入力を監視。
  • Rigidbody2D / Collider2D は getComponent で取得し、存在しなければエラーログを出して安全に動作停止。
  • 「元の重力スケール」を内部に保存し、壁から離れたときに必ず復元する。
  • 「壁との接触状態」は、Collider2D の onBeginContact / onEndContact(または同等のコールバック)を利用して管理する。

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

  • climbSpeed: number
    • 壁にくっついているときの上下移動速度(単位:m/s)。
    • 正の値のみ指定。上方向入力で +Y、下方向入力で -Y 方向に移動します。
    • 例: 3.0 ~ 6.0 程度が扱いやすいです。
  • upKey: KeyCode
    • 上昇に使うキー。
    • デフォルトは KeyCode.KEY_W
  • downKey: KeyCode
    • 下降に使うキー。
    • デフォルトは KeyCode.KEY_S
  • wallGroup: number
    • 壁として判定するグループ(物理レイヤー)
    • インスペクタ上では Group として表示され、DEFAULT / WALL などプロジェクトで定義したグループを選択可能。
    • ここで指定したグループの Collider2D と接触している間だけ「壁にくっついている」とみなします。
  • enableHorizontalLock: boolean
    • 壁登り中に、X方向の速度を 0 に固定するかどうか。
    • ON の場合、壁を登っている間は水平方向の速度を止め、垂直移動のみにします。
    • OFF の場合、他のスクリプトによる水平移動がそのまま残ります。
  • debugLog: boolean
    • 壁判定や重力切り替えのログをコンソールに出すかどうか。
    • 挙動確認時は ON、本番ビルドでは OFF 推奨。

これらのプロパティだけで、どのキーで登るか / どのレイヤーを壁とみなすか / どれくらいの速度で動くか をすべてインスペクタから完結して設定できます。


TypeScriptコードの実装


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

/**
 * 壁登りコンポーネント
 * - 壁(指定グループの Collider2D)に接触している間だけ重力を無効化し、
 *   上下キーで垂直移動できるようにする。
 * - 他のカスタムスクリプトには一切依存しない。
 */
@ccclass('WallClimber')
export class WallClimber extends Component {

    @property({
        tooltip: '壁にくっついている時の上下移動速度(m/s)。\n値が大きいほど速く移動します。'
    })
    public climbSpeed: number = 4.0;

    @property({
        tooltip: '上昇に使用するキー。',
    })
    public upKey: KeyCode = KeyCode.KEY_W;

    @property({
        tooltip: '下降に使用するキー。',
    })
    public downKey: KeyCode = KeyCode.KEY_S;

    @property({
        tooltip: '壁として判定するグループ(物理レイヤー)。\nここで選んだグループのCollider2Dと接触している間だけ壁登り状態になります。'
    })
    public wallGroup: number = 0; // デフォルト: DEFAULT グループ(プロジェクト設定に依存)

    @property({
        tooltip: '壁登り中に水平方向の速度を0に固定するかどうか。\nONにすると、壁に張り付いている間は上下移動のみになります。'
    })
    public enableHorizontalLock: boolean = true;

    @property({
        tooltip: 'デバッグログをコンソールに出力するかどうか。'
    })
    public debugLog: boolean = false;

    // 内部状態管理用
    private _rb2d: RigidBody2D | null = null;
    private _col2d: Collider2D | null = null;

    private _originalGravityScale: number = 1.0;
    private _isOnWall: boolean = false;

    // 入力状態
    private _upPressed: boolean = false;
    private _downPressed: boolean = false;

    onLoad() {
        // 必要なコンポーネントの取得
        this._rb2d = this.getComponent(RigidBody2D);
        this._col2d = this.getComponent(Collider2D);

        if (!this._rb2d) {
            warn('[WallClimber] このノードに RigidBody2D がありません。WallClimber を使用するには RigidBody2D を追加してください。');
        } else {
            this._originalGravityScale = this._rb2d.gravityScale;
        }

        if (!this._col2d) {
            warn('[WallClimber] このノードに Collider2D がありません。WallClimber を使用するには Collider2D(BoxCollider2D など)を追加してください。');
        }

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

        // 物理接触イベント登録(Collider2D が存在する場合のみ)
        if (this._col2d) {
            this._col2d.on('onBeginContact', this._onBeginContact, this);
            this._col2d.on('onEndContact', this._onEndContact, this);
        }
    }

    start() {
        if (this.debugLog) {
            log('[WallClimber] start: originalGravityScale =', this._originalGravityScale);
        }
    }

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

        // 物理イベント解除
        if (this._col2d) {
            this._col2d.off('onBeginContact', this._onBeginContact, this);
            this._col2d.off('onEndContact', this._onEndContact, this);
        }
    }

    update(deltaTime: number) {
        // 必要なコンポーネントがない場合は何もしない
        if (!this._rb2d) {
            return;
        }

        // 壁に接触していない場合は、重力を元に戻して終了
        if (!this._isOnWall) {
            if (this._rb2d.gravityScale !== this._originalGravityScale) {
                this._rb2d.gravityScale = this._originalGravityScale;
            }
            return;
        }

        // 壁に接触している間は重力を無効化
        if (this._rb2d.gravityScale !== 0) {
            if (this.debugLog) {
                log('[WallClimber] 壁登りモード: 重力を0に設定');
            }
            this._rb2d.gravityScale = 0;
        }

        // 垂直方向の入力から目標速度を計算
        let verticalDir = 0;
        if (this._upPressed) {
            verticalDir += 1;
        }
        if (this._downPressed) {
            verticalDir -= 1;
        }

        const v = this._rb2d.linearVelocity;

        // 壁登り中に水平ロックをするかどうか
        const vx = this.enableHorizontalLock ? 0 : v.x;
        const vy = verticalDir * this.climbSpeed;

        this._rb2d.linearVelocity = { x: vx, y: vy };
    }

    // ===== キー入力ハンドラ =====
    private _onKeyDown(event: EventKeyboard) {
        if (event.keyCode === this.upKey) {
            this._upPressed = true;
        } else if (event.keyCode === this.downKey) {
            this._downPressed = true;
        }
    }

    private _onKeyUp(event: EventKeyboard) {
        if (event.keyCode === this.upKey) {
            this._upPressed = false;
        } else if (event.keyCode === this.downKey) {
            this._downPressed = false;
        }
    }

    // ===== 物理接触ハンドラ =====
    private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        // 指定グループのオブジェクトとの接触のみ壁とみなす
        if (otherCollider.group === this.wallGroup) {
            this._isOnWall = true;
            if (this.debugLog) {
                log('[WallClimber] 壁と接触開始: group =', otherCollider.group);
            }
        }
    }

    private _onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        if (otherCollider.group === this.wallGroup) {
            this._isOnWall = false;

            // 壁から離れたので重力を元に戻す
            if (this._rb2d) {
                this._rb2d.gravityScale = this._originalGravityScale;
            }

            if (this.debugLog) {
                log('[WallClimber] 壁との接触終了: group =', otherCollider.group);
            }
        }
    }
}

コードのポイント解説

  • onLoad
    • RigidBody2DCollider2D を取得し、存在しなければ warn で警告。
    • 元の gravityScale を保存しておき、後で復元できるようにしています。
    • キーボード入力(KEY_DOWN / KEY_UP)と、Collider2D の接触イベントを登録します。
  • update(deltaTime)
    • _isOnWallfalse のときは、重力スケールを元に戻して終了。
    • _isOnWalltrue のときは、重力スケールを 0 にし、上下キー入力から垂直速度を決定します。
    • enableHorizontalLock が ON の場合、水平速度を 0 に固定します。
  • _onBeginContact / _onEndContact
    • 接触した相手の groupwallGroup と一致する場合のみ、「壁」として扱います。
    • 接触開始で _isOnWall = true、接触終了で _isOnWall = false & 重力スケール復元。
  • _onKeyDown / _onKeyUp
    • 押下中の上下キー状態を boolean で保持し、update 内で使用します。
    • キーは KeyCode をインスペクタから変更可能なので、W/S 以外にも自由に割り当てられます。

使用手順と動作確認

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

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. ファイル名を WallClimber.ts にします。
  3. 作成された WallClimber.ts をダブルクリックして開き、中身をすべて削除してから、上記のコードを丸ごと貼り付けて保存します。

2. 壁登りさせたいキャラクターノードの準備

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite などで、プレイヤー用ノード(例: Player)を作成します。
  2. Player ノードに RigidBody2D を追加します。
    1. Player ノードを選択。
    2. Inspector の Add Component ボタンをクリック。
    3. Physics 2D → RigidBody2D を選択。
    4. Body Type は Dynamic のままで構いません。
  3. Player ノードに Collider2D を追加します。
    1. 再び Add ComponentPhysics 2D → BoxCollider2D など、形状に合った Collider2D を追加します。
    2. Sprite のサイズに合わせて Size を調整しておきます。

この時点で、Player は重力で落下する通常の物理オブジェクトになっています。

3. 壁オブジェクトの準備

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite で、壁用ノード(例: Wall)を作成します。
  2. Inspector で Wall のスケールやサイズを調整し、縦長の長方形など「壁っぽい」形にします。
  3. Wall ノードに Collider2D を追加します。
    1. Wall ノードを選択。
    2. Add Component → Physics 2D → BoxCollider2D を追加。
  4. 壁として判定しやすくするため、専用のグループ(例: WALL) を作成することをおすすめします。
    1. メニューから Project → Project Settings → Group を開きます。
    2. 空いているスロットに WALL と入力し、新しいグループを追加します。
    3. Wall ノードを選択し、Inspector 上部の Group ドロップダウンから WALL を選択します。

4. WallClimber コンポーネントをアタッチ

  1. Hierarchy で Player ノード を選択します。
  2. Inspector の Add Component をクリック → CustomWallClimber を選択します。
  3. Inspector に表示される WallClimber の各プロパティを設定します。
    • Climb Speed: 例として 4.0 と入力。
    • Up Key: デフォルトの KEY_W のままでOK。
    • Down Key: デフォルトの KEY_S のままでOK。
    • Wall Group: 先ほど作成した WALL グループを選択。
    • Enable Horizontal Lock: ON(チェック)にすると、壁登り中は上下移動だけになります。
    • Debug Log: 挙動確認のため、最初は ON(チェック)にしておくとコンソールに状態が表示されます。

5. 物理設定の確認

壁とプレイヤーが正しく接触判定されるように、Physics 2D の衝突マトリクスを確認します。

  1. メニューから Project → Project Settings → Physics 2D を開きます。
  2. Collision Matrix で、DEFAULT(プレイヤーのグループ)と WALL の交差部分にチェックが入っていることを確認します。
  3. チェックが外れていると、接触イベントが発生せず壁登りが機能しません。

6. 再生して動作確認

  1. シーン内で Player を Wall の近くに配置し、少し離れた位置から落下して壁にぶつかるようにします。
  2. 画面上部の Play ボタンでゲームを実行します。
  3. Player が壁に接触した瞬間に、以下を確認します。
    • 重力による落下が止まり、壁に「貼り付いた」ように見える。
    • コンソールに [WallClimber] 壁と接触開始 のログが出る(Debug Log ON の場合)。
  4. 壁に接触している状態で、
    • W キー を押すと、Player が壁に沿って上方向に移動する。
    • S キー を押すと、Player が下方向に移動する。
    • どちらのキーも離すと、その場に静止する。
  5. Player を壁の端まで移動させて、壁から離れた瞬間に
    • 再び重力に従って落下する。
    • コンソールに [WallClimber] 壁との接触終了 のログが出る(Debug Log ON の場合)。

これで、WallClimber をアタッチするだけで壁登り挙動が完成します。


まとめ

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

  • RigidBody2D と Collider2D を持つ任意のノードにアタッチするだけで、
  • 外部の GameManager や入力管理スクリプトに一切依存せず、
  • 「壁に接している間だけ重力を無効化し、上下移動を可能にする」挙動

を完結して提供します。

応用例としては、

  • 壁だけでなく「ロープ」「はしご」用のグループを用意し、同じコンポーネントでロープ登りも実現する。
  • キャラクターごとに climbSpeed を変えて、俊敏なキャラ / 鈍重なキャラの差別化をする。
  • 入力キーをキャラクターごとに変えて、2P 協力プレイ時の操作を分ける。

といった使い方が考えられます。
1ファイル・1コンポーネントで完結しているため、他のプロジェクトへの持ち運びも容易です。
このままベースとして、壁ジャンプやスタミナ制限などを追加していくことで、よりリッチなアクションゲームの挙動に発展させることもできます。

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

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

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

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

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

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

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

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

URLをコピーしました!