Interrupting an animation prematurely when it normally plays until end

Godot Version

godot 3.5

Question

this one has been bugging me for a while so i think i need help. I finally busted open an old game i was making (hence it being in 3.5) and i’m trying to do an interrupted function, where when a player hits an enemy or vice versa, it also interrupts the other party’s attack. I’ve got a little bit of interrupted code, but i’ve googled and I’m at a loss of how to preumaturely end an animation like an attack animation, that normally plays until end.
I feel like I probably just have the wrong search terms, but if anyone else knows how to help that would be good.

Here is waht I have os far for code (its not much)

#this is the func we call to make it so that whoever was hit cannot do another hting
#until their interrupted timer passes
func interrupted():
	var sprites_to_freeze = [0] #fuck it it doesn't even need to be all sprites
	#we need to freeze the node
	#NOW I am thinking lets freeze indivdiual parts...
	#if we want a knockback... 
	can_move = false
	sprites_to_freeze = check_nodes_types_and_return_sprites()
	#okay this dont work in an array :X so we must loop
	for i in sprites_to_freeze.size():
		sprites_to_freeze[i].self_modulate = Color(0, 0, 1)

right now, as you can see, it just freezes th sprites, but does not make the one hit restart their animation.

here is the full script, though i think tahts the relevant part up there.

extends KinematicBody2D

var velocity = Vector2(0,0)
export var maxHealth = 10.0 #use for diffiuclty lvl mods
export var currentHealth = 10.0 #use for difficulty lvl mods
export var damage_done = 1.0#affects how much the user (this script) does in damage
export var damage_taken = 1.0 #for difficultymods. affects damage reduction
var knockback_smidge_value = 3.0 #totally guessed the value. we use this when deciding
#which way to knock the combatant - diagnoally or in 4 directions
export var is_pushable = true
#use knockback amount also for diffuclty lvl mods
export var knockback_amount = 200 #this affects how much WE (the combatant ) are knocked back
#NOT how much the combatant knocks other people back!!!!
var maxHealth_modded
var currentHealth_modded
var damage_done_modded
var damage_taken_modded
var knockback_modded
var charge_speed_modded
var attack_speed_modded
export var team = "enemy"
enum directions {DOWN, RIGHT, UP, LEFT, UPRIGHT, UPLEFT, DOWNRIGHT, DOWNLEFT}
var facing = directions.DOWN
var prev_facing = facing
var can_take_damage = false 
var is_in_safezone = false
var can_move = true

onready var immunity_timer = $ImmunityTimer
onready var interrupted_timer = $InterruptedTimer
onready var healthbar_total = $HealthBarTotal
onready var healthbar_current = $HealthBarTotal/HealthBarCurrent
onready var healthbar_new = $HealthBarNew
#thinking we're gonna need a placeholder sprite for tihs
#and then whenever we are changing the sprite in code on the player
#or enemy. we use this name, to modulate it!
#onready var sprite_holder = $Sprite
onready var test_sprite = $TestSprite

# Called when the node enters the scene tree for the first time.
func _ready():
	#do we update difficulty mods here? Or manually on player and enemies?
	#maybe i DO have to do it in the parent.... (here)
	update_difficulty_lvl()

#we need to update health bar when we HEAL too!!!!
#function for taking damage should be here!
#so ... when we take damage we ALSO need to stop our current action!
func take_damage(var damage):
	currentHealth_modded -= (damage * damage_taken_modded)
	update_small_healthbar(currentHealth_modded) 
	#if we're dead, we dont need the timers!
	if currentHealth_modded <= 0:
		on_death()
	else: 
		#knockback willb e called from the player GIVING damage, not here
		#call interrupted to interrupt them
		if interrupted_timer != null:
			interrupted()
			immune()
			interrupted_timer.start()
			immunity_timer.start()
		else:
			print ("Interrupted timer was null")

#dont think i've added custom knockback functionality yet. may add later. 
#called in area entereds, on teh player/ item giving damage
func knockback(var dam_pos):
	#first we need to compare the abs value. make sure that there is big
	#enough difference to actually make it diagnonal (as opposed to just 4 directoinal)
	var x_dif = self.position.x - dam_pos.x
	var y_dif = self.position.y - dam_pos.y
	var _abs_x_dif = abs(x_dif)
	var abs_y_dif = abs(y_dif) #could do this in just two lines with abs around the expressoin
	#but i noob and wnat it readable
	if self.global_position.x < dam_pos.x: 
		#if the damage comes FROM the right. 
		if abs_y_dif <= knockback_smidge_value:
			#we are in range. knocke em to the left
			#it looks like we used move_and_slide for the player and moveable enemies
			velocity.x += -knockback_amount
		elif self.global_position.y < dam_pos.y:
			#the damage comes FROM down direction
			#knock em upleft
			velocity.x += -knockback_amount
			velocity.y += -knockback_amount
		else:
			#its not in smidge and not from down, it must be from up.
			#knock em downleft
			velocity.x += -knockback_amount
			velocity.y += knockback_amount
	elif self.global_position.x > dam_pos.x:
		#damage came from the LEFT
		if abs_y_dif <= knockback_smidge_value:
			#knock em right
			velocity.x += knockback_amount
		elif self.global_position.y < dam_pos.y:
			#damage comes FROM down directoin 
			#knock them up right
			velocity.x += knockback_amount
			velocity.y += -knockback_amount
		else:
			#dmaage should be coming from up. 
			#knock em downright
			velocity.x += knockback_amount
			velocity.y += knockback_amount
	elif self.global_postiion.y < dam_pos.y:
		#damage coming FROM down. 
		#we dont need to check for x smidge value as we started w/ x logic
		#and its elfi
		#knock em up
		velocity.y += -knockback_amount
	else:
		#knocek em donw
		velocity.y += knockback_amount


#this is the func we call to make it so that whoever was hit cannot do another hting
#until their interrupted timer passes
func interrupted():
	var sprites_to_freeze = [0] #fuck it it doesn't even need to be all sprites
	#we need to freeze the node
	#NOW I am thinking lets freeze indivdiual parts...
	#if we want a knockback... 
	can_move = false
	sprites_to_freeze = check_nodes_types_and_return_sprites()
	#okay this dont work in an array :X so we must loop
	for i in sprites_to_freeze.size():
		sprites_to_freeze[i].self_modulate = Color(0, 0, 1)

#This lasts longer than interrupted
func immune():
	can_take_damage = false
	var sprites = [0]
	sprites = check_nodes_types_and_return_sprites()
	for i in sprites.size():
		sprites[i].self_modulate = Color(0, 0, 1, .5)
	# we need to make the node see through or a slightly different color
	#to clarify they cannot take damage now
	#sprite_holder.self_modulate.a = 0.5 #jsut a test value

#function for actually updating our little health bar
func update_small_healthbar(new_value):
	healthbar_new.value = new_value
#	var transform_vector = Vector2(currentHealth_modded/ maxHealth_modded, 1)
#	#we need to make it anchord to one side
#	#could use set offset. 
#	#need to do some math probably to make it work though
#	#when current health gets smaller, the amount of room by the bar is increased. which does make sense
#	#ugh what kidn of math do i need to do to make this right
#	#accoridng to print staement, bigger negative numbers numbers = more to the right
#	#no thats just when it gets smaller. oops. because it is CENTERED
#	var offset_vec = Vector2((0.5 * -maxHealth_modded), 0)
#	print ("from character parent script and update small healthbar func, offset vec is")
#	print (offset_vec)
#	healthbar_current.set_offset(offset_vec)
#	healthbar_current.set_scale(transform_vector)

#for destroying ourself if we die?
#or at least stopping our movement
func on_death():
	set_physics_process(false) 
	set_process(false)
	#play the animation for dying
	#or we could just fade out? IG

func _on_ImmunityTimer_timeout():
	can_take_damage = true #now we can take damage again
	var sprites = [0]
	sprites = check_nodes_types_and_return_sprites()
	for i in sprites.size():
		#this null check is very important. otherwise it bugs 
		#cuz of the first null sprite!
		if sprites[i] != null:
			sprites[i].self_modulate = Color(1, 1, 1, 1)
	
#so ...w e need to get ourself and all our children
#and see which are sprite nodes
#nad then return tehm!
func check_nodes_types_and_return_sprites():
#	var array_of_children = [] #hahah crap we're gonna have an off by oen error
#	var num_of_children
	var array_of_sprites = [test_sprite]
	#cuz of godots weird way to set arrays
	for i in self.get_children():
		#array_of_children.append(i)
		if i is Sprite:
			array_of_sprites.append(i)
	#alrighty... test sprite is STILL null WTF. 
	#but w/es. ill deal with tha tlater
	#print (array_of_sprites)
	return array_of_sprites
	#minus one is testing for off by one errors. I THINK
#	num_of_children = array_of_children.size() - 1
#	for n in range(0, num_of_children):


#DONT Delete this
#okay im a dumb dumb. i acutally need to get facing dir for raycasting code to work
#as it uses the facing dir to decide which way to cast the ray
#so this won't work on enemies at times?
#but it always works on player 
#It LOOKS like when the enemy collides with the player, then they cannot get facing!
func get_facing_dir():
	#if we are moving, we can change the facing dir
	if velocity != Vector2(0,0):
		#if we are moving positive in x dir
		if velocity.x > 0.1:
			#and not moving on y dir
			if velocity.y == 0:
				facing = directions.RIGHT
			#if we are moving positive in y and x (remmeber, pos y is down)
			elif velocity.y > 0.1:
				facing = directions.DOWNRIGHT
			elif velocity.y < -0.1:
				facing = directions.UPRIGHT
		#if we are moving left
		elif velocity.x < -0.1:
			if velocity.y == 0:
				facing = directions.LEFT
			elif velocity.y < -0.1:
				facing = directions.UPLEFT
			elif velocity.y > 0.1:
				facing = directions.DOWNLEFT
		elif velocity.y < -0.1 and velocity.x == 0:
			facing = directions.UP
		elif velocity.y > 0.1 and velocity.x == 0:
			facing = directions.DOWN
		#we enter else sometimes. need to find out why!
#		else: 
#			print("could not get facing direction")

#we cannot simply use velocity
#because the entity may have zero velocity
#but still be facing in a direction
func get_facing_dir_as_vector():
	match facing:
		directions.DOWN:
			return Vector2(0,1)
		directions.UP:
			return Vector2(0,-1)
		directions.RIGHT:
			return Vector2(1,0)
		directions.LEFT:
			return Vector2(-1,0)
		directions.UPRIGHT:
			return Vector2(1,-1).normalized()
		directions.UPLEFT:
			return Vector2(-1,-1).normalized()
		directions.DOWNRIGHT:	
			return Vector2(1,1).normalized()
		directions.DOWNLEFT:	
			return Vector2(-1,1).normalized()


#we need a way to update our variables.... for difficulty mods
func update_difficulty_lvl():
	if team == "player":
		#do player stuff
		#do we have to laod current health and max health differently?
		#based on IF the player or enemy already has helath off or not?
		#I THINK SO
		if currentHealth_modded == maxHealth_modded:
			maxHealth_modded = maxHealth * DifficultyManager.p_health_mod
			currentHealth_modded = maxHealth
			healthbar_new.max_value = maxHealth_modded
			healthbar_new.value = currentHealth_modded
		#we need to check for IF they have already been modified... hmmm 
		#maybe i need hte bases as variables and hten teh modified variants as varialbes
		#that seems overly complicated but lets try it
		damage_done_modded = damage_done * DifficultyManager.p_damage_mod
		damage_taken_modded = damage_taken * DifficultyManager.p_damage_per_taken
		knockback_modded = knockback_amount * DifficultyManager.p_knocked_back
		attack_speed_modded = 1.0 #multiplicative
		charge_speed_modded = 1.0
	elif team == "enemy":
		#do enemy stuff:
		if currentHealth_modded == maxHealth_modded:
			maxHealth_modded = maxHealth * DifficultyManager.e_health_mod
			currentHealth_modded = maxHealth_modded
			healthbar_new.max_value = maxHealth_modded
			healthbar_new.value = currentHealth_modded
		damage_done_modded = damage_done * DifficultyManager.e_damage_mod
		damage_taken_modded = damage_taken * DifficultyManager.e_damage_per_taken
		knockback_modded = knockback_amount * DifficultyManager.e_knocked_back
		#for some reason attack speed modded is being an ITEM/ node, NOT a number!!!!
		#so is charge speed, fyi
		attack_speed_modded = DifficultyManager.e_attack_speed
		charge_speed_modded = DifficultyManager.e_charge_speed
	else:
		#probably frienld. do nothing
		pass
	
#our save dict func
func save():
	var save_dict = {
		"filename" : get_filename(),
		"parent" : get_parent().get_path(),
		"pos_x" : position.x, #Vector 2 is not supported by JSON
		"pos_y" : position.y,
		"maxHealth" : maxHealth,
		"currentHealth" : currentHealth,
		"maxHealth_modded": maxHealth_modded,
		"currentHealth_modded": currentHealth_modded,
		"team" : team
	}
	return save_dict


func _on_InteruptedTimer_timeout():
	var sprites_to_unfreeze = [0] #fuck it it doesn't even need to be all sprites
	can_move = true
	sprites_to_unfreeze = check_nodes_types_and_return_sprites()
	#print ("Now we will print the sprites we are UNfreezing")
	#print (sprites_to_unfreeze)
	set_process(true)
	set_physics_process(true)
	for i in sprites_to_unfreeze.size():
		sprites_to_unfreeze[i].self_modulate = Color(1, 1, 1, .5)


EDit; sorry realized i have moer info. I did override the player and enemy interrupted functions (they inherit from teh same combatant script)

here is the enemies overrode f unction:

func interrupted():
	state = states.APPROACHTOATTACK
	animation_tree.advance(0)#trhis doens't seem to be working :< ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	#(hit box is in the attack anim)
	var sprites_to_freeze = [] #fuck it it doesn't even need to be all sprites
	#we need to freeze the node
	can_move = false
	sprites_to_freeze = check_nodes_types_and_return_sprites()
	#okay this dont work in an array :X so we must loop
	for i in sprites_to_freeze.size():
		sprites_to_freeze[i].self_modulate = Color(0, 0, 1)

and here is the players :

func interrupted():
	var sprites_to_freeze = [] #fuck it it doesn't even need to be all sprites
	#we need to freeze the node
	can_move = false
#	set_process(false)
#	set_physics_process(false)
	sprites_to_freeze = check_nodes_types_and_return_sprites()
	#print ("Now we will print the sprites we are freezing")
	#print (sprites_to_freeze)
	#okay this dont work in an array :X so we must loop
	for i in sprites_to_freeze.size():
		sprites_to_freeze[i].self_modulate = Color(0, 0, 1)
	idle_sprite.visible = true
	attack_sprite.visible = false
	run_sprite.visible = false
	animation_state.travel("Idle")

What are you animating and how?

oh sorry. i realize i should have clarified. I’m animation in 2d, with a sprite sheet for each animation. I’'m using animation player to animate hit boxes and stuff, and animation tree for switchin between animations and such.
did that answer your question?

All animation facilities in Godot have the stop() function.

ah okeyday :3 ill look into implementin that then, ill let post back here if i have problems with it

alright i THINK thats working. Im gonna keep testing with an enemy that will have longer wind up for attacks - its a lil hard for me to time it right atm :stuck_out_tongue:

edit: TY BTW