Trying to implement coyote time results in a double jump

Godot Version

v4.4.1.stable.official [49a5bc7b6]

Question

If I try to implement coyote time, the game lets me do a double jump (a janky triple jump because I have my own double jump).

extends CharacterBody2D

## 75 is my fps
# How fast you accelerate while on the ground
const grndaccel: float = 30 * 75
# How fast you decelerate while on the ground
const grnddecel: float = 65 * 75
# How fast you accelerate in the air
const airaccel: float = 15 * 75
# Speed cap
const maxspeed: float = 300
# How high you jump
const jumpvelocity: float = -400
# How high you double jump
const doublejumpvelocity: float = -300
# Gravity
const gravity: float = 750
# How fast you can fall
const maxfallspeed: float = 1000
# TODO How much knockback is applied on hit (100/value)
const weight: float = 100


@export var playerstates: Array = ["grounded", "airborne", "something else"]
@export var state: String = playerstates[1]


@export var candoublejump: bool = false
@export var canjump: bool = false


func _physics_process(delta: float) -> void:
	
	var direction = Input.get_axis("left", "right")
	
	if state == playerstates[0]:
		_grounded_physics(delta, direction)
	
	if state == playerstates[1]:
		_airborne_physics(delta, direction)
	
	%Label.text = str(canjump)
	
	move_and_slide()

func _grounded_physics(delta: float, direction: float) -> void:
	
	velocity.x = move_toward(velocity.x, maxspeed * direction, grndaccel * delta)
	
	%Coyotetime.start()
	
	# refresh jump and double jump
	canjump = true
	candoublejump = true # irrelevant double jump not the one that im talking about
	
	if Input.is_action_just_pressed("jump"):
		velocity.y = jumpvelocity
		state = playerstates[1] # sets player state to airborne
		canjump = false
	
	if !is_on_floor():
		state = playerstates[1]
		## canjump = false
		# FIXME If i put this line here, I can't do coyote time,
		# but if I don't, then the game only sets it to false for
		# one frame, and then back to true again, until %Coyotetime
		# times out and sets it back to false.
	

func _airborne_physics(delta: float, direction: float) -> void:
	
	# if a direction is held mid-air, accelerate to that
	# will not decelerate you if no button is held
	# FIXME might decelerate you if you get launched with speed higher than maxspeed
	# and hold in the same direction, probably wont happen often
	if direction:
		velocity.x = move_toward(velocity.x, maxspeed * direction, airaccel * delta)
	
	if Input.is_action_just_pressed("jump"):
		# coyote time
		# FIXME seems to behave weirdly, for some reason you can do
		# a second regular jump until the timer runs out
		if canjump == true:
			velocity.y = jumpvelocity
			canjump = false
		# double jump
		elif candoublejump == true:
			velocity.y = doublejumpvelocity
			candoublejump = false
	
	# gravity
	# should probably use move_toward()
	if velocity.y < maxfallspeed:
		velocity.y += gravity * delta
	if is_on_floor():
		state = playerstates[0]


func _on_coyotetime_timeout() -> void:
	canjump = false

Just add an int that tracks the number of jumps, and if the max has been reached, don’t allow a jump.

I also recommend you make playerstates an enum. It will make your code much more readable. So will using snake_case for variable names.

enum player_states = {grounded, airborne, something_else}

I don’t really understand how the int method is better, if anything it complicated stuff even more for me
I did manage to get it working with booleans though.

func _process(delta: float) -> void:
	match state:
		state_list.GROUNDED:
			_grounded(delta)
		state_list.AIRBORNE:
			_airborne(delta)
	move_and_slide()

func _grounded(delta: float) -> void:
	
	can_jump = true
	can_doublejump = true
	
	if Input.is_action_just_pressed("jump"):
		velocity.y = jump_velocity
		can_jump = false
		_change_state(state_list.AIRBORNE)
	
	elif !is_on_floor():
		_change_state(state_list.AIRBORNE)
		%Timer.start() # now it doesn't start if you pressed jump

func _airborne(delta: float) -> void:
	velocity.y = move_toward(velocity.y, max_fallspeed, gravity * delta)
	
	if Input.is_action_just_pressed("jump"):
		if can_jump == true:
			velocity.y = jump_velocity
			can_jump = false
		elif can_doublejump == true:
			velocity.y = doublejump_velocity
			can_doublejump = false
	
	if is_on_floor():
		_change_state(state_list.GROUNDED)

Now that I read this code again, this probably would’ve worked with integer, though I prefer booleans still.

Whatever floats your boat.