Jorge García
Jorge García

Reputation: 21

Godot jumping animation just plays the first frame

I have a script in my simple platformer game which says that if my player is in the ground and "Z" is pressed, his movement in the Y axis is going to go up to 600, and if he's not in the ground, he's going to perform the jump animation.

So here's the thing, I know it plays only the first frame of the jumping animation because the code is constantly detecting that the player is in the air. I want a way to tell the code to trigger the animation only once.

I tried using a function called input_(event): but it seems that it doesn't have a is_action_just_pressed type of Input, just is_action_pressed.

I'm fairly new to Godot and don't know how to use signals. Signals might help via animation_finished(), although that function might have nothing to do with what I actually want to do in my code.

Here's my code:

extends KinematicBody2D
#Variables van aquí 

var movimiento = Vector2();
var gravedad = 20; 
var arriba = Vector2(0, -1);
var velocidadMax = 600;
var fuerza_salto = -600;
var aceleracion = 5;
var saltando = false;
func _ready(): # Esto es void Start()
    pass;

func _physics_process(delta): #Esto es void Update() 

    movimiento.y += gravedad;


    if Input.is_action_pressed("ui_right"):
        $SonicSprite.flip_h = true;
        $SonicSprite.play("Walk");
        movimiento.x = min(movimiento.x +aceleracion, velocidadMax);


    elif Input.is_action_pressed("ui_left"):
        $SonicSprite.flip_h = false;
        $SonicSprite.play("Walk");
        movimiento.x = max(movimiento.x-aceleracion, -velocidadMax);

    else:
        movimiento.x = lerp(movimiento.x, 0, 0.09);
        $SonicSprite.play("Idle");


    if is_on_floor():
        if Input.is_action_just_pressed("z"):
            movimiento.y = fuerza_salto;
    else:
        $SonicSprite.play("Jump");

    movimiento = move_and_slide(movimiento, arriba)

Upvotes: 2

Views: 3230

Answers (3)

Pablo Fonovich
Pablo Fonovich

Reputation: 21

I had the smae problem, and it was solved for me adding the next after move_and_slide():

if velocity.y == 0:
    velocity.y = 10

Apparently, if velocity is 0 after move_and_slide() it does not detect is_on_floor() anymore (bug?) so i added small velocity in direction of gravity.

About usinginput_(event), you do not need just_pressed because it only process when there is an input.. you can do somethin like this:

func _input(event):
    if event.is_action_pressed("ui_up") and is_on_floor():
        velocity.y = jump_speed

What i called velocity, i think in your script is called movimiento. Also, i think you are mixing gravities and velocities in movimiento. I'm sharing you my character scrpit so you can compare and see if it works better:

extends KinematicBody2D

var Bullet = preload("res://Bullet.tscn")

var speed = 200
var jump_speed = -300
var shot_speed = 100
var velocity = Vector2()
var grav = 980
var shooting = false

func _ready():
    $AnimatedSprite.play()
    $AnimatedSprite.connect("animation_finished",self,"on_animation_finished")
func _input(event):
    if event.is_action_pressed("ui_up") and is_on_floor():
        velocity.y = jump_speed
    if event.is_action_pressed("shoot") and !shooting:
        shooting = true
        shot_speed = 20
        velocity.y = -200
        fire_weapon()

func _physics_process(delta):
    velocity.x = 0
    if Input.is_action_pressed("ui_right"):
        velocity.x += speed
    if Input.is_action_pressed("ui_left"):
        velocity.x -= speed
    if velocity.length() > 0:
        if velocity.x < 0:
            $AnimatedSprite.flip_v = true
            $AnimatedSprite.rotation_degrees = 180 
        elif velocity.x > 0:
            $AnimatedSprite.flip_v = false
            $AnimatedSprite.rotation_degrees = 0 
    if shooting:
        $AnimatedSprite.animation = "shot"
        velocity.x = -shot_speed * cos($AnimatedSprite.rotation_degrees)
        shot_speed *= 0.98
    else:
        if is_on_floor():
            if velocity.x == 0:
                $AnimatedSprite.animation = "idle"
            else:
                $AnimatedSprite.animation = "moving"
        else:
            $AnimatedSprite.animation = "jumping"

    velocity.y += grav * delta
    velocity = move_and_slide(velocity, Vector2(0,-1))
    if velocity.y == 0:
        velocity.y = 10

func on_animation_finished():
    if $AnimatedSprite.animation == "shot":
        shooting = false

func fire_weapon():
    var bullet = Bullet.instance()
    get_parent().add_child(bullet)
    if $AnimatedSprite.flip_v :
        bullet.position = $ShotLeft.global_position
    else:
        bullet.position = $ShotRight.global_position
    bullet.rotation_degrees = $AnimatedSprite.rotation_degrees
    bullet.linear_velocity = Vector2(1500 * cos(bullet.rotation),1500*sin(bullet.rotation))

Notice that i do not use gravity as a velocity or moviemiento; instead y multiply it by delta for getting velocity, as acceleration x time gives velocity.

I hope this helps.

Upvotes: 2

Minraws
Minraws

Reputation: 46

Let's dissect what's happening here, you fire up the code and ask your player to play the animation in one frame and then again in the other and in the next as long as the correct key is pressed or you in the correct state.

What the problem, the problem is that at every call of the function play it fires the animation again and again so it just restarts or another call runs separately and this causes unexpected behaviour.

The better way will be to manage the state of the player and animation player simultaneously and using that to perform animation calls.

enum State { IDLE, WALK, RUN, JUMP, INAIR, GROUNDED } ## This is to manage the player state
var my_state = State.IDLE  ## Start the player with the idle state

As for the animation player state use the signal that you were talking about using the GUI or with code like below.

get_node("Animation Player").connect("animation_finished", this, "method_name")
## Here I assume that you have the Animation Player as the Child of the Node you the script on

And also hold a boolean variable to tell if the animation is playing or not.

if ( animation_not_playing and (case)):
    animation_player.play("animation")

Turn it true or false as per your liking. Based on the animation finished signal.

In future, you might want to consider even using a simple FSM to maintain all this state data and variables.

Upvotes: 1

Kobrar
Kobrar

Reputation: 184

This kind of problem can be solved using a Finite State Machine to manage your character controls and behaviour. You would play the Jump animation on entering the Jumping state from the Walking state. Utilizing correct design patterns early prevents spaghetti code in the future.

This is not a Godot specific solution, but definitely one worth your attention.

Upvotes: 1

Related Questions