Skip to content

Instantly share code, notes, and snippets.

@firebelley
Created August 27, 2024 21:18
Show Gist options
  • Save firebelley/96f2f82e3feaa2756fe647d8b9843174 to your computer and use it in GitHub Desktop.
Save firebelley/96f2f82e3feaa2756fe647d8b9843174 to your computer and use it in GitHub Desktop.
class_name CallableStateMachine
var state_dictionary = {}
var current_state: String
func add_states(
normal_state_callable: Callable,
enter_state_callable: Callable,
leave_state_callable: Callable
):
state_dictionary[normal_state_callable.get_method()] = {
"normal": normal_state_callable,
"enter": enter_state_callable,
"leave": leave_state_callable
}
func set_initial_state(state_callable: Callable):
var state_name = state_callable.get_method()
if state_dictionary.has(state_name):
_set_state(state_name)
else:
push_warning("No state with name " + state_name)
func update():
if current_state != null:
(state_dictionary[current_state].normal as Callable).call()
func change_state(state_callable: Callable):
var state_name = state_callable.get_method()
if state_dictionary.has(state_name):
_set_state.call_deferred(state_name)
else:
push_warning("No state with name " + state_name)
func _set_state(state_name: String):
if current_state:
var leave_callable = state_dictionary[current_state].leave as Callable
if !leave_callable.is_null():
leave_callable.call()
current_state = state_name
var enter_callable = state_dictionary[current_state].enter as Callable
if !enter_callable.is_null():
enter_callable.call()
@mbnewsom
Copy link

mbnewsom commented Jun 1, 2025

I'm new to Godot / GDScript. Coming from C# and Typescript typing is something I hold in high regard, and find some frustrations in GDScripts handling of it. Just tried a small rewrite of this to remove the need for the as Callable casts, and here's what i came up with.

# honestly just using this class to get around 'no nested typed containers' error. 
# Curious if there's a cleaner / less bulky way to do this
class_name State
extends RefCounted

var normal: Callable
var enter: Callable
var leave: Callable

func _init(normal: Callable, enter: Callable, leave: Callable):
	self.normal = normal
	self.enter = normal
	self.leave = normal

class_name CallableStateMachine

var state_dictionary: Dictionary[String, State] = {}
var current_state: String

func add_state(
	normal_state_callable: Callable,
	enter_state_callable: Callable,
	leave_state_callable: Callable
):
	state_dictionary[normal_state_callable.get_method()] = \
			State.new(normal_state_callable, enter_state_callable, leave_state_callable)

func set_initial_state(state_callable: Callable):
	var state_name = state_callable.get_method()
	if state_dictionary.has(state_name):
		_set_state(state_name)
	else:
		push_warning("No state with name " + state_name)


func update():
	if current_state != "":  #null here was a bug. Strings default to "", not null
		state_dictionary[current_state].normal.call()


func change_state(state_callable: Callable):
	var state_name = state_callable.get_method()
	if state_dictionary.has(state_name):
		_set_state.call_deferred(state_name)
	else:
		push_warning("No state with name " + state_name)


func _set_state(state_name: String):
	if current_state:
		var leave_callable = state_dictionary[current_state].leave
		if leave_callable.is_valid():
			leave_callable.call()
	
	current_state = state_name
	var enter_callable = state_dictionary[current_state].enter
	if enter_callable.is_valid():
		enter_callable.call()

@SaintHF
Copy link

SaintHF commented Jul 17, 2025

Heya, if you're still working with this I'm running into an issue where the "enter" and "leave" callables are still being called when using await timer randf_range to determine when to transition. Any suggestions? Found the problem, I had another part of the script executing functions that shouldn't have been executed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment