【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
RigidBody2DとCollider2Dを取得し、存在しなければwarnで警告。- 元の
gravityScaleを保存しておき、後で復元できるようにしています。 - キーボード入力(KEY_DOWN / KEY_UP)と、Collider2D の接触イベントを登録します。
- update(deltaTime)
_isOnWallがfalseのときは、重力スケールを元に戻して終了。_isOnWallがtrueのときは、重力スケールを 0 にし、上下キー入力から垂直速度を決定します。enableHorizontalLockが ON の場合、水平速度を 0 に固定します。
- _onBeginContact / _onEndContact
- 接触した相手の
groupがwallGroupと一致する場合のみ、「壁」として扱います。 - 接触開始で
_isOnWall = true、接触終了で_isOnWall = false& 重力スケール復元。
- 接触した相手の
- _onKeyDown / _onKeyUp
- 押下中の上下キー状態を boolean で保持し、
update内で使用します。 - キーは
KeyCodeをインスペクタから変更可能なので、W/S 以外にも自由に割り当てられます。
- 押下中の上下キー状態を boolean で保持し、
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
WallClimber.tsにします。 - 作成された
WallClimber.tsをダブルクリックして開き、中身をすべて削除してから、上記のコードを丸ごと貼り付けて保存します。
2. 壁登りさせたいキャラクターノードの準備
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite などで、プレイヤー用ノード(例:
Player)を作成します。 - Player ノードに RigidBody2D を追加します。
- Player ノードを選択。
- Inspector の Add Component ボタンをクリック。
- Physics 2D → RigidBody2D を選択。
- Body Type は Dynamic のままで構いません。
- Player ノードに Collider2D を追加します。
- 再び Add Component → Physics 2D → BoxCollider2D など、形状に合った Collider2D を追加します。
- Sprite のサイズに合わせて
Sizeを調整しておきます。
この時点で、Player は重力で落下する通常の物理オブジェクトになっています。
3. 壁オブジェクトの準備
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite で、壁用ノード(例:
Wall)を作成します。 - Inspector で
Wallのスケールやサイズを調整し、縦長の長方形など「壁っぽい」形にします。 - Wall ノードに Collider2D を追加します。
- Wall ノードを選択。
- Add Component → Physics 2D → BoxCollider2D を追加。
- 壁として判定しやすくするため、専用のグループ(例: WALL) を作成することをおすすめします。
- メニューから Project → Project Settings → Group を開きます。
- 空いているスロットに
WALLと入力し、新しいグループを追加します。 - Wall ノードを選択し、Inspector 上部の Group ドロップダウンから
WALLを選択します。
4. WallClimber コンポーネントをアタッチ
- Hierarchy で Player ノード を選択します。
- Inspector の Add Component をクリック → Custom → WallClimber を選択します。
- 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(チェック)にしておくとコンソールに状態が表示されます。
- Climb Speed: 例として
5. 物理設定の確認
壁とプレイヤーが正しく接触判定されるように、Physics 2D の衝突マトリクスを確認します。
- メニューから Project → Project Settings → Physics 2D を開きます。
- Collision Matrix で、
DEFAULT(プレイヤーのグループ)とWALLの交差部分にチェックが入っていることを確認します。 - チェックが外れていると、接触イベントが発生せず壁登りが機能しません。
6. 再生して動作確認
- シーン内で Player を Wall の近くに配置し、少し離れた位置から落下して壁にぶつかるようにします。
- 画面上部の Play ボタンでゲームを実行します。
- Player が壁に接触した瞬間に、以下を確認します。
- 重力による落下が止まり、壁に「貼り付いた」ように見える。
- コンソールに
[WallClimber] 壁と接触開始のログが出る(Debug Log ON の場合)。
- 壁に接触している状態で、
- W キー を押すと、Player が壁に沿って上方向に移動する。
- S キー を押すと、Player が下方向に移動する。
- どちらのキーも離すと、その場に静止する。
- Player を壁の端まで移動させて、壁から離れた瞬間に
- 再び重力に従って落下する。
- コンソールに
[WallClimber] 壁との接触終了のログが出る(Debug Log ON の場合)。
これで、WallClimber をアタッチするだけで壁登り挙動が完成します。
まとめ
今回実装した WallClimber コンポーネントは、
- RigidBody2D と Collider2D を持つ任意のノードにアタッチするだけで、
- 外部の GameManager や入力管理スクリプトに一切依存せず、
- 「壁に接している間だけ重力を無効化し、上下移動を可能にする」挙動
を完結して提供します。
応用例としては、
- 壁だけでなく「ロープ」「はしご」用のグループを用意し、同じコンポーネントでロープ登りも実現する。
- キャラクターごとに
climbSpeedを変えて、俊敏なキャラ / 鈍重なキャラの差別化をする。 - 入力キーをキャラクターごとに変えて、2P 協力プレイ時の操作を分ける。
といった使い方が考えられます。
1ファイル・1コンポーネントで完結しているため、他のプロジェクトへの持ち運びも容易です。
このままベースとして、壁ジャンプやスタミナ制限などを追加していくことで、よりリッチなアクションゲームの挙動に発展させることもできます。




