Double jump while falling

Godot Version

4.7

Question


I have a question. I want the player to jump twice while in FALL state (from IDLE to FALL). The double jump works from the ground. The double jump after a coyote jump works too. Am I just missing something obvious?

extends CharacterBody2D

enum STATE {
	IDLE,
	RUN,
	JUMP,
	FALL,
	DOUBLE_JUMP,
}

const fall_gravity := 1000.0
const fall_velocity := 300.0
const run_velocity := 120.0
const jump_velocity := -280.0
const jump_deceleration := 500.0
const double_jump_velocity := -200.0

@onready var player_sprite: Sprite2D = $Player_sprite
@onready var coyote_timer: Timer = $CoyoteTimer

var active_state := STATE.FALL
var can_double_jump := false

func _ready() -> void:
	switch_state(active_state)

func _physics_process(_delta: float) -> void:
	process_state(_delta)
	move_and_slide()

func switch_state (to_state: STATE) -> void:
	var previous_state := active_state
	active_state = to_state
	match active_state:
		STATE.FALL:
			if previous_state == STATE.IDLE:
				coyote_timer.start()
			
		STATE.IDLE:
			can_double_jump = true
			
		STATE.JUMP:
			velocity.y = jump_velocity
			coyote_timer.stop()
			
		STATE.DOUBLE_JUMP:
			velocity.y = double_jump_velocity
			can_double_jump = false

func process_state(_delta: float) -> void:
	match active_state:
		STATE.FALL:
			velocity.y = move_toward(velocity.y, fall_velocity, fall_gravity * _delta)
			handle_movement()
			if is_on_floor():
				switch_state(STATE.IDLE)
			elif Input.is_action_just_pressed("jump"):
				if coyote_timer.time_left > 0:
					switch_state(STATE.JUMP)
				elif can_double_jump:
					switch_state(STATE.DOUBLE_JUMP)
		
		STATE.IDLE:
			handle_movement()
			if not is_on_floor():
				switch_state(STATE.FALL)
			elif Input.is_action_just_pressed("jump"):
				switch_state(STATE.JUMP)
				
		STATE.JUMP, STATE.DOUBLE_JUMP:
			velocity.y = move_toward(velocity.y, 0, jump_deceleration * _delta)
			handle_movement()
			if Input.is_action_just_released("jump") or velocity.y >=0:
				velocity.y = 0
				switch_state(STATE.FALL)

func handle_movement() -> void:
	var input_direction := signf(Input.get_axis("move_left", "move_right"))
	if input_direction:
		player_sprite.flip_h = input_direction < 0
	velocity.x = input_direction * run_velocity


Perhaps you enter the double_jump state, set velocity.y to 0, and in the next call to process_state you switch to FALL since velocity.y = 0 and in the condition you have velocity.y >= 0

I believe the problem could be that your if: jump, elif: double_jump filters away the jump when you fall but allows you to double jump. Are you sure you do the normal jump when when in fall state? Could you double check?

Either way it looks like the double jump elif will bypass the coyote timer whenever you try to do a first jump after coyote has expired. I would try to solve it by setting can_double_jump to try when entering jump state and not do it in idle.

When from IDLE to FALL and I press “jump”, it jumps only once, debug says that after pressing jump I switch to DOUBLE_JUMP where can_double_jump = false

It could be that your coyote timer is very short. Try to increase it to a second or more just to test. But also make it so that can double jump is false until you have jumped the normal jump. Right now, you can enter the double jump before the first jump, and you cant enter normal jump after. You can solve it in other ways but as you add more states it can quickly turn messy. If you set ut to true only after the first jump you’ll make it simple for yourself.

If you print can_double_jump false from the end of the double jump section, its fine. You set it to false a line or a few lines above. It is set to true when you first enter the state.