Search code examples
animationgame-developmentgodotgdscriptgodot4

When to play animations in Godot 4 (GDScript)


When should I play animations and how to make sure an animation is finished before a different one is played. I am currently having a problem with my "punch" animation that only plays for a split second before it reverts back to the idle animation. This code is mostly testing things out so it is quite messy and I apologise.

extends CharacterBody2D

const SPEED = 130.0
const JUMP_VELOCITY = -600.0

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

@onready var animated_sprite = $AnimatedSprite2D
@onready var collision_top = $CollisionShape2DTop
@onready var raycast_crouch_1 = $RayCast2DCrouch1
@onready var raycast_crouch_2 = $RayCast2DCrouch2
@onready var hitbox = $AreaHitbox

var health = 100
var was_on_floor = false
var was_jumping_up = false
var land_timer = 0.0
var is_crouching = false
var crouching_under = false

var enemy_attack = false
var attack_cooldown = true
var alive = true

var is_punching = false

func _physics_process(delta):
    update_health()
    enemy_is_attacking()
    
    if health <= 0:
        alive = false
        get_tree().reload_current_scene()
    # Add the gravity.
    if not is_on_floor():
        velocity.y += gravity * delta

    # Handle jump.
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY
    
    if Input.is_action_just_pressed("crouch") and is_on_floor():
        crouch()
    elif Input.is_action_just_released("crouch"):
            if can_stand():
                stand()
            else:
                if not crouching_under:
                    crouching_under = true
        
    if crouching_under and  can_stand() and not Input.is_action_pressed("crouch"):
        stand()
        crouching_under = false
    
    # Get the input direction: -1, 0, 1
    var direction = Input.get_axis("move_left", "move_right")
    
    # Flip the sprite
    if direction > 0:
        animated_sprite.flip_h = false
    elif direction < 0:
        animated_sprite.flip_h = true

        # Handle punch
    if Input.is_action_just_pressed("punch"):
        
        is_punching = true
    
    # Determine animation state
    if is_punching:
        animated_sprite.play("punch")
        is_punching = false  # Punch animation is over
    elif is_on_floor() and not is_punching:
        if not was_on_floor:
            animated_sprite.play("land")
            land_timer = 0.1  # Play land animation for 0.1 seconds
        elif land_timer > 0:
            land_timer -= delta
        elif direction == 0:
            if is_crouching:
                animated_sprite.play("crouch")
            else:
                animated_sprite.play("idle")
        else:
            if is_crouching:
                animated_sprite.play("crouch")
            else:
                animated_sprite.play("run")
        was_on_floor = true
        was_jumping_up = false

    else:
        if velocity.y < 0:
            if not was_jumping_up:
                animated_sprite.play("jump_up")
                was_jumping_up = true
        elif velocity.y > -100 and velocity.y < 100:
            animated_sprite.play("jump_max")
        else:
            animated_sprite.play("jump_down")
        was_on_floor = false

    # Handle movement
    if direction:
        velocity.x = direction * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)

    move_and_slide()
    
func can_stand() -> bool:
    var result = not raycast_crouch_1.is_colliding() and not raycast_crouch_2.is_colliding()
    return result
    
func crouch():
        if is_crouching:
            return
        collision_top.disabled = true
        is_crouching = true

func stand():
    if is_crouching == false:
        return 
    collision_top.disabled = false
    is_crouching = false

func update_health():
    var health_bar = $HealthBar
    health_bar.value = health
    if health >= 100:
        health_bar.visible = false
    else:
        health_bar.visible = true
    
func player():
    pass
        
func _on_regen_timer_timeout():
    if health < 100:
        health = health + 10
        if health > 100:
            health = 100
    elif health <= 0:
        health = 0

func enemy_is_attacking():
    if enemy_attack and attack_cooldown:
        health -= 20
        attack_cooldown = false
        $DamageCooldown.start()
        print(health)

func _on_damage_cooldown_timeout():
    attack_cooldown = true


func _on_area_hitbox_body_entered(body):
    print("Entered")
    enemy_attack = true


func _on_area_hitbox_body_exited(body):
    if body.has_method("enemy"):
        enemy_attack = false

I tried adding timers, conditions, etc. but I cant seem to figure it out and I am not sure what is the right way. I have seen multiple examples using different methods on how to do it so I am not sure which is the right way.


Solution

  • Your animation will immediately finish, because you reset your is_punching variable to early. The play function of the AnimationSprite will not hold your code till the animation is finished. So by setting is_punching false there your code will branch into your normal behaviour on the next call.

    Instead you can use the signals of the AnimatedSprite. It has the signal animation_finished which is called every time a animation ends. You can connect a function to it through the editor in the right tree in the tab "Node".

    Or connect it through code like this (I did it in the ready function):

    func _ready():   
        animated_sprite.animation_finished.connect(self._on_animated_sprite_2d_animation_finished)
    

    In the function you can check, if the animation was your "punch" animation. And if so reset your boolean:

    func _on_animated_sprite_2d_animation_finished():
        if animated_sprite.animation == "punch":
            is_punching = false
    

    The rest of your code can stay the same to have a working punch animation. Just remove the is_punching = false part from the loop:

    # Determine animation state
    if is_punching:
        animated_sprite.play("punch")
    elif is_on_floor() and not is_punching: