• 2D
  • ✔ How could I restrict the Coyote Jump to when the Player falls below the ,,y Pos Before Unfloored"?

I put a Coyote Jump in my Player Script, because it's a very important ,,Invisible Ability" in any good 2D Platformer. It works as intended, but also has the unwanted side effect of making Double-Jumps possible.

I want to remove this Double-Jump, and I already have the idea of how I would go about it, but not the necessary knowledge for the execution:

I first want to record a snapshot of the Player's Y-Position inside a decimal number variable called ,,yPosBeforeUnfloored" every time the Player leaves the floor (,,floored" turns false). And then I want to add something along the lines of yPos == yPosBeforeUnfloored || yPos > yPosBeforeUnfloored to this if-line: if Input.is_action_just_pressed("jump") && coyoteTimer < 0.2:

You can do this by keeping track of how long it has been since the player was on the floor, and also whether the player is currently jumping. So something like this:

const JUMP_GRACE_PERIOD := 0.1  # 1/10th of a second

var _air_time := 0.0
var jumping := false

func _physics_process(delta: float) -> void:
	if is_on_floor():
		_air_time == 0.0
		jumping = false
	else:
		_air_time += delta
	
	if Input.is_action_just_pressed("jump"):
		if is_on_floor() || (!jumping && _air_time < JUMP_GRACE_PERIOD):
			# jump here - only if we're on the floor, OR if we've been falling 
			#	 for less than the grace period and we're not currently jumping
			jumping = true

This code is not tested, but it should demonstrate how to keep track of how long you have been falling, and whether or not you are jumping.

I hope this helps.

From the first answer I got (the one above), I now know that I didn't make the question clear enough. Mistakes is what happens sometimes. I originally removed the player script from the root comment of this discussion, because I thought it might be information overflow. But now I think it might be needed:

# youtu.be/wETY5_9kFtA	# youtu.be/BfQGXtlmE7k
# youtu.be/vFsJIrm2btU
extends KinematicBody2D

const UP = Vector2(0, -1)
var motion = Vector2()
const speed= 100
const gravity = 10
const jumpHeight = -200

var coyoteTimer = 0.0
const coyoteTime = 0.2

var running = false
var floored = false

func _physics_process(_delta):
	
	motion.y += gravity;
	
	if Input.is_action_pressed("right"):
		motion.x = speed
		$Sprite.flip_h = false; running = true
	elif Input.is_action_pressed("left"):
		motion.x = -speed
		$Sprite.flip_h = true; running = true
	else:
		motion.x = 0
		running = false
		
	if is_on_floor(): floored = true
	else:             floored = false
		
	if Input.is_action_just_pressed("jump") && coyoteTimer < coyoteTime:
		motion.y = jumpHeight

	motion = move_and_slide(motion, UP)

	
func _process(delta):
	
	if floored:
		coyoteTimer = 0
	else:
		coyoteTimer += 1 * delta
	
	print(coyoteTimer)
	
	animate()
	
func animate():
	if floored == true:
		if running == true: $Sprite.animation = "run"
		else: $Sprite.animation = "idle"
	else:
		$Sprite.animation = "jump"

I've already set up Coyote Time before that answer, and the question of this discussion is, how to prevent the players from abusing it for double-jumps:

@Sosasees said: I put a Coyote Jump in my Player Script, because it's a very important ,,Invisible Ability" in any good 2D Platformer. It works as intended, but also has the unwanted side effect of making Double-Jumps possible.

I want to remove this Double-Jump, and I already have the idea of how I would go about it, but not the necessary knowledge for the execution:

I first want to record a snapshot of the Player's Y-Position inside a decimal number variable called ,,yPosBeforeUnfloored" every time the Player leaves the floor (,,floored" turns false). And then I want to add something along the lines of yPos == yPosBeforeUnfloored || yPos > yPosBeforeUnfloored to this if-line: if Input.is_action_just_pressed("jump") && coyoteTimer < 0.2:

Apologies, I'm apparently misunderstanding.

It sounds like you just need to keep track of whether the player is jumping or not, and simply not allow a jump if they are already jumping, which was done in my first example.

However the mention of "record a snapshot of the Player's Y-Position" seems to indicate that you want the player to maintain the same y-position for a time after they leave the floor.

If that is the case then there are a couple of ways I can think of to do that off the top of my head:

Add an extra collision shape that's wider than the player. This will keep the player at the same y position when they step over a ledge. You could then disable this shape when the timer elapses.

Or - change this line:

	motion.y += gravity

to something like:

	if  coyoteTimer == 0.0 || coyoteTimer > coyoteTime:
		motion.y += gravity	

So don't apply gravity if the timer has not elapsed.

Also I would suggest making your consts uppercase.

Good luck.

I'm a bit confused. You mentioned that in the first example, you showed how to detect if the player is jumping or just falling. But I can't really comprehend how it works:

@bitshift-r said: You can do this by keeping track of how long it has been since the player was on the floor, and also whether the player is currently jumping. So something like this:

const JUMP_GRACE_PERIOD := 0.1  # 1/10th of a second

var _air_time := 0.0
var jumping := false

func _physics_process(delta: float) -> void:
	if is_on_floor():
		_air_time == 0.0
		jumping = false
	else:
		_air_time += delta
	
	if Input.is_action_just_pressed("jump"):
		if is_on_floor() || (!jumping && _air_time < JUMP_GRACE_PERIOD):
			# jump here - only if we're on the floor, OR if we've been falling 
			#	 for less than the grace period and we're not currently jumping
			jumping = true

This code is not tested, but it should demonstrate how to keep track of how long you have been falling, and whether or not you are jumping.

I hope this helps.

@bitshift-r said: Also I would suggest making your consts uppercase. I know that constants are conventionally typed in UPPERCASE, but I type them in camelCase on purpose so that I could easily change them to variables without it seeming Off, In case I need to.

It's that jumping bool defined on line 4.

So if the player is on the floor then we're not jumping so we set it to false (line 9).

Then when the user invokes the "jump" action we only proceed if the player is on the floor or if they are in the air but not jumping (line 14).

Then when we decide that we can let them jump we set jumping to true (line 17), and it will remain true until the player lands on the floor again.

Line 15 is where you would set your motion.y to actually make the player jump.

@bitshift-r said: func _physics_process(delta: float) -> void: What does -> void mean?

It's part of static typing: when you explicitly type out the data types of variables, function returns, etc... : float has the same purpose, which is to show that delta is a float.

By the way, your question is still pretty confusing. Can you tell us the mechanics of the "Coyote jump", or is it just a regular jump?

The ,,Coyote Jump" is intended to work like this: After the Player leaves the Floor, they should have a small time window in which they can still jump, because Human brains aren't perfect:

In platformers without this mechanic, almost every player, including me, will complain about how the game often doesn't register the press of the jump button.

The topic of this discussion is, that I need help to eliminate an unwanted side effect: In the moment, the player can double-jump if they double-press Jump really fast, but I don't want that in my game.

This is my current Player Movement Script (Embedded atop the Player Scene):

# youtu.be/wETY5_9kFtA	# youtu.be/BfQGXtlmE7k
# youtu.be/vFsJIrm2btU
extends KinematicBody2D

const UP = Vector2(0, -1)
var motion = Vector2()
const speed= 100
const gravity = 10
const jumpHeight = -200

var coyoteTimer = 0.0
const coyoteTime = 0.2

var running = false
# var jumping = false
var floored = false

func _physics_process(_delta):
	
	motion.y += gravity;
	
	if Input.is_action_pressed("right"):
		motion.x = speed
		$Sprite.flip_h = false; running = true
	elif Input.is_action_pressed("left"):
		motion.x = -speed
		$Sprite.flip_h = true; running = true
	else:
		motion.x = 0
		running = false
		
	if is_on_floor():
		floored = true
	else:
		floored = false
		
	if Input.is_action_just_pressed("jump"):
		if coyoteTimer < coyoteTime:
			motion.y = jumpHeight
			
 

	motion = move_and_slide(motion, UP)

	
func _process(delta):
	
	if floored:
		coyoteTimer = 0
	else:
		coyoteTimer += 1 * delta
	
	animate()
	
func animate():
	if floored == true:
		if running == true: $Sprite.animation = "run"
		else: $Sprite.animation = "idle"
	else:
		$Sprite.animation = "jump"

Ok I understand. Here's an idea you can try. Have a variable that tells you whether the player is jumping. This is different from whether it's on the floor since jumping is not the same as falling. Also have a variable that stores how long since the player was in the air. Set jumping if the player executed a jump, and reset when he's in the ground. The air timer can simply be an incrementer. It can either be in frames or seconds. The timer resets when it's on the ground. Your coyoteTimer should do the trick. If the player is not jumping, is not on the floor, and the air timer is within a certain range, then a midair jump can be executed. Here's how the code would look.

var jumping = false
...
    # inside physics_process
    if Input.is_action_just_pressed("jump"):
        if not floored and not jumping and coyoteTimer < coyoteTime:
            motion.y = jumpHeight
            jumping = true
        elif floored:
            motion.y = jumpHeight
            jumping = true
...
    # inside process
    if floored:
        jumping = false

Thanks very much! Your help is really appreciated! Maybe I would've had to give up before finishing the base controls, if I didn't get the help.

@SIsilicon28 I'll credit you in the Credits.html of my game (A document for both Web-browser display And In-game dispaly).

@bitshift-r Even though you didn't come up with the final solution to my problem (at least not one I could really comprehend, honestly), I still want to credit you in the same section of the Credits.html (Special Thanks to The Answers in the Godot Forum).

This will look like this (Credits are in alphabetical order):

××× Special Thanks to The Answers in the Godot Forum ×××

@bitshift-r (Bugfix for Coyote-Jump) @SIsilicon28 (Bugfix for Coyote-Jump)

You both can reply if you disagree about being credited or the way you're being credited, but by default you'll be credited. From now on (21st June 2020, 18:00 German Time), you have 2 days to post a comment about disagreeing before the Credits.html is adopted into the game's repository (At 23rd June 2020, 14:00 German Time)

@Sosasees said: @SIsilicon28 I'll credit you in the Credits.html of my game (A document for both Web-browser display And In-game dispaly). Oops! I forgot to ask wether you also want to be credited as a Godot Forums Moderator in my game credits.

It's fine. You can leave that out. Glad I can help! :)

@bitshift-r said: A credit is not necessary. Thanks.

Are you implying that you don't want to be credited?

EDIT: Ze's clearly not. When I typed this, I was a bit tired. I'd like to apologize for this rude sentence.

@bitshift-r @SIsilicon28 Do you want any more website links besides your name in the Credits? Currently, I have put your Godot Forums Profile Page as the Link besides your name in the credits.