Search code examples
gdscriptgodot4

Why jump animation is not working in Godot 4?


I've made controller and it works but jump animation is not working. idk what to do with that. I made jump and walk anims not loop. Pls answer the qustion (Do not write about Vector 3) The problem is only in anims.

func _input(event):
    
    if Input.is_action_pressed("light plus"):
        LIGHTLVL.light_energy += 0.1
    if Input.is_action_pressed("light minus"):
        LIGHTLVL.light_energy -= 0.1
        
    if LIGHTLVL.light_energy > 1:
        LIGHTLVL.light_energy = 1
    if LIGHTLVL.light_energy < 0:
        LIGHTLVL.light_energy = 0

func _physics_process(delta):
    # Add the gravity.
    if not is_on_floor():
        velocity.y -= gravity * delta

    # Handle Jump.
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Get the input direction and handle the movement/deceleration.
    # As good practice, you should replace UI actions with custom gameplay actions.
    var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    if direction:
        velocity.x = direction.x * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        
    if Input.is_action_pressed("ui_right"):
        $AnimatedSprite2D.play("Walk")
        $AnimatedSprite2D.flip_h = 0
        
    elif Input.is_action_pressed("ui_left"):
        $AnimatedSprite2D.play("Walk")
        $AnimatedSprite2D.flip_h = 1
        
    elif Input.is_action_just_pressed("ui_accept") and is_on_floor():
        $AnimatedSprite2D.play("Jump")
        
    else:
        if !$AnimatedSprite2D.is_playing():
            $AnimatedSprite2D.play("Idle")
            
    move_and_slide()

Solution

  • Given that you jumped with:

        if Input.is_action_just_pressed("ui_accept") and is_on_floor():
            velocity.y = JUMP_VELOCITY
    

    If the execution flow is not entering here:

        elif Input.is_action_just_pressed("ui_accept") and is_on_floor():
            $AnimatedSprite2D.play("Jump")
    

    It must be because the execution flow is entering one of the preceding conditionals:

        if Input.is_action_pressed("ui_right"):
            $AnimatedSprite2D.play("Walk")
            $AnimatedSprite2D.flip_h = 0
            
        elif Input.is_action_pressed("ui_left"):
            $AnimatedSprite2D.play("Walk")
            $AnimatedSprite2D.flip_h = 1
    

    So, for example, if you are holding "ui_right" or "ui_left" and press "ui_accept", then the character will jump, but the play animation will not play because the blocks for the walk animation take precedence.

    Presumably you want the jump to take precedence, then check for jump first:

        if Input.is_action_just_pressed("ui_accept") and is_on_floor():
            $AnimatedSprite2D.play("Jump")
    
        elif Input.is_action_pressed("ui_right"):
            $AnimatedSprite2D.play("Walk")
            $AnimatedSprite2D.flip_h = 0
            
        elif Input.is_action_pressed("ui_left"):
            $AnimatedSprite2D.play("Walk")
            $AnimatedSprite2D.flip_h = 1
                   
        else:
            if !$AnimatedSprite2D.is_playing():
                $AnimatedSprite2D.play("Idle")
    

    Addendum:

    I'm interesting in conveying how to approach these problems. So before I go into it, a couple things:

    • Yes, the code will get complex. Not everything will be a couple lines.
    • You got the right idea in separating the logic for the motion and the logic for animation. However, ideally you would not be reading input twice.

    walk anim plays after the 1st frame when jump

    Since you the code was using is_action_just_pressed which will only be true for one frame (physics frame in this case), it makes sense that after that frame the execution flow will go to the walk animation.

    Thus, when exactly should it be playing the walk animation? The first thing that comes to mind is that it should be only when the character is on the floor.

    That suggest to add a check for is_on_floor() so that the walking animation does not play in the air. Which suggest that is it better to reorganize the checks around is_on_floor() since jump has it too.

    That will be my approach, with some extra considerations:

    • The character might fall (and thus not be on the floor) without jumping.
    • I'd expect the character to flip even in the air.
    • Let us have it read input only once.

    Something to be aware of: Your code for choosing animations is before move_and_slide. It is move_and_slide what updates is_on_floor(). It also updates velocity based on what it collides with.

    So before move_and_slide the character will have have some downwards vertical velocity because of gravity regardless if the character is on the floor.... Unless it jumped.


    Let us start with the flip. It is unclear to me why you are setting an int, but I believe this does the same you were doing:

    $AnimatedSprite2D.flip_h = 1 if input_dir.x < 0.0 else 0
    

    If you can set a bool, it would be this:

    $AnimatedSprite2D.flip_h = input_dir.x < 0.0
    

    Note that here I'm assuming that the direction is controlled by input, not by velocity. However, you might prefer to have velocity control it:

    $AnimatedSprite2D.flip_h = velocity.x < 0.0
    

    I'll keep using velocity below (which has the added advantage that the character will stop its animation when it stops moving, barring the fact the code is before move_and_slide as mentioned above). However, at least for the horizontal, you could use input_dir instead.


    About the animations, I remind you that the character could be in the air because it jumped or because it is falling. The simpler approach is to only change animations on the ground.

    if is_on_floor():
        # ON THE FLOOR
        if velocity.y > 0.0:
            # ON THE FLOOR AND GOING UP
            $AnimatedSprite2D.play("Jump")
        elif is_zero_approx(velocity.x):
            # ON THE FLOOR AND STATIONARY
            $AnimatedSprite2D.play("Idle")
        else:
            # ON THE FLOOR AND MOVING HORIZONTALLY
            $AnimatedSprite2D.play("Walk")
    

    If you wanted to have a falling animation, then you have a decision to make: when does it switch to it?

    • It could be in any airborne downwards motion:

      if is_on_floor():
          # …
      else:
          if velocity.y < 0.0:
              $AnimatedSprite2D.play("Fall")
      
    • It could be only when not jumped, so it does not play in the downwards part of the jump:

      We need a variable to store if the character jumped (be aware that you will want to change to an int if you want to support double-jump or any other limited multi-jump):

      var jumped := false
      

      Which means we have to set it to true when jumping, and to false when on the ground and not jumping (be aware that if you want to implement coyote time, or jump buffering you will have to change this code):

      if is_on_floor():
          if Input.is_action_just_pressed("ui_accept"):
              velocity.y = JUMP_VELOCITY
              jumped = true
          else:
              jumped = false
      

      And finally we can apply the animation:

      if is_on_floor():
          # …
      else:
          if not jumped:
              $AnimatedSprite2D.play("Fall")
      
    • Or it could be after falling for a while.

      For this you want to compute the height at which it will start falling. So it is a similar setup than above:

      @export var falling_offset := 0.0
      var fall_height := 0.0
      
      if is_on_floor():
          fall_height = global_position.y + falling_offset
          if Input.is_action_just_pressed("ui_accept"):
              velocity.y = JUMP_VELOCITY
      
      if is_on_floor():
          # …
      else:
          if global_position.y > fall_height:
              $AnimatedSprite2D.play("Fall")
      

    And yes, presumably you have all three. And yes, the character controller can get much more complex.