Only first frame of jump/fall animation is playing in Godot

Viewed 373

So I was trying to write a code for double jump in godot, which I managed to do, but for some reason, when I jump it only plays the first frame of the animation

    if is_on_floor():
        if on_ground == false:
            on_ground = true
            jump_count = 0
    else:   
        if on_ground == true:
            on_ground = false
            jump_count = 1
        if velocity.y < 0:
            $Sprite.play("jump")
        else:
            $Sprite.play("fall")

Here is the whole code just in case


const SPEED = 100
const GRAVITY = 8
const JUMPFORCE = -150
const FLOOR = Vector2.UP

var velocity = Vector2(0, 0)
var on_ground = false
var jump_count = 0
var should_change_animation = true

func _physics_process(_delta):
    if Input.is_action_pressed("right"):
        velocity.x = SPEED
        $Sprite.play("run")
        $Sprite.flip_h = false
    elif Input.is_action_pressed("left"):
        velocity.x = -SPEED
        $Sprite.play("run")
        $Sprite.flip_h = true
    else:
        $Sprite.play("idle")
    
    if is_on_floor():
        if on_ground == false:
            on_ground = true
            jump_count = 0
    else:   
        if on_ground == true:
            on_ground = false
            jump_count = 1
        if velocity.y < 0:
            $Sprite.play("jump")
        else:
            $Sprite.play("fall")

    velocity.y = velocity.y + GRAVITY
    
    if Input.is_action_just_pressed("jump"):
        if jump_count < 2:
            jump_count += 1
            velocity.y = JUMPFORCE
            on_ground = false

    velocity = move_and_slide(velocity, FLOOR)
    
    velocity.x = lerp(velocity.x, 0, 0.1)

Is there a way to fix this?

1 Answers

This code runs at the start of every physics frame:

    if Input.is_action_pressed("right"):
        velocity.x = SPEED
        $Sprite.play("run")
        $Sprite.flip_h = false
    elif Input.is_action_pressed("left"):
        velocity.x = -SPEED
        $Sprite.play("run")
        $Sprite.flip_h = true
    else:
        $Sprite.play("idle")

As a result, every physics frame stars by setting the sprite animation to either "idle" or "run".


I would go on about separation of concerns (Which I suggest you read about). I would argue you should refactor the code, separating the motion logic from the visual logic. Currently they are interleaved. It would convenient to have all the decisions about something together and not mixed with others. So, for example, if the wrong animation is playing, there is only one place you need to look, because there is only one place you play animations. I would rewrite your entire code if you let me at it… But, your code is still small, you can afford a little mess, so I'll let you at it.


For this particular case, you need to consider: What are the conditions to apply an animation?

For instance, it is necessary that the player pressed a direction to play the "run" animation. But it is not sufficient, the avatar must be on the floor too. You can add conditionals to that effect.


By the way, lerp(velocity.x, 0, 0.1) is equivalent to this velocity.x * 0.9. I believe there is some tutorial teaching some cargo cult. You don't need lerp here. Anyway, the effect is (frame depended) deceleration, the avatar does not stop immediately (and how quickly it stops depends on the physics frame rate).

However you would be changing to the "idle" animation once there is no input, regardless if the avatar is in the air (which is relevant for this question), or if the avatar is still moving.


Thus, bottom line is: do not pick the animation based on input. Instead pick animation based on the state of the avatar, in particular if it is on the floor and the value of velocity.

Related