Animations looping for seemingly no reason

Godot Version

4.0

Question

I’m creating an archer enemy and having some issues with the animations. I’m using a state machine for the enemy to handle what the enemy should do and what animations should play but Despite the state changing, the animation starts looping (See video) It happens when the enemy enters the attack state but the player leaves the attack range and the detection range (detect the player) before the animation is finished

Ground/movement state:

extends Enemy_State

#--------------------------
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var chase = false
var speed = 50
#---------------------------
@export var Idle_animation_node: String = "Idle"
@export var Run_animation_node: String = "Run"
@export var Attack_state: Enemy_State
@export var Flee_state: Enemy_State
@onready var attack_state = $"../Archer_attack_state"  # Get attack state reference
#--------------------------
@onready var animation_tree: AnimationTree = $"../../AnimationTree"
#-------------------------

func on_enter():
	playback.travel(Run_animation_node)
	speed = 50
	# If player is within 352 pixels, start chasing
	var distance_to_player = snapped(character.player.position.x - character.global_position.x, 1)
	chase = -352 < distance_to_player and distance_to_player < 352

# Called when the node enters the scene tree for the first time.
func _ready():
	animation_tree.active = true

func _physics_process(delta):
	if character.velocity.x == 0 and chase == false:
		playback.travel(Idle_animation_node)
	print(chase)
	animation_tree.set("parameters/Run/blend_position", snapped(character.direction.x, 1))
	if chase:
		playback.travel(Run_animation_node)
		character.velocity.x = character.direction.x * speed


	if not character.is_on_floor():
		character.velocity.y += gravity * delta

	if character.direction.x < 0:
		$"../../AnimatedSprite2D".flip_h = true
	else:
		$"../../AnimatedSprite2D".flip_h = false

	character.move_and_slide()

	# Enter flee state if too close
	var distance_to_player = snapped(character.player.position.x - character.global_position.x, 1)
	if -100 < distance_to_player and distance_to_player < 100:
		chase = false
		
		next_state = Flee_state

# Detects if the player is in range and starts running towards them
func _on_detection_area_body_entered(body):
	if body.is_in_group("Player_body"):
		chase = true

# Stops chasing the player
func _on_detection_area_body_exited(body):
	if body.is_in_group("Player_body"):
		chase = false
		character.velocity.x = 0
		attack_state.keep_attacking = false  # Stop attack loop

# If the player enters attack range, switch to attack state
func _on_start_attack_area_body_entered(body):
	if body.is_in_group("Player_body"):
		chase = false
		next_state = Attack_state

# If the player exits attack range, stop attacking
func _on_start_attack_area_body_exited(body):
	if body.is_in_group("Player_body"):
		attack_state.keep_attacking = false  # Stop attack loop

Attack state:

extends Enemy_State

#--------------------------------
@onready var timer: Timer = $Attack_timer
#--------------------------------
@export var Attack_animation: String = "Shoot"
@export var Ground_state: Enemy_State
@export var Flee_state: Enemy_State
#--------------------------------
var keep_attacking = false  # Track if the enemy should keep attacking
#--------------------------------

func on_enter():
	keep_attacking = true  # Allow continuous shooting
	attack()


func attack():
	if keep_attacking and timer.is_stopped():  # Only attack if the player is in range and the timer is ready
		playback.travel(Attack_animation)
		timer.start()

func _process(_delta):
	if not timer.is_stopped():
		character.velocity.x = 0


	var distance_to_player = snapped(character.player.position.x - character.global_position.x, 1)
	if -100 < distance_to_player and distance_to_player < 100:
		keep_attacking = false



func _on_attack_timer_timeout():
	if keep_attacking:  
		attack()  # Restart attack if the player is still in range
	else:
		next_state = Ground_state  # Return to ground state if no longer attacking

func _on_start_attack_area_body_exited(body):
	if body.is_in_group("Player_body"):
		keep_attacking = false  # Stop attacking when the player leaves attack range

Video (too large for the post):

Animation tree statemachine node setup:

Please ask if there’s anything else you need to see that’s needed to fix the issue
Any and all help is greatly appreciated :))

you have the video set so that people have to request to view it rather than anyone with the link is able to view it

My bad, it’s updated now

Thanks for lettting me know :slight_smile:

1 Like

Do you know for certain that the archer is actually exiting the attack state? you could create a print command in the section of code that is supposed to stop them from attacking to see if they ever reach that state?

Yeah, I’ve checked with a print command and it seems to just be the animation that doesn’t transition when the state does it (or there’s something that makes the animation loop)

i’ve done a little bit on 2D animation and had a problem with looping animations, i managed to fix it by using a signal from the animated sprite that tells then sets the sprite to its default/idle state when it loops an animation

image
its this signal here

1 Like

The animation dosen’t appear to loop in a way that triggers the signal

i didnt make use of the animation state trees so im not totally sure,


but heres the code i have to reset my sprites to their default state

1 Like