Hi, probably a repeatedly asked question:
How can I implement Coyote Time with CollisionPolygon2D and CharacterBody2D?
I’m new to Godot and GDScript, sorry.
Here’s my (very messy) code:
extends CharacterBody2D
var speed = 300.0
var jump_speed = -350.0
var dir = "right"
@onready var _animated_sprite = $AnimatedSprite2D
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
func _physics_process(delta):
# Add the gravity.
velocity.y += gravity * delta
# Handle Jump.
if Input.is_action_just_pressed("up") and is_on_floor():
velocity.y = jump_speed
# Get the input direction.
var direction = Input.get_axis("left", "right")
velocity.x = direction * speed
move_and_slide()
if not(Input.is_action_pressed("anyControl")):
if is_on_floor():
_animated_sprite.play("idleCycle")
if not(Input.is_action_just_pressed("anyControl")):
if dir == "right":
_animated_sprite.scale.x = 0.3
else:
_animated_sprite.scale.x = -0.3
if Input.is_action_pressed("left"):
_animated_sprite.stop()
_animated_sprite.play("walk")
dir = "left"
if Input.is_action_pressed("right"):
_animated_sprite.stop()
_animated_sprite.play("walk")
dir = "right"
if Input.is_action_just_pressed("up"):
_animated_sprite.stop()
if Input.is_action_pressed("quit"):
get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
get_tree().quit(1)
If anyone thinks my code is extremely bad, please tell me, as I’m not that good at keeping it good & formatted!
First let’s make a var called something like can_jump
var can_jump = true
Add this to the top of your code somewhere near your other variables.
Let’s make a new function so the code doesn’t get so confusing called jump(). We’re going to move some of your code under # Handle Jump here. We’re going to also change is_on_floor() with can_jump. We’re doing this because when you’re in “Coyote Time” you’re not on the ground so that won’t help us here.
func _physics_process(delta):
...
if Input.is_action_just_pressed("up") and can_jump:
jump()
...
func jump():
velocity.y = jump_speed
can_jump = false
Setting can_jump to false will prevent your players from being able to jump all the time in the air, but now when you land can_jump will still be false so let’s fix that by adding another check in your _physics_process
func _physics_process(delta):
...
if can_jump == false and is_on_floor():
can_jump = true
...
Okay so now when your players land back on the ground they can jump again. Now we can implement Coyote Time.
Add a Timer node to your scene and change it’s wait time to something pretty small like 0.3 seconds. We also want to check the One Shot box so it doesn’t automatically start again. You can rename it to something like “CoyoteTimer” if you’d like.
Connect the timeout() node to your script. If you named it CoyoteTimer it will default to making a function called _on_coyote_timer_timeout().
We’re going to make it so you can no longer jump once this time runs out like so:
func _on_coyote_timer_timeout():
can_jump = false
Now we just need to trigger this timer. So let’s add another check in our _physics_process. We only want this to activate when the following conditions are met:
We’re not on the ground
We can still jump (as in, we didn’t jump to get off the ground)
The timer isn’t running
So let’s add another check to know if we need to start the timer.
func _physics_process(delta):
...
if (is_on_floor() == false) and can_jump and $CoyoteTimer.is_stopped():
$CoyoteTimer.start()
...
I know this was kind of a lot, so feel free to ask if anything is confusing.
This has worked, however it’s given me a double jump now!!
Have I just implemented the code wrong?
extends CharacterBody2D
var speed = 300.0
var jump_speed = -350.0
var dir = "right"
var can_jump = true
@onready var _animated_sprite = $AnimatedSprite2D
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
func jump():
velocity.y = jump_speed
can_jump = false
func _on_coyote_timer_timeout():
can_jump = false
func _physics_process(delta):
# Add the gravity.
velocity.y += gravity * delta
# Handle Jump.
if Input.is_action_just_pressed("up") and can_jump:
jump()
if can_jump == false and is_on_floor():
can_jump = true
if (is_on_floor() == false) and can_jump and $CoyoteTimer.is_stopped():
$CoyoteTimer.start()
# Get the input direction.
var direction = Input.get_axis("left", "right")
velocity.x = direction * speed
move_and_slide()
if not(Input.is_action_pressed("anyControl")):
if is_on_floor():
_animated_sprite.play("idleCycle")
if not(Input.is_action_just_pressed("anyControl")):
if dir == "right":
_animated_sprite.scale.x = 0.3
else:
_animated_sprite.scale.x = -0.3
if Input.is_action_pressed("left"):
_animated_sprite.stop()
_animated_sprite.play("walk")
dir = "left"
if Input.is_action_pressed("right"):
_animated_sprite.stop()
_animated_sprite.play("walk")
dir = "right"
if Input.is_action_just_pressed("up"):
_animated_sprite.stop()
if Input.is_action_pressed("quit"):
get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
get_tree().quit(1)
Since I wrote all that code without having Godot open I did miss something haha.
We currently have
if can_jump == false and is_on_floor():
can_jump = true
AFTER all of the times when can_jump would be set to false. So when your character jumps during that same frame they are still on the floor and since we are checking if they are on the floor AFTER they jump it gets reset back to being able to jump.
Simply move that part of the code to the top of your func _physics_process(delta): and it should fix it.
extends CharacterBody2D
var speed = 300.0
var jump_speed = -350.0
var dir = "right"
var can_jump = true
@onready var _animated_sprite = $AnimatedSprite2D
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
func jump():
velocity.y = jump_speed
can_jump = false
func _on_coyote_timer_timeout():
can_jump = false
func _physics_process(delta):
if can_jump == false and is_on_floor():
can_jump = true
# Add the gravity.
velocity.y += gravity * delta
# Handle Jump.
if Input.is_action_just_pressed("up") and can_jump:
jump()
if (is_on_floor() == false) and can_jump and $CoyoteTimer.is_stopped():
$CoyoteTimer.start()
# Get the input direction.
var direction = Input.get_axis("left", "right")
velocity.x = direction * speed
move_and_slide()
if not(Input.is_action_pressed("anyControl")):
if is_on_floor():
_animated_sprite.play("idleCycle")
if not(Input.is_action_just_pressed("anyControl")):
if dir == "right":
_animated_sprite.scale.x = 0.3
else:
_animated_sprite.scale.x = -0.3
if Input.is_action_pressed("left"):
_animated_sprite.stop()
_animated_sprite.play("walk")
dir = "left"
if Input.is_action_pressed("right"):
_animated_sprite.stop()
_animated_sprite.play("walk")
dir = "right"
if Input.is_action_just_pressed("up"):
_animated_sprite.stop()
if Input.is_action_pressed("quit"):
get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
get_tree().quit(1)