AnimationTree animations loop incorrectly

Godot Version

v4.2.stable.official [46dc27791]

Question

When the player flies, I’m trying to:

  • Start with the jetpack intro anim
  • Loop the main jetpack animation while jump button is held
  • Play jetpack outro anim when jump button is released

This is done with the AnimationTree.

However, rather than loop the main jetpack animation, it cycles through the intro, loop, and outro before repeating the cycle.

I’m suspecting this is due to the code playing the animations being in a _physics_process() function. The problem is that I only need the code to travel to the jetpack intro once while also checking for the jump input constantly.

Here’s my AnimationTree:

Here’s my player code (even though most of the code isn’t relevant to my issue, I’m still including it for the bigger picture):

# Player camera is its own seperate scene.

extends CharacterBody2D

const SPEED = 600.0
const JUMP_VELOCITY = -40.0
@export_range(0.0, 1.0) var friction = 0.3
@export_range(0.0 , 1.0) var acceleration = 0.25

var directionX = 1
var bumped = false
var alive = true
var jumping = false
var grabbing = false
var launched = false
@export var state = 1

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

# Nodes
@onready var ray_cast_left = $RayCastLeft
@onready var ray_cast_right = $RayCastRight
@onready var animated_sprite = $AnimatedSprite2D
@onready var animation_player = $AnimationPlayer

# Sounds
@onready var mach_sound = $MachSound
@onready var bump_sound = $BumpSound
@onready var death_scream_sound = $DeathScreamSound
@onready var explosion_sound = $ExplosionSound
@onready var electric_sound = $ElectricSound
@onready var hurt_sound = $HurtSound

# Anim state machine
var run_speed = 80.0

@onready var state_machine = $AnimationTree["parameters/playback"]

# Signals
signal damage_taken()
signal boing_boing()

func bump(dir_x, ray_cast, flip):
	# Variables!
	ray_cast.enabled = false
	mach_sound.playing = false
	bump_sound.playing = true
	bumped = true
	# Play bump
	animated_sprite.play("bump")
	await get_tree().create_timer(0.3).timeout
	# Flip it around
	directionX = dir_x
	animated_sprite.flip_h = flip
	animated_sprite.play("fly")
	# More variables!
	mach_sound.playing = true
	bumped = false
	# Enable raycast after 1 sec; DON'T PUT VARIABLES BELOW THIS!!!
	await get_tree().create_timer(0.1).timeout
	ray_cast.enabled = true

func death(kill_type):
	print("lmao")
	set_collision_mask_value(1, false)
	
	# No longer a living Noise to make some Noise
	alive = false
	velocity.y = -400
	launched = true
	mach_sound.playing = false

	state_machine.travel("death")
	
	# Noise and Noise Torture - Now with sounds!
	# 0 = default
	if kill_type == 0:
		hurt_sound.playing = true
		death_scream_sound.playing = true
		explosion_sound.playing = true
	# 1 = death by outlet
	elif kill_type == 1:
		hurt_sound.playing = true
		death_scream_sound.playing = true
		electric_sound.playing = true
		explosion_sound.playing = true

func jump():
	jumping = true
	grabbing = false
	state_machine.travel("boxxed_jetpackintro")
	velocity.y += JUMP_VELOCITY
	
func grab():
	jumping = false
	grabbing = true
	state_machine.travel("boxxed_hitstart")
	#var grab_speed = 1000 * animated_sprite.scale.x
	#velocity.x = grab_speed
	#velocity.y = 0
	await get_tree().create_timer(1).timeout
	grabbing = false

func _physics_process(delta):
	if state == 0:
		# Bump and turn stuff
		if ray_cast_left.is_colliding() and alive:
			bump(1, ray_cast_left, false)
		elif ray_cast_right.is_colliding() and alive:
			bump(-1, ray_cast_right, true)
		
		# Add the gravity.
		
		var directionY = Input.get_axis("move_up", "move_down")
		if not bumped and alive:
			velocity.y = directionY * (SPEED / 2)
			#print("HEEHEE")
		elif not alive and launched:
			velocity.y += gravity * delta
			#print(velocity.y)
		elif alive and not launched:
				velocity.y = 0
				#print("LOL")
			
		# Get the input direction and handle the movement/deceleration.
		var directionX = Input.get_axis("move_left", "move_right")
		if alive:
			velocity.x = (directionX * SPEED) + (acceleration * 150)
		else:
			velocity.x = (-directionX * SPEED)
		
		# Fly anim FPS
		if abs(velocity.x) == SPEED:
			animated_sprite.sprite_frames.set_animation_speed("fly", 24)
		elif abs(velocity.x) > SPEED:
			animated_sprite.sprite_frames.set_animation_speed("fly", 30)
		elif abs(velocity.x) < SPEED:
			animated_sprite.sprite_frames.set_animation_speed("fly", 18)
		
		move_and_slide()
	elif state == 1:
		velocity.y += gravity * delta
		var dir = 0
		if grabbing:
			dir = 0
		else:
			dir = Input.get_axis("move_left", "move_right")
		if alive and not grabbing:
			if dir != 0:
				velocity.x = lerp(velocity.x, dir * SPEED, acceleration)
			else:
				velocity.x = lerp(velocity.x, 0.0, friction)
		elif alive and grabbing:
			velocity.x = 500 * animated_sprite.scale.x
		else:
			velocity.x = (-directionX * SPEED)
		
		if alive:
			# Handle animations.
			if velocity.x != 0:
				animated_sprite.scale.x = sign(velocity.x)
			
			if not grabbing:
				if abs(velocity.x) >= 10 and is_on_floor():
					state_machine.travel("boxxed_walk")
				elif not is_on_floor() and not Input.is_action_pressed("jump"):
					state_machine.travel("boxxed_fall")
				#elif not is_on_floor() and Input.is_action_pressed("jump"):
					#state_machine.travel("boxxed_jetpackintro")
				elif is_on_floor() and not Input.is_action_pressed("jump"):
					state_machine.travel("boxxed_idle")
			
			# Handle misc actions
			if Input.is_action_pressed("jump"):
				jump()
				#velocity.y += JUMP_VELOCITY
				
			if Input.is_action_pressed("grab"):
				grab()
				#grabbing = true
				#state_machine.travel("boxxed_hitstart")
				#var grab_speed = 1000 * animated_sprite.scale.x
				#velocity.x = grab_speed
				#velocity.y = 0
				#await get_tree().create_timer(1).timeout
				#grabbing = false
			
		move_and_slide()

Here’s the link to a video of the improper AnimationTree.

Figured this one out on my own!

I ended up using variables and signals to make the animation only play once.

Player code (AnimationTree was not changed):

# Player camera is its own seperate scene.

extends CharacterBody2D

const SPEED = 600.0
const JUMP_VELOCITY = -40.0
@export_range(0.0, 1.0) var friction = 0.3
@export_range(0.0 , 1.0) var acceleration = 0.25

var directionX = 1
var bumped = false
var alive = true
var jumping = false
var grabbing = false
var launched = false
@export var state = 1

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

# Nodes
@onready var ray_cast_left = $RayCastLeft
@onready var ray_cast_right = $RayCastRight
@onready var animated_sprite = $AnimatedSprite2D
@onready var animation_player = $AnimationPlayer

# Sounds
@onready var mach_sound = $MachSound
@onready var bump_sound = $BumpSound
@onready var death_scream_sound = $DeathScreamSound
@onready var explosion_sound = $ExplosionSound
@onready var electric_sound = $ElectricSound
@onready var hurt_sound = $HurtSound

# Anim state machine
var run_speed = 80.0

@onready var state_machine = $AnimationTree["parameters/playback"]

# Signals
signal damage_taken()
signal jump_signal()
signal grab_signal()

func bump(dir_x, ray_cast, flip):
	# Variables!
	ray_cast.enabled = false
	mach_sound.playing = false
	bump_sound.playing = true
	bumped = true
	# Play bump
	animated_sprite.play("bump")
	await get_tree().create_timer(0.3).timeout
	# Flip it around
	directionX = dir_x
	animated_sprite.flip_h = flip
	animated_sprite.play("fly")
	# More variables!
	mach_sound.playing = true
	bumped = false
	# Enable raycast after 1 sec; DON'T PUT VARIABLES BELOW THIS!!!
	await get_tree().create_timer(0.1).timeout
	ray_cast.enabled = true

func death(kill_type):
	print("lmao")
	set_collision_mask_value(1, false)
	
	# No longer a living Noise to make some Noise
	alive = false
	velocity.y = -400
	launched = true
	mach_sound.playing = false

	state_machine.travel("death")
	
	# Noise and Noise Torture - Now with sounds!
	# 0 = default
	if kill_type == 0:
		hurt_sound.playing = true
		death_scream_sound.playing = true
		explosion_sound.playing = true
	# 1 = death by outlet
	elif kill_type == 1:
		hurt_sound.playing = true
		death_scream_sound.playing = true
		electric_sound.playing = true
		explosion_sound.playing = true

func jump():
	jumping = true
	grabbing = false
	state_machine.travel("boxxed_jetpackintro")

	
func grab():
	jumping = false
	grabbing = true
	state_machine.travel("boxxed_hitstart")
	#var grab_speed = 1000 * animated_sprite.scale.x
	#velocity.x = grab_speed
	#velocity.y = 0
	await get_tree().create_timer(1).timeout
	push_warning("skibidi")
	grabbing = false

func _physics_process(delta):
	if state == 0:
		# Bump and turn stuff
		if ray_cast_left.is_colliding() and alive:
			bump(1, ray_cast_left, false)
		elif ray_cast_right.is_colliding() and alive:
			bump(-1, ray_cast_right, true)
		
		# Add the gravity.
		
		var directionY = Input.get_axis("move_up", "move_down")
		if not bumped and alive:
			velocity.y = directionY * (SPEED / 2)
			#print("HEEHEE")
		elif not alive and launched:
			velocity.y += gravity * delta
			#print(velocity.y)
		elif alive and not launched:
				velocity.y = 0
				#print("LOL")
			
		# Get the input direction and handle the movement/deceleration.
		var directionX = Input.get_axis("move_left", "move_right")
		if alive:
			velocity.x = (directionX * SPEED) + (acceleration * 150)
		else:
			velocity.x = (-directionX * SPEED)
		
		# Fly anim FPS
		if abs(velocity.x) == SPEED:
			animated_sprite.sprite_frames.set_animation_speed("fly", 24)
		elif abs(velocity.x) > SPEED:
			animated_sprite.sprite_frames.set_animation_speed("fly", 30)
		elif abs(velocity.x) < SPEED:
			animated_sprite.sprite_frames.set_animation_speed("fly", 18)
		
		move_and_slide()
	elif state == 1:
		if jumping:
			velocity.y += JUMP_VELOCITY
		else:
			velocity.y += gravity * delta
		print(velocity.y)
			
		var dir = 0
		if grabbing:
			dir = 0
		else:
			dir = Input.get_axis("move_left", "move_right")
		if alive and not grabbing:
			if dir != 0:
				velocity.x = lerp(velocity.x, dir * SPEED, acceleration)
			else:
				velocity.x = lerp(velocity.x, 0.0, friction)
		elif alive and grabbing:
			velocity.x = 500 * animated_sprite.scale.x
		else:
			velocity.x = (-directionX * SPEED)
		
		if alive:
			# Handle animations.
			if velocity.x != 0:
				animated_sprite.scale.x = sign(velocity.x)
			
			if not grabbing:
				if abs(velocity.x) >= 10 and is_on_floor():
					state_machine.travel("boxxed_walk")
				elif not is_on_floor() and not Input.is_action_pressed("jump"):
					state_machine.travel("boxxed_fall")
				#elif not is_on_floor() and Input.is_action_pressed("jump"):
					#state_machine.travel("boxxed_jetpackintro")
				elif is_on_floor() and not Input.is_action_pressed("jump"):
					state_machine.travel("boxxed_idle")
			
			# Handle misc actions
			if Input.is_action_pressed("jump") and not jumping and not grabbing:
				emit_signal("jump_signal")
				#velocity.y += JUMP_VELOCITY
				
			if Input.is_action_pressed("grab") and not grabbing:
				emit_signal("grab_signal")
				#grabbing = true
				#state_machine.travel("boxxed_hitstart")
				#var grab_speed = 1000 * animated_sprite.scale.x
				#velocity.x = grab_speed
				#velocity.y = 0
				#await get_tree().create_timer(1).timeout
				#grabbing = false
				
			if Input.is_action_just_released("jump"):
				jumping = false
				push_warning("yup")
		
		move_and_slide()