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

SosaseesSosasees Posts: 54Member
edited July 4 in 2D

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:


Tags :

Comments

  • bitshift-rbitshift-r Posts: 55Member

    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.

  • SosaseesSosasees Posts: 54Member

    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:

  • bitshift-rbitshift-r Posts: 55Member

    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.

  • SosaseesSosasees Posts: 54Member

    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.

  • bitshift-rbitshift-r Posts: 55Member

    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.

  • SosaseesSosasees Posts: 54Member

    @bitshift-r said:
    func _physics_process(delta: float) -> void:

    What does -> void mean?

  • SIsilicon28SIsilicon28 Posts: 714Moderator

    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.

  • SIsilicon28SIsilicon28 Posts: 714Moderator
    edited June 21

    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?

  • SosaseesSosasees Posts: 54Member

    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"
    
  • SIsilicon28SIsilicon28 Posts: 714Moderator

    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
    
    
  • SosaseesSosasees Posts: 54Member

    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.

  • SosaseesSosasees Posts: 54Member
    edited June 21

    @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)

  • SosaseesSosasees Posts: 54Member

    @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.

  • SIsilicon28SIsilicon28 Posts: 714Moderator
    edited June 21

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

  • bitshift-rbitshift-r Posts: 55Member

    A credit is not necessary. Thanks.

  • SosaseesSosasees Posts: 54Member
    edited June 23

    @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.

  • SosaseesSosasees Posts: 54Member
    edited June 23

    @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.

  • SIsilicon28SIsilicon28 Posts: 714Moderator

    I'm fine with how it is. :)

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file