State Machine not working

Godot Version

Godot Version Godot 4.2.2

Question

Good day Godotians, today I come for help with a state machine set-up not working as I want it to for some reason.

The State machine is node based, only has 2 states right now, and has a few layers so let me go through them first, apologies for how long this whole thing is going to be;

The State Machine script itself:

extends Node

@export var initial_state : State

var current_state : State
var states : Dictionary = {}

func _ready():
	for child in get_children():
		if child is State:
			states[child.name.to_lower()] = child
			child.Transition.connect(state_transition)
		if initial_state:
			initial_state.enter()
			current_state = initial_state

func _process(delta):
	if current_state:
		current_state.state_frame_process(delta)

func _physics_process(delta):
	if current_state:
		current_state.state_physics_process(delta)

func state_transition(new_state):
	if new_state != current_state:
		current_state.exit()
		current_state = new_state
		current_state.enter()

The base State script my other states extend from:

extends Node
class_name State

var state = self

signal Transition(state)

func call_transition():
	pass

func enter():
	pass

func state_physics_process(_delta):
	pass

func state_frame_process(_delta):
	pass

func exit():
	pass

My Movement State script:

extends State
class_name MoveState

var ACCEL = 300
var SPEED_MAX = 300

var direction = Input.get_axis("move_left", "move_right")

@export var character : CharacterBody2D
@export var animated_sprite : AnimatedSprite2D

func call_transition():
	if direction and character.is_on_floor():
		Transition.emit(state)

func enter():
	pass

func state_physics_process(_delta):
	if direction:
		character.velocity.x = move_toward(character.velocity.x, SPEED_MAX * direction, ACCEL)
		animated_sprite.play("run")

func state_frame_process(_delta):
	if direction > 0:
		animated_sprite.flip_h = false
	elif direction < 0:
		animated_sprite.flip_h = true

func exit():
	pass

And finally my Idle State script:

extends State
class_name IdleState

var ACCEL = 300
var SPEED_MAX = 300

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var direction = Input.get_axis("move_left", "move_right")

@export var character : CharacterBody2D
@export var animated_sprite : AnimatedSprite2D

func call_transition():
	if !direction and character.is_on_floor():
		Transition.emit(state)

func enter():
	pass

func state_physics_process(_delta):
	character.velocity.x = move_toward(character.velocity.x, 0, ACCEL)

func state_frame_process(_delta):
	animated_sprite.play("idle")

func exit():
	pass

I’m not getting any errors in the debugger so it isn’t like something’s flat out broken it’s just something in all my logic isn’t connecting.

I placed some prints() in a few of the places that would’ve caught most of my suspicion and it seems like the call_transition function in my state scripts aren’t working as they don’t print anything, so I feel like even beyond that my transition logic and signal usage is overall wrong for what I’m trying to do.

The goal of the transitioning system is for the states themselves to listen for the proper inputs and conditions and then emit a signal passing itself as a variable through the Transition signal, which is connected to the state_transition function renaming the state variable as new_state to be compared to the current_state and then call the exit function on the current_state, declare the new_state as the current_state, and then call the enter function on the new current_state.

I’m not new to programming in object oriented languages, but I am still definitely learning and I didn’t start in Godot either, so I apologize if this problem or what further conversation comes from this is the result of me missing a fundamental property or limitation with what I’m trying to use here, but without any actual errors in the debug I have to simply ask for those with more experience.

Thank you regardless.

It looks like you never call call_transition anywhere in the code provided.

I don’t know how I never saw an issue with that, thank you for engaging with my stupidity

Alright well after that embarrassing oversight I tried to amend it but haven’t gotten anywhere, the function isn’t firing or signal still isn’t emitting

The intended function of call_transition is for it to listen for the conditions laid out in it and then emit the transition function if they turn out true

Thinking it over for more than a second it became readily apparent how odd it was to expect a function to listen for itself to activate when I haven’t got anything to activate it to listen in the first place so I tried a couple different options that didn’t work out

First I tried converting all the call_transition functions in the State classes to either the built in _physics_process or _process to get the function automatically firing every frame or physics frame and still the function isn’t doing anything, I feel like in this approach I’m going about firing it off wrong because I was getting some debug errors about not using delta in either of the processes when I tried to use them, which if using delta in the function is intrinsic to them calling automatically would obviously be an issue

Second I went back to having the custom call_transition function but would have it being called in the state machine itself. In the built in processes functions again I tried to run a for loop to grab the children like I did in _ready to assign the local child variable to be set to any of the State classes it grabs from the get_children function and then call child.call_transition hoping it would fire off all the call_transition functions in every child it could find under itself every frame and henceforth would be watching for the conditions to emit the signal, but no dice still

I understand these are probably very inoptimal methods of doing this if they had succeeded but at this point I’m just trying to get something to work so I know better how any of this works, I thought individually and even interconnectedly I at least had a decent understanding of how these built in processes, syntax, and core fundamentals operated but evidently I do not somewhere

Further amendment and I’ve pretty much got it, I forgot to call for the character body’s move_and_slide function in the physics processes in each State’s state_physics_process functions to make the movement actually operate, now the movement conditions I’m looking for are actually present in the scene and henceforth can be listened for by the call_transition functions

The move state doesn’t appear to want to work but that’s a much smaller issue than my entire transition signal system refusing to work