【Cocos Creator 3.8】JumpGap(穴飛び越え)の実装:アタッチするだけで「足元の穴をRayCastで検知して自動ジャンプ」する汎用スクリプト
このコンポーネントは、横スクロール系のアクションやランゲームで「キャラが自動で前進し、足元に穴があるときだけ自動ジャンプする」挙動を簡単に実現するためのものです。
プレイヤーキャラなどのノードに JumpGap をアタッチし、いくつかのパラメータを設定するだけで、移動先の床をRayCastで検知し、床が途切れている場合にジャンプを実行します。
コンポーネントの設計方針
1. 機能要件の整理
- ノード(プレイヤーなど)は一定速度で水平方向に移動する。
- ノードの「進行方向の足元」にRayCastを飛ばし、床(コライダー)があるかどうかを調べる。
- 一定距離先の足元に床が「ない」と判定された場合、ジャンプを実行する。
- ジャンプは
RigidBody2Dに対して上向きの速度を与えることで実現する。 - ジャンプ中は連続ジャンプしないように、接地判定またはクールタイムで制御する。
- 外部スクリプトには一切依存せず、このコンポーネント単体で完結する。
2. 使用する標準コンポーネント
このコンポーネントは、以下の標準コンポーネントを利用します。
RigidBody2D(必須): 水平移動とジャンプの物理挙動を制御。Collider2D(推奨): 地面との当たり判定用。接地判定に利用。PhysicsSystem2D(グローバル): RayCastによる床検知に使用。
防御的実装として、RigidBody2D や Collider2D が見つからない場合は error ログを出し、挙動を止めるようにします。
3. インスペクタで設定可能なプロパティ設計
JumpGap コンポーネントでインスペクタから調整できるプロパティを定義します。
- moveSpeed: number
- 水平方向の移動速度(単位: m/s)。
- 正の値で右方向、負の値で左方向に移動します。
- 例: 3 ~ 8 程度。
- autoMove: boolean
trueの場合、自動で水平移動します。falseの場合、水平移動は行わず「穴検知とジャンプ処理のみ」行います(外部で速度を与えている場合などに使用)。
- jumpVelocity: number
- ジャンプ時に上方向へ与える速度(単位: m/s)。
- 例: 8 ~ 15 程度。重力設定に合わせて調整。
- raycastDistance: number
- 足元の「どれくらい先」までRayCastで床をチェックするか(単位: m)。
- 進行方向に向けてこの距離だけ先の足元を調べます。
- 値が大きいほど早めにジャンプし、小さいほどギリギリでジャンプします。
- 例: 1.0 ~ 3.0。
- raycastDownOffset: number
- RayCastの開始位置をノードの中心からどれだけ下げるか(単位: m)。
- キャラの足元あたりからRayを飛ばしたい場合に使用します。
- 例: 0.3 ~ 0.6(キャラの高さに応じて)。
- raycastDepth: number
- RayCastをどれくらい下方向へ伸ばすか(単位: m)。
- 地面の厚みや段差に合わせて調整します。
- 例: 0.5 ~ 2.0。
- groundLayerMask: number
- RayCastで「地面」として判定するレイヤーのビットマスク。
- デフォルトで
0xffffffff(すべてのレイヤー)にしておき、必要に応じて変更します。
- checkInterval: number
- 穴検知を行う間隔(単位: 秒)。
- 毎フレームRayCastするとコストが高くなるため、0.02~0.1秒おきなどに制限します。
- 例: 0.05。
- minJumpCooldown: number
- ジャンプ後、次のジャンプまでの最低待機時間(単位: 秒)。
- 多段ジャンプを防ぎたい場合に使用。接地判定と合わせて制御します。
- 例: 0.2 ~ 0.5。
- requireGroundedToJump: boolean
trueの場合、「接地状態」のときだけジャンプを許可します。falseの場合、空中でもクールタイムさえ過ぎていればジャンプ可能(特殊な挙動用)。
- debugDraw: boolean
trueの場合、RayCastのラインと判定結果をdebugログに出します。- 挙動確認や調整時に使用し、完成後は
falseにしておくことを推奨します。
TypeScriptコードの実装
import {
_decorator,
Component,
Node,
RigidBody2D,
Collider2D,
Contact2DType,
IPhysics2DContact,
PhysicsSystem2D,
Vec2,
Vec3,
director,
macro
} from 'cc';
const { ccclass, property } = _decorator;
/**
* JumpGap
* 足元の一定距離先に穴がある場合、自動でジャンプするコンポーネント
* - RigidBody2D を利用して水平移動とジャンプを制御
* - PhysicsSystem2D.raycast を使って地面(コライダー)の有無を検知
*/
@ccclass('JumpGap')
export class JumpGap extends Component {
@property({
tooltip: '水平方向の移動速度(m/s)。正の値で右方向、負の値で左方向に移動します。'
})
public moveSpeed: number = 4;
@property({
tooltip: 'true の場合、自動で水平移動します。false の場合は外部から速度を与える想定です。'
})
public autoMove: boolean = true;
@property({
tooltip: 'ジャンプ時に上方向へ与える速度(m/s)。重力設定に合わせて調整してください。'
})
public jumpVelocity: number = 10;
@property({
tooltip: '進行方向の足元をチェックする距離(m)。大きいほど早めにジャンプします。'
})
public raycastDistance: number = 2;
@property({
tooltip: 'RayCast開始位置をノードの中心からどれだけ下げるか(m)。キャラの足元付近に設定します。'
})
public raycastDownOffset: number = 0.5;
@property({
tooltip: 'RayCastを下方向へ伸ばす長さ(m)。地面の厚みや段差に合わせて調整します。'
})
public raycastDepth: number = 1.5;
@property({
tooltip: '地面として判定するレイヤーのビットマスク。デフォルトは全レイヤーを対象。'
})
public groundLayerMask: number = 0xffffffff;
@property({
tooltip: '穴検知を行う間隔(秒)。毎フレーム行うと重い場合があるため、0.02~0.1程度を推奨。'
})
public checkInterval: number = 0.05;
@property({
tooltip: 'ジャンプ後、次のジャンプまでの最低待機時間(秒)。多段ジャンプ防止に使用します。'
})
public minJumpCooldown: number = 0.25;
@property({
tooltip: 'true の場合、接地しているときのみジャンプを許可します。'
})
public requireGroundedToJump: boolean = true;
@property({
tooltip: 'true の場合、RayCastラインや判定結果をログ出力します(開発・デバッグ用)。'
})
public debugDraw: boolean = false;
private _rigidBody: RigidBody2D | null = null;
private _collider: Collider2D | null = null;
private _isGrounded: boolean = false;
private _lastJumpTime: number = -999;
private _elapsedCheckTime: number = 0;
// Cocosの時間取得用(director.getTotalTimeはms単位)
private get _time(): number {
return director.getTotalTime() / 1000;
}
onLoad() {
// 必須コンポーネントの取得
this._rigidBody = this.getComponent(RigidBody2D);
if (!this._rigidBody) {
console.error('[JumpGap] RigidBody2D がアタッチされていません。このノードに RigidBody2D を追加してください。');
}
// 接地判定用に Collider2D を取得(任意だが強く推奨)
this._collider = this.getComponent(Collider2D);
if (!this._collider) {
console.warn('[JumpGap] Collider2D が見つかりません。接地判定が正しく行えない可能性があります。地面との当たり判定用に Collider2D を追加することを推奨します。');
} else {
// 衝突開始・終了イベントで接地状態を管理
this._collider.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
this._collider.on(Contact2DType.END_CONTACT, this._onEndContact, this);
}
// 2D物理システムの有効化を確認
if (!PhysicsSystem2D.instance.enable) {
console.warn('[JumpGap] PhysicsSystem2D が無効になっています。Project Settings の Physics で 2D Physics を有効にしてください。');
}
}
onDestroy() {
if (this._collider) {
this._collider.off(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
this._collider.off(Contact2DType.END_CONTACT, this._onEndContact, this);
}
}
start() {
// 初期状態で接地しているかどうかは、最初の接触イベントで更新されます。
}
update(deltaTime: number) {
if (!this._rigidBody) {
return;
}
// 自動移動処理
if (this.autoMove) {
const v = this._rigidBody.linearVelocity;
v.x = this.moveSpeed;
this._rigidBody.linearVelocity = v;
}
// 一定間隔ごとに穴チェック
this._elapsedCheckTime += deltaTime;
if (this._elapsedCheckTime < this.checkInterval) {
return;
}
this._elapsedCheckTime = 0;
this._checkAndJumpIfNeeded();
}
/**
* 接地状態の更新(接触開始)
*/
private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
// 非常にシンプルに「何かに触れていれば接地」とみなす
this._isGrounded = true;
}
/**
* 接地状態の更新(接触終了)
*/
private _onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
// すべての接触が終わったときに false になるよう、
// 実際には接触中の数をカウントするのがより厳密だが、
// 汎用コンポーネントとしては簡易実装に留める。
this._isGrounded = false;
}
/**
* 穴があるかどうかをRayCastでチェックし、必要ならジャンプする。
*/
private _checkAndJumpIfNeeded() {
if (!this._rigidBody) {
return;
}
const now = this._time;
const timeSinceLastJump = now - this._lastJumpTime;
// クールタイム中なら何もしない
if (timeSinceLastJump < this.minJumpCooldown) {
return;
}
// 接地が必須なら、接地していない場合は何もしない
if (this.requireGroundedToJump && !this._isGrounded) {
return;
}
// 進行方向(moveSpeedの符号)を決定
const direction = this.moveSpeed >= 0 ? 1 : -1;
// ノードのワールド座標を取得
const worldPos = this.node.worldPosition.clone();
// RayCast開始位置(足元付近)
const start = new Vec2(
worldPos.x,
worldPos.y - this.raycastDownOffset
);
// 進行方向 + 下方向にraycastDistanceだけ進んだ位置の真下を調べたいので、
// 「少し前方にずらした位置」から「下方向に raycastDepth」分伸ばす Ray を飛ばす。
const forwardOffset = this.raycastDistance * direction;
const end = new Vec2(
worldPos.x + forwardOffset,
worldPos.y - this.raycastDownOffset - this.raycastDepth
);
// RayCast実行
const results = PhysicsSystem2D.instance.raycast(
start,
end,
// RayCastType.Closest 相当(Cocos Creator 3.8 では第3引数はオプションだが、
// ここでは明示せずデフォルトの振る舞いを利用する)
null,
this.groundLayerMask
);
const hasGround = results.length > 0;
if (this.debugDraw) {
console.log(
`[JumpGap] RayCast from (${start.x.toFixed(2)}, ${start.y.toFixed(2)})` +
` to (${end.x.toFixed(2)}, ${end.y.toFixed(2)})` +
` - hasGround: ${hasGround}`
);
}
// 床が「ない」場合にジャンプ
if (!hasGround) {
this._doJump();
}
}
/**
* 実際にジャンプを行う。
*/
private _doJump() {
if (!this._rigidBody) {
return;
}
const v = this._rigidBody.linearVelocity;
v.y = this.jumpVelocity;
this._rigidBody.linearVelocity = v;
this._lastJumpTime = this._time;
if (this.debugDraw) {
console.log('[JumpGap] Jump executed. velocity.y =', this.jumpVelocity);
}
}
}
コードの主要部分の解説
- onLoad()
RigidBody2Dを取得し、存在しない場合はerrorログを出して処理を継続しないようにしています。Collider2Dがあれば接触イベントを購読し、_isGroundedフラグを更新します。PhysicsSystem2Dが無効な場合はwarnを出して注意喚起します。
- update(deltaTime)
autoMoveがtrueの場合、RigidBody2D.linearVelocity.xにmoveSpeedを設定して自動で水平移動します。checkIntervalで指定した間隔ごとに_checkAndJumpIfNeeded()を呼び出し、穴検知とジャンプ判定を行います。
- _checkAndJumpIfNeeded()
- ジャンプクールタイム(
minJumpCooldown)と接地条件(requireGroundedToJump)を満たしているか確認します。 - ノードのワールド座標から、
raycastDownOffsetだけ下げた位置を「足元」とみなし、そこからraycastDistanceだけ進行方向にオフセットした位置の真下に向かって Ray を飛ばします。 PhysicsSystem2D.instance.raycast(start, end, null, groundLayerMask)でRayCastを実行し、ヒット結果があれば「床あり」と判定します。- 床がない場合(
hasGround === false)に_doJump()を呼び出してジャンプします。
- ジャンプクールタイム(
- _doJump()
RigidBody2D.linearVelocity.yにjumpVelocityをセットし、上向きの速度を与えます。_lastJumpTimeを更新し、クールタイム管理に利用します。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、スクリプトを置きたいフォルダ(例:
assets/scripts)を選択します。 - フォルダ上で右クリック → Create → TypeScript を選択します。
- 作成されたファイル名を
JumpGap.tsに変更します。 - ダブルクリックして開き、先ほどのコード全文を貼り付けて保存します。
2. テスト用シーンとノードの準備
- シーンの作成
- メニューから File → New Scene で新しいシーンを作成し、
auto_jump_test.sceneなどの名前で保存します。
- メニューから File → New Scene で新しいシーンを作成し、
- 地面(床)ノードの作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、
Groundノードを作成します。 - Inspector で
Groundの Transform を調整し、横に長い地面になるようにスケールやサイズを設定します。 - Add Component ボタンを押し、Physics 2D → BoxCollider2D を追加します。
- 必要に応じて RigidBody2D を追加し、Type を Static に設定して「動かない床」にします。
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、
- 穴(ギャップ)の作成
- 簡単な方法: 床を2つに分けて間に隙間を空けます。
Groundを右クリック → Duplicate で複製し、Ground2を作成します。GroundとGround2の Position.x を調整し、プレイヤーが走る方向に適度な隙間(穴)ができるように配置します。
- 簡単な方法: 床を2つに分けて間に隙間を空けます。
3. プレイヤーノードの準備
- プレイヤーノードの作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、
Playerノードを作成します。 Playerの Position.y をGroundの少し上(例: y = 100)に設定し、地面の上に乗るように調整します。
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、
- 物理コンポーネントの追加
Playerを選択し、Inspector の Add Component ボタンをクリックします。- Physics 2D → RigidBody2D を追加します。
- Type は Dynamic にします。
- 重力スケール(Gravity Scale)はデフォルトの
1のままで構いませんが、ジャンプの高さに応じて調整しても良いです。
- 同様に Physics 2D → BoxCollider2D を追加し、キャラの見た目に合うようにサイズを調整します。
4. JumpGap コンポーネントのアタッチ
Playerノードを選択します。- Inspector の Add Component ボタンをクリックし、Custom → JumpGap を選択します。
- もし Custom カテゴリに表示されない場合は、スクリプトのコンパイルが完了しているか、クラス名とファイル名が
JumpGapになっているか確認してください。
- もし Custom カテゴリに表示されない場合は、スクリプトのコンパイルが完了しているか、クラス名とファイル名が
5. JumpGap プロパティの設定例
Player の JumpGap コンポーネントに、以下のような値を設定してみてください(あくまで一例です)。
- moveSpeed:
4- 右方向にゆっくり走る程度の速度。
- autoMove:
true- 自動で右方向へ走ります。
- jumpVelocity:
12- 標準的なジャンプ高さ。重力が強い場合は値を大きくします。
- raycastDistance:
2- プレイヤーの少し前方の足元をチェックします。穴の幅に応じて調整してください。
- raycastDownOffset:
0.5- キャラの中心から少し下(足元付近)からRayを飛ばします。キャラの高さに合わせて調整。
- raycastDepth:
1.5- 地面の厚みと高さに合わせて、十分に下まで伸ばします。
- groundLayerMask:
0xffffffff- 全レイヤーを地面判定対象にします。レイヤーを分けている場合は適宜変更してください。
- checkInterval:
0.05- 約20FPS相当で穴検知を行います。より正確にしたければ
0.02などにします。
- 約20FPS相当で穴検知を行います。より正確にしたければ
- minJumpCooldown:
0.25- 0.25秒以内に連続ジャンプしないように制限します。
- requireGroundedToJump:
true- 接地しているときだけジャンプする、一般的な挙動になります。
- debugDraw:
true(調整中のみ)- RayCastの情報をコンソールに出して挙動を確認します。問題なければ
falseに戻してください。
- RayCastの情報をコンソールに出して挙動を確認します。問題なければ
6. 再生して動作を確認
- 画面上部の Play ボタンを押してゲームを実行します。
Playerが右方向に自動で走り、穴の手前まで来たときにジャンプして飛び越えられているか確認します。- もし穴に落ちてしまう場合は、以下のパラメータを調整してみてください。
- raycastDistance を大きくする(例: 2 → 3)と、早めにジャンプしやすくなります。
- jumpVelocity を大きくする(例: 12 → 15)と、より高く遠くへ飛びます。
- 地面のコライダーサイズや位置が正しく設定されているか確認します。
- Physics 2D の Gravity が強すぎる場合は少し弱めるか、jumpVelocity を上げます。
まとめ
この JumpGap コンポーネントは、
- RigidBody2D + Collider2D + PhysicsSystem2D だけに依存し、外部のゲームマネージャやシングルトンに一切依存しない完全独立型コンポーネントです。
- ノードにアタッチして数値を調整するだけで、「足元の穴を検知して自動ジャンプする」挙動をすぐに導入できます。
- 移動速度、ジャンプ力、RayCastの距離・深さ、クールタイムなどをインスペクタから柔軟に調整できるため、さまざまなゲームテンポやレベルデザインに対応できます。
応用例としては、
- オートランゲームの敵キャラや仲間キャラにアタッチして、障害物や穴を自動で避けるAIの一部として利用する。
- チュートリアル用デモキャラにアタッチして、プレイヤーに「理想的な動き」を見せるゴーストとして使用する。
- レベルテスト時に、さまざまな速度・ジャンプ力で自動プレイさせ、コース設計の難易度チェックに使う。
このように、JumpGap を単体コンポーネントとして設計しておくことで、プロジェクトごとに余計な依存関係を気にせず、必要なノードにドラッグ&ドロップするだけで再利用できます。
挙動をさらに高度にしたい場合は、
- 接地判定を「法線方向が上向きの接触のみ有効」とする。
- RayCastの方向を傾斜や段差に合わせて動的に変える。
- 穴だけでなく「前方の壁」も検知して、二段ジャンプや壁ジャンプに発展させる。
といった拡張も容易です。
まずはこの記事のコードをそのまま試し、プロジェクトに合わせてパラメータやロジックを少しずつカスタマイズしてみてください。




