【Godot】Godotのジャンプ入門:コヨーテタイムと先行入力で、崖際と着地際のジャンプを拾う

作成: 2026-09-22最終更新: 2026-10-10

コヨーテタイムと先行入力の2本の残り時間で、崖の端や着地の直前に押したジャンプを拾う書き方を解説します。

横スクロールのジャンプを気持ちよくするには、崖の端や着地の直前に押したジャンプも拾えるようにします。床を離れたあと少しだけ跳べる「コヨーテタイム」と、着地の前に押したジャンプを覚えておく「先行入力」の2つです。

どちらも無いと、崖の端で押したのにそのまま落ちたり、着地の直前に押したジャンプが消えたりします。押したフレームと床にいるフレームが、前後にずれるからです。

この記事では、2本の残り時間でそのずれを埋める書き方と、確かめ方を解説します。

床にいる区間の前にはみ出した押下は先行入力、後ろにはみ出した押下はコヨーテで拾う

この記事でわかること

  • ジャンプのタイミングのずれ2種類
  • コヨーテタイム(床を離れたあとの猶予)の書き方
  • 先行入力(着地の前に押したジャンプを覚えておく)の書き方
  • 「跳びたい」と「跳べる」の条件の分け方と、_physics_process の中の順番
  • 実践:L字の足場で、2つの猶予を確かめる

動作確認環境: Godot 4.7

Sponsored

ジャンプのタイミングのずれ

ジャンプが成立しない理由はいくつもありますが、タイミングのずれに限ると2種類しかありません。

押したタイミングは、床にいるあいだより後ろにずれるか、前にずれるかの2種類

押したタイミングが、床にいるあいだに入っていれば跳べます。ずれ方は2つです。

  • 床を離れたあとに押した: 崖から歩き出したあとで、もう落ちはじめている
  • 着地する前に押した: まだ空中にいる

埋め方も別々です。離れたあとのずれは、跳べる時間を床を離れたあとまで少し延ばして拾います。着地前のずれは、押したことを覚えておいて、着地したときに使います。

コヨーテタイム(床を離れたあとの猶予)

床を離れたあと、短いあいだだけジャンプを許します。残り時間を1本持つだけです。

if on_floor:
	coyote_timer = COYOTE_TIME     # 床にいるあいだは最大のまま
else:
	coyote_timer -= delta
床を離れたあとも0.1秒だけ跳べる区間を延ばし、その中の押下を拾うのがコヨーテタイム

接地しているあいだは、毎フレーム最大の値(COYOTE_TIME)に戻します。床を離れた瞬間から減りはじめ、0 になるまでは跳べます。delta は前のフレームからの経過秒数なので、残り時間は秒の単位で減ります。

自分でジャンプして床を離れたときも、同じように残り時間が減りはじめます。消さないままにすると、床を離れた直後にもう一度押したときも条件が通ります。空中で二段ジャンプになるのは、これが原因です。

跳んだフレームでコヨーテの残り時間を0にしないと、床を離れた直後の空中で2回目が通る

ジャンプが成立したフレームで、最大に戻す行より後ろに coyote_timer = 0.0 と書けば止まります。跳んだフレームの is_on_floor()(直前の move_and_slide() で床に触れていたかを返す関数)はまだ true なので、順番が逆だと同じフレームのうちに最大へ戻ります。この消す行は、次に足す先行入力とセットになります。

先行入力(押したジャンプを覚えておく)

押した瞬間の判定は、そのフレームにしか立ちません。着地の3フレーム前に押しても、着地したときにはもう消えています。

if jump_pressed:
	jump_buffer_timer = BUFFER_TIME    # 押した瞬間だけ覚える
else:
	jump_buffer_timer -= delta
着地の前に押した瞬間を0.1秒の残り時間として覚えておき、着地したら使うのが先行入力

覚えるのは押した瞬間の1回だけです。is_action_pressed()(押しているあいだ)で覚えると、指を離さないかぎり毎フレーム最大に戻ります。着地するたびに勝手に跳び続けることになります。

3つの入力判定の使い分けそのものは 入力判定入門 にまとめてあります。

Sponsored

ジャンプの条件(跳びたいと跳べる)

2本の残り時間ができたので、条件を書きます。ここで両方を必須にすると困ったことになります。

if jump_buffer_timer > 0.0 and coyote_timer > 0.0:    # これだと困る

この形だと、COYOTE_TIME を 0 にした瞬間に、床の上で押しても跳べなくなります。猶予を切ったときの比較ができません。分けて書きます。

var wants_jump := jump_pressed or jump_buffer_timer > 0.0
var can_jump := on_floor or coyote_timer > 0.0

if wants_jump and can_jump:
	velocity.y = JUMP_VELOCITY
	coyote_timer = 0.0
	jump_buffer_timer = 0.0
「跳びたい」はいま押したか覚えているか、「跳べる」は床にいるかコヨーテが残っているか

「跳びたい」は、いま押したか、さっき押したのを覚えているか。「跳べる」は、いま床にいるか、離れてまだ間がないか。猶予はどちらも or の右側にいるだけなので、0 にしても左側の普通のジャンプは残ります。

_physics_process の順番

ここまでの部品を1つの関数に並べます。順番に意味があります。

前フレームの接地を読むところからmove_and_slide()を1回呼ぶまで、物理処理に並べる5つの順番
  1. is_on_floor() を読む。返ってくるのは、前の物理フレーム(物理の計算1回ぶん。既定は1秒に60回)の move_and_slide() が床に当たったかどうかです
  2. 接地なら coyote_timer を最大に、空中なら減らして重力を足す
  3. 押した瞬間なら jump_buffer_timer を最大に、そうでなければ減らす
  4. 「跳びたい」と「跳べる」がそろっていれば velocity.y を書き換え、両方を 0 にする
  5. 左右の速度を入れて move_and_slide() を1回だけ呼ぶ

move_and_slide() を2回呼ぶと、そのフレームだけ移動が二重になり、接地の結果も上書きされます。押した瞬間の判定は _physics_process の中で読んで構いません。Input.is_action_just_pressed() は物理処理の中なら物理フレームを基準に判定します。

Sponsored

実践:L字の足場で、2つの猶予を確かめる

横スクロールアクションの崖際、メトロイドヴァニアの足場渡り、パズルアクションの落下——ジャンプの入力猶予はジャンルを問わず効きます。段差1つで、2つの猶予を別々に確かめます。

① 置くもの

新しいシーンを1つ作り、main.tscn として保存してメインシーンに指定します。プレイヤーとHUDは、同じ main.tscn の中に置きます(% 付きの名前は、同じシーンの中でしか引けないため)。

Main (Node2D)
├── UpperFloor (StaticBody2D)      位置 (300, 300)
│   └── CollisionShape2D           RectangleShape2D  (400, 32)
├── LowerFloor (StaticBody2D)      位置 (760, 560)
│   └── CollisionShape2D           RectangleShape2D  (500, 32)
├── Player (CharacterBody2D)       位置 (200, 260)   player.gd
│   ├── Sprite2D                   icon.svg・Scale (0.25, 0.25)
│   └── CollisionShape2D           RectangleShape2D  (32, 32)
└── Hud (CanvasLayer)             画面に固定して描く層
    └── VBox (VBoxContainer)       位置 (24, 24)。子を縦に並べる
        ├── CoyoteBar (ProgressBar)
        ├── BufferBar (ProgressBar)
        └── CountLabel (Label)
上の段から260px下に下の段があり、左上にプレイヤー。コヨーテのバーはいっぱい、先行入力のバーは空

足場には絵を付けていないので、実行しても形は見えません。エディタ上部の Debug から Visible Collision Shapes をオンにしておくと、当たり判定の枠が出て確かめやすくなります。

CoyoteBar・BufferBar・CountLabel の3つは、シーンドックで右クリックして Access as Unique Name をオンにします(スクリプトから % 付きの名前で呼べるようにする設定)。VBox の Custom Minimum Size を (240, 0) にしておくと、バーが見やすい幅になります。上の段と下の段は縦に 260 離れているので、落ちきるまで 0.7 秒ほどかかります。コヨーテの 0.1 秒はその途中で切れます。Project Settings の Input Map タブで、Add New Action に jump と入れて追加し、行の + からスペースキーを登録します(Input Map 入門)。

② 2本の残り時間を持つ

player.gd の全文です。

# player.gd
extends CharacterBody2D

const SPEED := 220.0
const JUMP_VELOCITY := -400.0       # 2Dは上向きがマイナス
const COYOTE_TIME := 0.1
const BUFFER_TIME := 0.1

var coyote_timer := 0.0
var jump_buffer_timer := 0.0
var jump_count := 0

@onready var coyote_bar: ProgressBar = %CoyoteBar
@onready var buffer_bar: ProgressBar = %BufferBar
@onready var count_label: Label = %CountLabel

func _physics_process(delta: float) -> void:
	var on_floor := is_on_floor()          # 前の物理フレームの結果

	if on_floor:
		coyote_timer = COYOTE_TIME
	else:
		coyote_timer -= delta
		velocity += get_gravity() * delta

	var jump_pressed := Input.is_action_just_pressed("jump")
	if jump_pressed:
		jump_buffer_timer = BUFFER_TIME
	else:
		jump_buffer_timer -= delta

	var wants_jump := jump_pressed or jump_buffer_timer > 0.0
	var can_jump := on_floor or coyote_timer > 0.0
	if wants_jump and can_jump:
		velocity.y = JUMP_VELOCITY
		coyote_timer = 0.0                 # 消さないと空中で2回目が通る
		jump_buffer_timer = 0.0
		jump_count += 1

	velocity.x = Input.get_axis("ui_left", "ui_right") * SPEED
	move_and_slide()
	_update_hud()

func _update_hud() -> void:
	coyote_bar.value = _ratio(coyote_timer, COYOTE_TIME)
	buffer_bar.value = _ratio(jump_buffer_timer, BUFFER_TIME)
	count_label.text = "ジャンプ %d 回" % jump_count

func _ratio(left: float, limit: float) -> float:
	if limit <= 0.0:
		return 0.0                         # 0 で割らない
	return clampf(left / limit, 0.0, 1.0) * 100.0

jump_count を出しているのは、成立したフレームだけ色を変えても目で追えないからです。回数が増えたかどうかで判定します。

③ 動かして確かめる

上の段から落ちた直後はコヨーテのバーが残って跳べ、着く前に押すと先行入力が残って着地の次のフレームで跳ぶ

F5 で実行し、右矢印で上の段の端まで歩きます。見るのは4つです。

まずコヨーテです。端から落ちはじめてすぐスペースを押すと、コヨーテのバーが残っているあいだなら、空中にいても回数が増えて跳びます。バーは0.1秒で空になるので、かなり素早く押す必要があります。間に合わないときは、COYOTE_TIME を 0.3 ほどに伸ばして確かめると分かりやすくなります(確かめたら戻します)。

次に先行入力です。下の段へ落ちていき、着く少し前に押します。押したフレームでは回数が増えず、足が下の段に着いた次の物理フレームで増えます。is_on_floor() が前のフレームの結果を返すからで、60分の1秒後なので見た目には着地と同時です。

3つ目は消し忘れです。coyote_timer = 0.0 の行だけをコメントアウトし、上の段の上でスペースを押して、コヨーテのバーが残っているうちにもう一度押します。2回目が空中で通り、回数が2つ増えます。確かめたら、行を元に戻します。

仕上げに、COYOTE_TIME と BUFFER_TIME をどちらも 0.0 にして、1つ目と2つ目の操作をもう一度します。端から落ちた直後に押しても回数は増えずにそのまま落ち、着く前に押したジャンプは着地しても出ません。床の上で押せば、普通のジャンプはそのまま跳べます。さっき拾えていた2つの押下は、足した2本の残り時間が拾っていたものです。

ポイントは2つです。

  • 成立したフレームで、両方を 0 にする: コヨーテを残すと空中の2回目が通ります。先行入力のほうは、着地までが短い足場で、同じ押下がもう一度使われるのを防ぎます
  • 見るのはスプライトではなく当たり判定: 四角い当たり判定は、底の端が床の上面に乗っているうちは床の上です。仕上げの 0.0 でも端で跳べてしまうなら、コヨーテではなく、まだ床に乗っていたということです

うまくいかないときは

症状原因直し方
実行すると「Node not found: "%CoyoteBar"」と出るCoyoteBar などに Access as Unique Name を付けていないシーンドックで CoyoteBar・BufferBar・CountLabel を右クリックし、Access as Unique Name をオンにする
成立したフレームで 0 にしたのに、空中で2回目が通る最大に戻す行が、0 にする行より後ろにある。跳んだフレームの is_on_floor() はまだ true なので、同じフレームで最大に戻る最大に戻す処理を先に置き、0 にする行はその後ろにする
スペースを押しても回数が増えず、出力に The InputMap action "jump" doesn't exist. が並ぶInput Map に jump を作っていない。存在しない名前を読むと、毎フレームこのエラーを出して false を返すProject Settings の Input Map に jump を足し、スペースキーを割り当てる

おまけ:先に知っておくと良いこと

  • 0.1秒は出発点: 空中で跳べる感じが残るなら短く、崖で毎回落ちるなら長くします。フレーム数ではなく秒で持てば、物理FPS(1秒あたりの物理の更新回数。既定は60)を変えても猶予の長さは保てます
  • 跳べたあとの弧は別の話: 離したら上昇を切る可変ジャンプは 入力判定入門 にあります。この記事は「そのフレームで受理するか」までです
  • 攻撃と組み合わせるなら: 地上でだけジャンプと攻撃を受け付け、攻撃中は読まない形は プレイヤーステートマシン入門 にあります
  • 床の扱いそのもの: 壁に触れただけで跳べるなど、どの面を床とみなすかは motion_modeの使い分け 、移動と減速は move_and_slideとmove_toward にあります

まとめ

  • ずれは2種類: 床の区間の右にはみ出すか、左にはみ出すか。コヨーテは離れたあと、先行入力は着地する前を埋める
  • コヨーテは空中で減らす: 接地中は最大に保ち、空中で delta ずつ減らす
  • 覚えるのは押した瞬間だけ: 成立したら、跳んだフレームで両方の残り時間を 0 にする
  • 跳びたいと跳べるを分ける: 分けておくと、猶予を 0 にしても普通に跳べる
  • move_and_slide() は最後に1回: フレームの終わりに1回だけ呼ぶ

ジャンプの条件に is_on_floor() がそのまま入っていると、そこが崖際で落ちる場所です。いま書いているジャンプの条件は、どうなっていますか。

次に読む学習パスの次の記事
物理・移動

Area2Dで作る範囲検出とトリガー

この記事を読む
Godot このセクションのノート104