Need help on state machine

Godot Version

4.4

Question

Hi! This is my first time creating a state machine for a game and I’m having some difficulty making the enemy AI switch states. The state machine relies on a series of timers to switch between the following states: {IDLE, SCATTER, CHASE, EVADE, EATEN} and once one timer for a state times out, it should switch to another state. My issue is that when the timer expires, the AI will not switch states and will instead be trapped in a loop of the same state. I have gone over my code repeatedly and I cannot seem to find a solution, although I’m sure that its very obvious, and I was wondering if I could get some help here. Below I’ve included the edited code for my enemy node and for the GameManager node, which contains the timers that should cause the enemy to switch states

GameManager

#code cut for readability

#handle timers
var scatter_timer_active : bool = false
var chase_timer_active : bool = false
var evade_timer_active : bool = false


func _process(delta: float) -> void:

		#code cut for readability

func _on_chase_timer_timeout() -> void:
	chase_timer_active = false
	get_tree().call_group("Ghosts", "scatter_mode")
	$Timers/ScatterTimer.start()

func _on_scatter_timer_timeout() -> void:
	scatter_timer_active = false
	get_tree().call_group("Ghosts", "chase_mode")
	print("Timeout!")
	$Timers/ChaseTimer.start()

func _on_evade_timer_timeout() -> void:
	evade_timer_active = false
	get_tree().call_group("Ghosts", "scatter_mode")
	$Timers/ScatterTimer.start()'''

Heres the code for the enemy class:

extends CharacterBody2D
class_name Ghost

#setup state machine
enum States {IDLE, CHASE, SCATTER, EVADE, EATEN}
@export var state : States = States.IDLE

#finds player
var target 

#handle ghost movement (cut for readability)

#track how many times scatter mode has been entered (enemy will no longer enter scatter mode after the third loop)
@export var scatter_count : int = 0

func _physics_process(delta: float) -> void:
	get_target()
	$State.text = str(state)
	navigator.target_position = target.position

	direction = (navigator.get_next_path_position() - global_position)
	direction = direction.normalized()
				
	velocity = Vector2(movement_speed, movement_speed) * direction
				
	move_and_slide()

func get_target():
	if state == States.CHASE and GameManager.chase_timer_active == false:
		target = get_parent().get_parent().find_child("Player")

	elif state == States.SCATTER and scatter_count <= 3 and GameManager.scatter_timer_active == false:
		target = get_parent().get_parent().find_child("ScatterPoints").find_child("InkyScatter")
		scatter_count += 1

func _on_ghost_hitbox_body_entered(body: Node2D) -> void:
	if body is Player:
		print("die!")

func chase_mode():
	state = States.CHASE
	GameManager.chase_timer_active = true

func scatter_mode():
	state = States.SCATTER
	GameManager.scatter_timer_active = true

Offhand, looking at this, your ghosts seem to start in IDLE, and I don’t see anything that would pull them out of that state unless some code you haven’t referenced is poking a different state into them.

I have it setup so that the ghosts start in the Scatter state using the editor. Apologies for not clarifying

Does that set GameManager.scatter_timer_active? I’m wondering if it’s false and your state machine is repeatedly incrementing scatter_count every call to _process().

Are the ifs conditions correct? For example, when the Ghost’s state becomes CHASE, the chase_timer_active becomes true, then, in the first if, state == States.CHASE is true, but GameManager.chase_timer_active == false is false, because chase_timer_active value is now true. When GameManager.chase_timer_active becomes false, in the GameManager’s _on_chase_timer_timeout function, you call scatter_mode function, that changes the state to SCATTER, and now, state == States.CHASE is false and GameManager.chase_timer_active == false is true. I didn’t find any situation that makes these if conditions be true