Godot Version
Godot 4.4.1-stable
Question
Hi everyone. I’ve been working on my first platformer and run into an issue where attempting to implement Coyote Time in a Finite State Machine, based on this one, results in an infinite jump while in the air.
Here’s the code, which I currently have in the Jump_state script
extends State
#region Variables
@export
var idle_state: State
@export
var run_state: State
@export
var fall_state: State
@export
var attack_state: State
@export
var jump_velocity: int = -400
@onready var coyote_timer: Timer = %CoyoteTimer
var coyote_jump: bool = false
#endregion
#region Functions
func enter() -> void:
super()
parent.velocity.y = jump_velocity
func process_physics(delta: float) -> State:
if !parent.is_on_floor():
parent.velocity.y += gravity * delta
if coyote_timer.is_stopped():
coyote_timer.start()
else:
coyote_jump = true
coyote_timer.stop()
if parent.velocity.y > 0:
return fall_state
if Input.is_action_just_pressed("Jump") && coyote_jump:
parent.velocity.y = jump_velocity
coyote_jump = false
print("Coyote Jump check")
var movement:= Input.get_axis("Left", "Right") * speed
if movement != 0:
parent.animations.flip_h = movement < 0
parent.velocity.x = movement
var was_on_floor := parent.is_on_floor()
parent.move_and_slide()
if was_on_floor && !parent.is_on_floor():
coyote_jump = true
if parent.is_on_floor():
if movement != 0:
return run_state
return fall_state
return null
func _on_coyote_timer_timeout() -> void:
print("Timer works")
coyote_jump = false
#endregion
Picture a player that is infinitely falling. On frame 1, the player is in the air. Therefore, the first check (if !parent.is_on_floor()
) succeeds. If the coyote timer has stopped, it is started.
On frame 2, the player is still falling. The coyote_jump
variable is set to true
and the timer is stopped. The player can now jump in the air once. Let’s say that the player does.
Frame 3, the player is still in the air. The timer is stopped, so it is once again started.
Frame 4. Player is in the air. Timer is running, so coyote_jump
is set to true
and the timer is stopped. The player can now jump in the air again.
You should probably want to keep track of the number of times the player is allowed to jump while in the air, and prevent a jump from being processed if the player tries to jump more often than allowed. The number of total jumps in the air can be reset to zero whenever the player makes contact with the ground.
1 Like
Thank you for the response!
I’ve managed to implement a crude solution based on your answer. Is there any input on how I could improve this? Thank you again
Fall state:
extends State
#region Variables
@export
var run_state: State
@export
var idle_state: State
@onready
var jump_velocity: int = -420
var jump_count: int = 0
var airtime: float = 0.0
var coyote_time_active: bool = true
#endregion
#region Functions
func process_physics(delta: float) -> State:
airtime += delta
if !parent.is_on_floor():
parent.velocity.y += gravity * delta
if %CoyoteTimer.is_stopped():
%CoyoteTimer.start()
else:
coyote_time_active = true
%CoyoteTimer.stop()
var movement := Input.get_axis('Left', 'Right') * speed
if movement !=0:
parent.animations.flip_h = movement < 0
parent.velocity.x = movement
parent.move_and_slide()
if Input.is_action_just_pressed("Jump") and coyote_time_active:
parent.velocity.y = jump_velocity
if parent.is_on_floor():
airtime = 0.0
jump_count = 0
if movement != 0:
return run_state
return idle_state
return null
func _on_coyote_timer_timeout() -> void:
coyote_time_active = false
#endregion
Jump state
extends State
#region Variables
@export
var idle_state: State
@export
var run_state: State
@export
var fall_state: State
@export
var attack_state: State
@export
var jump_velocity: int = -450
var jump_count: int = 0
var airtime: float = 0.0
#endregion
#region Functions
func enter() -> void:
super()
parent.velocity.y = jump_velocity
func process_physics(delta: float) -> State:
airtime += delta
if !parent.is_on_floor():
parent.velocity.y += gravity * delta
if Input.is_action_just_pressed("Jump") and jump_count < 1 and airtime < 0.1:
parent.velocity.y = jump_velocity
var movement:= Input.get_axis("Left", "Right") * speed
if movement != 0:
parent.animations.flip_h = movement < 0
parent.velocity.x = movement
parent.move_and_slide()
if parent.is_on_floor():
airtime = 0.0
jump_count = 0
if movement != 0:
return run_state
return fall_state
return null
#endregion
My main concern right now is if I need to have jump count logic in two states, and which state should get the coyote time logic.