Coyote Time never turns off

Godot Version

4.1

Question

Just lost this whole post because I ctrl+a ctrl+v to copy over my code into the box and it deleted everything. :upside_down_face: I will try and write it out again to the best of my ability.

This is my first ever project. I successfully followed Brackeys “How to make a Video Game - Godot Beginner Tutorial” and have been trying to add a coyote time feature for which I have been following the kidscancode tutorial here: Coyote Time :: Godot 4 Recipes

I feel bad asking for help especially as I am following some of the most popular, and basic, Godot tutorials out there but I am at my wits end when it comes to understanding what is causing the coyote time to never turn off here. I am pretty sure that’s the issue as the game behaves normally until I walk off of a platform at which point I may jump infinitely.

I have been searching for an answer, looking at other tutorials and comparing my project to the “2d_moving_platforms” project supplied by kidscancode for literal hours and I don’t feel like I’m making any progress. If this was something I needed to just get done then I would scrap it all and start again but crucially I need to understand why this is happening and I’m not seeing what would cause this.

The main discrepancy I can see is that I have an actual timer node with One Shot enabled whereas the kidscancode project doesn’t visibly have an actual timer node but I don’t understand how or why that is the case because the CoyoteTimer is referenced in their code.

This will presumably be some of the ugliest, worst formatted code you’ll ever see - at this point it’s ideas mashed up from at least three different sources but I feel like I must be missing something fundamental/obvious to people who actually know what they’re doing.

extends CharacterBody2D


const SPEED = 130.0
const JUMP_VELOCITY = -300.0

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var coyote_frames = 6  # How many in-air frames to allow jumping
var coyote = false  # Track whether we're in coyote time or not
var last_floor = false  # Last frame's on-floor state
var jumping = false
@onready var coyote_timer = $CoyoteTimer

@onready var animated_sprite = $AnimatedSprite2D

func _ready():
	$CoyoteTimer.wait_time = coyote_frames / 60.0

func get_input(delta):
	# Handle Jump.
	if Input.is_action_just_pressed("Jump") and (is_on_floor() or coyote):
		velocity.y = JUMP_VELOCITY
		jumping = true

func _physics_process(delta):
	# Add the gravity.
	if not is_on_floor():
		velocity.y += gravity * delta
	# Get input direction: -1, 0, 1
	var direction = Input.get_axis("Move Left", "Move Right")
	
	#Flip da sprite
	if direction > 0: 
		animated_sprite.flip_h = false
	elif direction < 0: 
		animated_sprite.flip_h = true
	
	#Play Animations
	
	if is_on_floor():
		if direction == 0:
			animated_sprite.play("Idle")
		else:
			animated_sprite.play("Run")
	else:
		animated_sprite.play("Jump")
	
	#Apply Movement
	if direction:
		velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
	get_input(delta)
	move_and_slide()
	if is_on_floor() and jumping:
		jumping = false
	if !is_on_floor() and last_floor and !jumping:
		coyote = true
		$CoyoteTimer.start()
	last_floor = is_on_floor()

func _on_coyote_timer_timeout():
	coyote = false
	

add in ready, as this is not connected and you won’t change coyote variable

$CoyoteTimer.timeout.connect(_on_coyote_timer_timeout)

Try setting coyote to false right after the check. It doesnt matter if it is already false. Those times it is true are the ones you want to catch. No need to introduce an extra if instead of just setting it to false for every case.

That worked! Thank you so much.

So what we’ve done here is under the _ready function we have created a connection to the CoyoteTimer node that is looking for the timeout signal? Does the name in the brackets define what we need to type to reference this connection later in the code? I apologise if connection is the wrong word to use here.

I am confused as to why there doesn’t seem to need to be anything like that in the kidscancode “2d_moving_platforms” project

I’ll put it here in case anyone would like to explain that to me but I am not asking you to do this and I really appreciate the help!

extends CharacterBody2D

@export var speed = 150
@export var jump_speed = -350
@export var acceleration = 15
@export var friction = 16

var emote_textures = {
	"skull": Vector2i(20, 27),
	"surprise": Vector2i(20, 25),
	"question": Vector2i(22, 25)
}
var coyote_frames = 6
var coyote = false
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var last_floor = false
var jumping = false
var dead = false


func _ready():
	$Emote.hide()
	$CoyoteTimer.wait_time = coyote_frames / 60.0

func get_input(delta):
	var direction = Input.get_axis("move_left", "move_right")
	if direction:
		velocity.x = lerp(velocity.x, direction * speed, acceleration * delta)
		$AnimatedSprite2d.play("walk")
	else:
		velocity.x = lerp(velocity.x, 0.0, friction * delta)
		$AnimatedSprite2d.play("idle")
	if jumping:
		if velocity.y < 0:
			$AnimatedSprite2d.play("jump_up")
		elif velocity.y > 0:
			$AnimatedSprite2d.play("jump_down")
	if Input.is_action_just_pressed("jump") and (is_on_floor() or coyote):
		velocity.y = jump_speed
		jumping = true
	
func _physics_process(delta):
	velocity.y += gravity * delta
	if dead:
		$AnimatedSprite2d.play("dead")
		$Emote.show()
		return
	get_input(delta)
	move_and_slide()
	for i in get_slide_collision_count():
		var c = get_slide_collision(i).get_collider()
		var n = get_slide_collision(i).get_normal()
		if c.is_in_group("pushable"):
			c.apply_central_impulse(-n * 5)
#			c.set_axis_velocity(-n * 10)
	if is_on_floor() and jumping:
		jumping = false
	if !is_on_floor() and last_floor and !jumping:
		coyote = true
		$CoyoteTimer.start()
	if velocity.x > 0:
		$AnimatedSprite2d.flip_h = false
	if velocity.x < 0:
		$AnimatedSprite2d.flip_h = true
	last_floor = is_on_floor()


func _on_coyote_timer_timeout():
	coyote = false

Thank you for the help,

This was a useful change to make, or at least not a harmful change to make, and was helpful for testing as it allowed me to set my coyote time to be very late without being able to double jump.

By itself however all this did was ensure I could only jump once after walking off of a ledge - the coyote state was still never turning off due to timeout iyswim

P.S. Can’t believe the Baba wanted to help little old me

It usage of signals,

The official documentation have explained it the best

The line reads like so: we connect the Timer's "timeout" signal to the node to which the script is attached. When the Timer emits timeout, we want to call the function _on_timer_timeout(), that we need to define.

In this case, it is another Baba! A mini boss from Breath of Fire 1 for the SNES! :smiley: