How to change state of enemy? It continues to repeat a function

Godot Version

4.2.2

Question

In the following code, I have used “await ap.animation_finished” when the enemy is in “ATTACK state” before player.take_damage(1) then change state to “MOVE state” to check the collision and if player.is_attacking.

However, it does not change state after enter the “ATTACK state” it continues to give (repeat?) player damage (func player.take_damage) even after player die()

player script:
@export var health = 40
var max_health = 4

func _ready():
health = max_health
add_to_group(“player”)

func take_damage(damage_amount):
if can_take_damage:
health -= damage_amount
if health <=0:
die()
if is_dead:
return
health -= amount
if health <= 0:
die()

func die():
if is_dead:
return

is_dead = true
velocity = Vector2.ZERO
ap.play("death")

queue_free()

code for enemy script:
extends CharacterBody2D
@onready var sprite = $Sprite2D
@onready var player = %Player
@onready var health = 20
@onready var rayCast = $RayCast2D
@onready var rayCast2 = $RayCast2D2

var move_direction
var wander_time : float

enum{
IDLE,
HURT,
ATTACK,
MOVE,
DEATH
}

var state = MOVE
@onready var ap = $AnimationPlayer

func _ready():
randomize_wander()
pass

func _physics_process(delta):
add_to_group(“enemy”)

match state: 
	IDLE:
		ap.play("idle")
		print("IDLE state")
	HURT:
		ap.play("hurt")
		health -= 10
		print("HURT! enemy health is ", health)
		if health > 0:
			state = MOVE
			print("HURT to MOVE, health > 0")
		else: 
			state = DEATH
			print("HURT to DEATH, health <= 0")
	**ATTACK: **

** ap.play(“attack”)**
** #player take damage after animation finish**
** await ap.animation_finished**
** player.take_damage(1)**
** print("ATTACK, player health is ", player.health)**
** #change state to MOVE to check ray cast collision**
** state = MOVE**
** print(“ATTACK to MOVE state”)**

	MOVE:
		ap.play("move")
		
		if wander_time > 0:
			wander_time -= delta
		
		#change state to attack if player is colliding with rayCast (left and right)
		if rayCast.is_colliding() or rayCast2.is_colliding():
			# Get the collider object from the ray cast
			var collider1 = rayCast.get_collider()
			var collider2 = rayCast2.get_collider()
			# Check if either collider is part of the "player" group
			if (collider1 and collider1.is_in_group("player")) or (collider2 and collider2.is_in_group("player")):
				print("player is collising with ray cast")
				if player.is_attacking:
					state = HURT
					print("player is attaking, enemy MOVE to HURT state")
				else: 
					state = ATTACK
					print("player is not attacking, enemy MOVE to ATTACK state")
		#move
		velocity = move_direction * 50
		move_and_slide()
		randomize_wander()
	DEATH:
		ap.play("death") #in ap play "death", after animation_finished, will queue_free()
		print("enemy DEATH")

func randomize_wander(): #randomize wander for MOVE state for x-axis (move left / right)
move_direction = Vector2(randf_range(-1, 1), 0).normalized()
wander_time = randf_range(1, 3)

if velocity.x < 0:
	sprite.flip_h = false
else:
	sprite.flip_h = true
pass

Output:
player is collising with ray cast
player is not attacking, enemy MOVE to ATTACK state
ATTACK, player health is 3
ATTACK to MOVE state
ATTACK, player health is 2
ATTACK to MOVE state
ATTACK, player health is 1
ATTACK to MOVE state
ATTACK, player health is 0
ATTACK to MOVE state
ATTACK, player health is -1
ATTACK to MOVE state
ATTACK, player health is -2

await and _physics_process don’t mesh well together, Godot is going to run this function ever frame including the animation play and await. Change their state before awaiting the animation.

Also make sure to paste scripts with proper formatting

1 Like

I tried taking it out of the _physical_process
but it still plays per frame, even the randomize_wonder() also updates every frame so the enemy looks like it can’t decide where to go and keeps ‘glitching’

I commented out the timer for the ap because it gave error: invalid get index ‘create’ (on base: “SceneTree”)

below is the revised code:

extends CharacterBody2D

@onready var sprite = $Sprite2D
@onready var player = %Player
@onready var rayCast = $RayCast2D
@onready var rayCast2 = $RayCast2D2
@onready var ap = $AnimationPlayer
@onready var progress_bar = $EnemyProgressBar

@onready var health = 50
@onready var move_direction 
@onready var wander_time : float

enum{ 
	IDLE,
	HURT, 
	ATTACK, 
	MOVE, 
	DEATH
}

var state = IDLE


func _ready():
	randomize_wander()
	pass
	
func _physics_process(delta):
	add_to_group("enemy")
	match state: 
		IDLE:
			idle()
		MOVE:
			if wander_time > 0:
				wander_time -= delta
			move()
		HURT:
			hurt()
		ATTACK: 
			attack()
		DEATH:
			death()

func idle():
	
	print("IDLE state")
	#change state to attack if player is colliding with rayCast (left and right)
	if rayCast.is_colliding() or rayCast2.is_colliding():
		# Get the collider object from the ray cast
		var collider1 = rayCast.get_collider()
		var collider2 = rayCast2.get_collider()
		# Check if either collider is colliding with "player" group
		if (collider1 and collider1.is_in_group("player")) or (collider2 and collider2.is_in_group("player")):
			print("player is collising with ray cast")
			if player.is_attacking: #if player is within range and is attacking, the enemy will go into HURT state and receive damage 
				state = HURT
				print("player is attaking, enemy IDLE to HURT state")
			else: #player within range and is not attacking, then emeny will attack the player and give damage to player
				state = ATTACK 
				print("player is not attacking, enemy IDLE to ATTACK state")
	else: 
		state = MOVE #change state to MOVE to randomize wonder if no player within range
		ap.play("idle")
#		await get_tree().create.timer(0.9).timeout
		await ap.animation_finished
	
func move():
	print("MOVE state")
	#move
	velocity = move_direction * 50
#	move_and_slide()
	randomize_wander()
	state = IDLE
	print("MOVE to IDLE")
	ap.play("move")
#	await get_tree().create.timer(0.6).timeout
	await ap.animation_finished
	
func randomize_wander(): #randomize wander for MOVE state for x-axis (move left / right)
	print("randomize_wonder")
	move_direction = Vector2(randf_range(-1, 1), 0).normalized()
	wander_time = randf_range(1, 3)
	if velocity.x < 0:
		sprite.flip_h = false
	else:
		sprite.flip_h = true
	pass

func attack():
	print("ATTACK state, player initial health is ", player.health)
	player.take_damage(1)
	print("ATTACK, player health is ", player.health)
	#change state to IDLE to check ray cast collision
	state = IDLE
	print("ATTACK to IDLE")
	#play attack animation
	ap.play("attack")
#	await get_tree().create.timer(1.2).timeout
	#player take damage after animation finish
	await ap.animation_finished

func hurt():
	print("HURT state. initial health is ", health)
	health -= 2
	progress_bar.value = health
	print("HURT! enemy health is ", health)
	if health > 0:
		state = IDLE
		print("HURT to IDLE, health > 0")
	else: 
		state = DEATH
		print("HURT to DEATH, health <= 0")
	ap.play("hurt")
#	await get_tree().create.timer(0.5).timeout
	await ap.animation_finished

func death():
	print("DEATH state")
	print("enemy DEATH")
	ap.play("death") #in ap play "death", after animation_finished, will queue_free()
#	await get_tree().create.timer(2.3).timeout
	queue_free()

Output

IDLE state
Something entered the frag
MOVE state
randomize_wonder
MOVE to IDLE
IDLE state
MOVE state
randomize_wonder
MOVE to IDLE
IDLE state
MOVE state
randomize_wonder
MOVE to IDLE
IDLE state
player is collising with ray cast
player is attaking, enemy IDLE to HURT state
HURT state. initial health is 50
HURT! enemy health is 48
HURT to IDLE, health > 0

There isn’t anything stopping the state machine from transitioning to and from ATTACK or HURT to IDLE and back every frame. Instead of creating timers every frame make a Timer node that prevents state changes until it is stopped.

1 Like

how to prevent the state change when have the Timer node? I tried adding a Timer node, but it still run every frame.

Another thing is, the defeated_enemy variable which keeps counts the number of enemies the player kills resets after each kill (and because it runs every frame, the count keeps adding till queue_free which adds up to about 140)

extends CharacterBody2D

@onready var sprite = $Sprite2D
@onready var player = %Player
@onready var rayCast = $RayCast2D
@onready var rayCast2 = $RayCast2D2
@onready var ap = $AnimationPlayer
@onready var progress_bar = $EnemyProgressBar
@onready var timer = $Timer

@onready var health = 50
@onready var move_direction 
@onready var wander_time : float
@onready var defeated_enemy = Global.get("defeated_enemy")

enum{ 
	IDLE,
	HURT, 
	ATTACK, 
	MOVE, 
	DEATH
}

var state = IDLE

func _ready():
	randomize_wander()
	timer.timeout.connect(_on_timer_timeout)  # Connect the timer's timeout signal to a custom function
	pass
	
func _physics_process(delta):
	add_to_group("enemy")
	match state: 
		IDLE:
			idle()
		MOVE:
			if wander_time > 0:
				wander_time -= delta
			move()
		ATTACK: 
			attack()
		HURT:
			hurt()
		DEATH:
			death()

func idle():
	
	print("IDLE state")
	#change state to attack if player is colliding with rayCast (left and right)
	if rayCast.is_colliding() or rayCast2.is_colliding():
		# Get the collider object from the ray cast
		var collider1 = rayCast.get_collider()
		var collider2 = rayCast2.get_collider()
		# Check if either collider is colliding with "player" group
		if (collider1 and collider1.is_in_group("player")) or (collider2 and collider2.is_in_group("player")):
			print("player is collising with ray cast")
			if player.is_attacking: #if player is within range and is attacking, the enemy will go into HURT state and receive damage 
				state = HURT
				print("player is attaking, enemy IDLE to HURT state")
			else: #player within range and is not attacking, then emeny will attack the player and give damage to player
				state = ATTACK 
				print("player is not attacking, enemy IDLE to ATTACK state")
		else: 
			state = MOVE 
			print("ray cast colliding, not with player, enemy IDLE to MOVE")
	else: 
		
		ap.play("idle")
#		await get_tree().create.timer(0.9).timeout
#		await ap.animation_finished
		state = MOVE #change state to MOVE to randomize wonder if no player within range
#		timer.start(0.9)  # Start the timer to delay the state change
#		await timer.timeout  # Wait for the timer to finish
	
func move():
	
	print("MOVE state")
	ap.play("move")
	
#	timer.start(wander_time)  # Start the timer to delay the state change
	await ap.animation_finished
	
	#move
	velocity = move_direction * 50
	
	randomize_wander()
	
#	await timer.timeout  # Wait for the timer to finish
	state = IDLE
	print("MOVE to IDLE")
#	await get_tree().create.timer(0.6).timeout
	
func randomize_wander(): #randomize wander for MOVE state for x-axis (move left / right)
	print("randomize_wonder")
	move_direction = Vector2(randf_range(-1, 1), 0).normalized()
	wander_time = randf_range(1, 3)
	move_and_slide()
	if velocity.x < 0:
		sprite.flip_h = false
	else:
		sprite.flip_h = true
	pass

func attack():
	print("ATTACK state, player initial health is ", player.health)
	player.take_damage(1)
	print("ATTACK, player health is ", player.health)
	#change state to IDLE to check ray cast collision
	state = IDLE
	print("ATTACK to IDLE")
	#play attack animation
	ap.play("attack")
#	await get_tree().create.timer(1.2).timeout
	timer.start(1.2)  # Start the timer to delay the state change
	await timer.timeout  # Wait for the timer to finish
	#player take damage after animation finish
	await ap.animation_finished

func hurt():
	print("HURT state. initial health is ", health)
	health -= 2
	progress_bar.value = health
	print("HURT! enemy health is ", health)
	if health > 0:
		state = IDLE
		print("HURT to IDLE, health > 0")
	else: 
		state = DEATH
		print("HURT to DEATH, health <= 0")
	ap.play("hurt")
	timer.start(0.5)  # Start the timer to delay the state change
	await timer.timeout  # Wait for the timer to finish
#	await get_tree().create.timer(0.5).timeout
	await ap.animation_finished

func death():
	print("DEATH state")
	print("enemy DEATH")
	player.health = player.max_health #restore player health after enemy dies
	defeated_enemy += 1 #add counter for lever
	print("defeated enemy is ", defeated_enemy)
	ap.play("death") #in ap play "death", after animation_finished, will queue_free()
	timer.start(2.3)  # Start the timer to delay the state change
	await timer.timeout  # Wait for the timer to finish
#	await get_tree().create.timer(2.3).timeout
	queue_free()
	
func _on_timer_timeout():
	pass # Replace with function body.

Something like this, less awaits

# Check if either collider is colliding with "player" group
if timer.is_stopped() and (collider1 and collider1.is_in_group("player")) or (collider2 and collider2.is_in_group("player")):

added the if statement to manage state change per frame, but its not working. It still plays per frame and is stuck at IDLE state. When player enter raycast, attack animation plays, but no damage taken and no damage is given (when player is attacking)
I tried using while statement because I thought if use if statement it may read it once and pass it because it is false (timer is still running). Outcome is the same as if statement

extends CharacterBody2D

@onready var sprite = $Sprite2D
@onready var player = %Player
@onready var rayCast = $RayCast2D
@onready var rayCast2 = $RayCast2D2
@onready var ap = $AnimationPlayer
@onready var progress_bar = $EnemyProgressBar
@onready var timer = $Timer

@onready var health = 50
@onready var move_direction 
@onready var wander_time : float
@onready var global = Global

enum{ 
	IDLE,
	HURT, 
	ATTACK, 
	MOVE, 
	DEATH
}

var state = IDLE

func _ready():
	randomize_wander()
	timer.timeout.connect(_on_timer_timeout)  # Connect the timer's timeout signal to a custom function
	pass
	
func _physics_process(delta):
	add_to_group("enemy")
	match state: 
		IDLE:
			idle()
		MOVE:
			if wander_time > 0:
				wander_time -= delta
			move()
		ATTACK: 
			attack()
		HURT:
			hurt()
		DEATH:
			death()

func idle():
	ap.play("idle")
	print("IDLE state")
	timer.start(0.9)  # Start the timer to delay the state change
	#change state to attack if player is colliding with rayCast (left and right)
	if rayCast.is_colliding() or rayCast2.is_colliding():
		# Get the collider object from the ray cast
		var collider1 = rayCast.get_collider()
		var collider2 = rayCast2.get_collider()
		# Check if either collider is colliding with "player" group
		if timer.is_stopped() and (collider1 and collider1.is_in_group("player")) or (collider2 and collider2.is_in_group("player")):
			print("player is collising with ray cast")
			if player.is_attacking: #if player is within range and is attacking, the enemy will go into HURT state and receive damage 
				state = HURT
				print("player is attaking, enemy IDLE to HURT state")
			else: #player within range and is not attacking, then emeny will attack the player and give damage to player
				state = ATTACK 
				print("player is not attacking, enemy IDLE to ATTACK state")
	else: 
		
		if timer.is_stopped():
			print("timer stopped and ray cast not colliding, state change from IDLE to MOVE")
			state = MOVE
#		await timer.timeout
#		await get_tree().create.timer(0.9).timeout
#		await ap.animation_finished
		#while !timer.is_stopped(): #timer still running
			#if timer.is_stopped(): #timer is stopeed
				#print("IDLE to MOVE state timer is stopped")
				#state = MOVE #change state to MOVE to randomize wonder if no player within range


#		await timer.timeout  # Wait for the timer to finish
	
func move():
	timer.start(wander_time)  # Start the timer to delay the state change
	print("MOVE state")
	ap.play("move")
	
#	await ap.animation_finished
	#move
	randomize_wander()
#	await timer.timeout  # Wait for the timer to finish
	if timer.is_stopped():
		state = IDLE
		print("MOVE to IDLE")
#	await get_tree().create.timer(0.6).timeout
	
func randomize_wander(): #randomize wander for MOVE state for x-axis (move left / right)
	print("randomize_wonder")
	move_direction = Vector2(randf_range(-1, 1), 0).normalized()
	wander_time = randf_range(1, 3)
	velocity = move_direction * 50
	move_and_slide()
	if velocity.x < 0:
		sprite.flip_h = false
	else:
		sprite.flip_h = true
	pass

func attack():
	ap.play("attack")
	timer.start(1.2)  # Start the timer to delay the state change

	if timer.is_stopped():
		print("ATTACK state, player initial health is ", player.health)
		player.take_damage(1)
		print("ATTACK, player health is ", player.health)
		#change state to IDLE to check ray cast collision
		state = IDLE
		print("ATTACK to IDLE")
	#play attack animation
#	await get_tree().create.timer(1.2).timeout
#	await timer.timeout  # Wait for the timer to finish
	#player take damage after animation finish
#	await ap.animation_finished

func hurt():
	timer.start(0.5)  # Start the timer to delay the state change
	if timer.is_stopped(): 
		print("HURT state. initial health is ", health)
		health -= 2
		progress_bar.value = health
		print("HURT! enemy health is ", health)
		if health > 0:
			state = IDLE
			print("HURT to IDLE, health > 0")
		else: 
			state = DEATH
			print("HURT to DEATH, health <= 0")
		ap.play("hurt")
#	await timer.timeout  # Wait for the timer to finish
#	await get_tree().create.timer(0.5).timeout
#	await ap.animation_finished

func death():
	timer.start(2.3)  # Start the timer to delay the state change
	ap.play("death") #in ap play "death", after animation_finished, will queue_free()
	print("DEATH state")
	if timer.is_stopped(): 
		player.health = player.max_health #restore player health after enemy dies
		global.defeated_enemy += 1 #add counter for lever
		print("defeated enemy is ", global.defeated_enemy)
#		await timer.timeout  # Wait for the timer to finish
#		await get_tree().create.timer(2.3).timeout
		queue_free()
	
func _on_timer_timeout():
	pass # Replace with function body.

You need to start the timers after assesing they are false.

if timer.is_stopped(): # assess we can transition state
    state = IDLE
    timer.start(3) # lock transitioning states

Still accessing every frame

extends CharacterBody2D

@export var gravity = 30

@onready var sprite = $Sprite2D
@onready var player = %Player
@onready var rayCast = $RayCast2D
@onready var rayCast2 = $RayCast2D2
@onready var ap = $AnimationPlayer
@onready var progress_bar = $EnemyProgressBar
@onready var timer = $Timer

@onready var health = 50
@onready var move_direction 
@onready var wander_time : float
@onready var global = Global

enum{ 
	IDLE,
	HURT, 
	ATTACK, 
	MOVE, 
	DEATH
}

var state = IDLE

func _ready():
	randomize_wander()
	timer.timeout.connect(_on_timer_timeout)  # Connect the timer's timeout signal to a custom function
	timer.start(3) #start initial state change controller timer
	pass
	
func _physics_process(delta):
	if not is_on_floor():
		velocity.y += gravity * delta
	else: 
		velocity.y = 0
	add_to_group("enemy")
	match state: 
		IDLE:
			idle()
		MOVE:
			if wander_time > 0:
				wander_time -= delta
			move()
		ATTACK: 
			attack()
		HURT:
			hurt()
		DEATH:
			death()

func idle():
	print("IDLE state")
	#change state to attack if player is colliding with rayCast (left and right)
	if rayCast.is_colliding() or rayCast2.is_colliding():
		# Get the collider object from the ray cast
		var collider1 = rayCast.get_collider()
		var collider2 = rayCast2.get_collider()
		# Check if either collider is colliding with "player" group
		if (collider1 and collider1.is_in_group("player")) or (collider2 and collider2.is_in_group("player")):
#			print("player is collising with ray cast")
			if player.is_attacking: #if player is within range and is attacking, the enemy will go into HURT state and receive damage 
				if timer.is_stopped(): # assess we can transition state
					state = HURT
				timer.start(3) # lock transitioning statesstate = HURT
				print("player is attaking, enemy IDLE to HURT state")
			else: #player within range and is not attacking, then emeny will attack the player and give damage to player
				if timer.is_stopped(): # assess we can transition state
					state = ATTACK 
				timer.start(3)
				print("player is not attacking, enemy IDLE to ATTACK state")
		else: 
			if timer.is_stopped(): # assess we can transition state
				state = MOVE 
			timer.start(3)
			print("ray cast colliding, not with player, enemy IDLE to MOVE")
	else: 
		
		ap.play("idle")
#		await get_tree().create.timer(0.9).timeout
#		await ap.animation_finished
		if timer.is_stopped(): # assess we can transition state
			state = MOVE 
		timer.start(3)
		print("IDLE to MOVE state")
#		timer.start(0.9)  # Start the timer to delay the state change
#		await timer.timeout  # Wait for the timer to finish
	
func move():
	print("MOVE state")
	ap.play("move")
#	timer.start(wander_time)  # Start the timer to delay the state change
#	await ap.animation_finished
	
	#move
	velocity = move_direction * 50
	randomize_wander()
	if timer.is_stopped(): # assess we can transition state
		state = IDLE
	timer.start(3) # lock transitioning states
#	await timer.timeout  # Wait for the timer to finish
	#state = IDLE
	print("MOVE to IDLE")
#	await get_tree().create.timer(0.6).timeout
	
func randomize_wander(): #randomize wander for MOVE state for x-axis (move left / right)
	print("randomize_wonder")
	move_direction = Vector2(randf_range(-1, 1), 0).normalized()
	wander_time = randf_range(1, 3)
	move_and_slide()
	if velocity.x < 0:
		sprite.flip_h = false
	else:
		sprite.flip_h = true
	pass

func attack():
#	print("ATTACK state, player initial health is ", player.health)
	player.take_damage(1)
	print("ATTACK, player health is ", player.health)
	#change state to IDLE to check ray cast collision
	
	if timer.is_stopped(): # assess we can transition state
		state = IDLE
	timer.start(3) # lock transitioning states
	print("ATTACK to IDLE")
	#play attack animation
	ap.play("attack")
#	await get_tree().create.timer(1.2).timeout
#	timer.start(1.2)  # Start the timer to delay the state change
#	await timer.timeout  # Wait for the timer to finish
	#player take damage after animation finish
#	await ap.animation_finished

func hurt():
#	print("HURT state. initial health is ", health)
	health -= 2
	progress_bar.value = health
#	print("HURT! enemy health is ", health)
	if health > 0:
		if timer.is_stopped(): # assess we can transition state
			state = IDLE
		timer.start(3) # lock transitioning states
		print("HURT to IDLE, health > 0")
	else: 
		if timer.is_stopped(): # assess we can transition state
			state = DEATH
		timer.start(3) # lock transitioning states
		print("HURT to DEATH, health <= 0")
	ap.play("hurt")
	#timer.start(0.5)  # Start the timer to delay the state change
	#await timer.timeout  # Wait for the timer to finish
#	await get_tree().create.timer(0.5).timeout
	#await ap.animation_finished

func death():
#	print("DEATH state")
#	print("enemy DEATH")
	ap.play("death") #in ap play "death", after animation_finished, will queue_free()
	timer.start(2.3)  # Start the timer to delay the state change
	global.defeated_enemy += 1 #add counter for lever
	print("defeated enemy is ", global.defeated_enemy)
	player.health = player.max_health #restore player health after enemy dies
	await timer.timeout  # Wait for the timer to finish
	queue_free()
	
func _on_timer_timeout():
	pass # Replace with function body.

Got the solution. The functions was being called in the _physics_process(delta) which runs every frame. So, initiated the code using check_state() in _ready() and after time I change the state

check_state() is where I put the match cases to change the states and run the functions for the appropriate state.

extends CharacterBody2D

@onready var sprite = $Sprite2D
@onready var player = %Player
@onready var rayCast = $RayCast2D
@onready var rayCast2 = $RayCast2D2
@onready var ap = $AnimationPlayer
@onready var progress_bar = $EnemyProgressBar

@onready var health = 2
@onready var move_direction 
@onready var wander_time : float
@onready var global = Global

enum{ 
	IDLE,
	HURT, 
	ATTACK, 
	MOVE, 
	DEATH
}

var state = IDLE

func _ready():
	randomize_wander()
	check_state()
	pass
	
func _physics_process(delta):
	add_to_group("enemy")
	if wander_time > 0:
		wander_time -= delta
		
func check_state():
	match state: 
		IDLE:
			idle()
		MOVE:
			move()
		ATTACK: 
			attack()
		HURT:
			hurt()
		DEATH:
			death()

func idle():
	if rayCast.is_colliding() or rayCast2.is_colliding():
		# Get the collider object from the ray cast
		var collider1 = rayCast.get_collider()
		var collider2 = rayCast2.get_collider()
		# Check if either collider is colliding with "player" group
		if (collider1 and collider1.is_in_group("Player")) or (collider2 and collider2.is_in_group("Player")):
			print("is colliding with player")
			if player.is_attacking: #if player is within range and is attacking, the enemy will go into HURT state and receive damage 
				state = HURT
				check_state()
			else: #player within range and is not attacking, then emeny will attack the player and give damage to player
				state = ATTACK
				check_state() 
		else: 
			state = MOVE 
			check_state()
	else: 
		ap.play("idle")
		await ap.animation_finished
		state = MOVE #change state to MOVE to randomize wonder if no player within range
		check_state()
#
func move():
	ap.play("move")
	await ap.animation_finished
	randomize_wander()
	state = IDLE
	check_state()
	
func randomize_wander(): 
	move_direction = Vector2(randf_range(-1, 1), 0).normalized()
	wander_time = randf_range(3, 5)
	velocity = move_direction * 50
	move_and_slide()
	if velocity.x < 0:
		sprite.flip_h = false
	else:
		sprite.flip_h = true
	pass

func attack():
	ap.play("attack")
	player.take_damage(1)
	await ap.animation_finished
	state = IDLE
	check_state()

func hurt():
	ap.play("hurt")
	health -= 1
	progress_bar.value = health
	await ap.animation_finished
	if health > 0:
		state = IDLE
		check_state()
	else: 
		state = DEATH
		check_state()

func death():
	ap.play("death") #in ap play "death", after animation_finished, will queue_free()
	player.health = player.max_health #restore player health after enemy dies
	global.defeated_enemy += 1 #add counter for lever
	await ap.animation_finished  
	queue_free()
	

Thank you @gertkeno for helping and being patient with me :heart:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.