gdstyle 0.1.2

A fast, opinionated linter and formatter for GDScript (Godot 4.x)
Documentation
## A generic finite state machine.

class_name StateMachine
extends Node

signal StateChanged(old_state: StringName, new_state: StringName)

@export var initial_state: NodePath

var currentState: StringName = &""
var previousState: StringName = &""
var _states: Dictionary = {}
var stateTimer: float = 0.0

func _ready() -> void:
	for child in get_children():
		if child.has_method('enter') and child.has_method('exit') and child.has_method('update'):
			_states[child.name] = child
			child.set_process(false)
			child.set_physics_process(false)

	if initial_state:
		var node = get_node_or_null(initial_state)
		if node:
			transition_to(node.name)

func _process(delta: float) -> void:
	if currentState == &"":
		return
	stateTimer+=delta
	if _states.has(currentState):
		_states[currentState].update(delta)

func _physics_process(delta: float) -> void:
	if currentState == &"" :
		return
	if _states.has(currentState):
		if _states[currentState].has_method("physics_update"):
			_states[currentState].physics_update(delta)

func transition_to(new_state: StringName, params: Dictionary = {}) -> void:
	if !_states.has(new_state):
		push_warning("StateMachine: state '%s' does not exist" % new_state)
		return

	if currentState != &"":
		_states[currentState].exit()
		_states[currentState].set_process(false)
		_states[currentState].set_physics_process(false)

	previousState = currentState
	currentState = new_state
	stateTimer = 0.0
	_states[currentState].set_process(true)
	_states[currentState].set_physics_process(true)
	_states[currentState].enter(params)

	StateChanged.emit(previousState, currentState)

func get_current_state_node() -> Node:
	if _states.has(currentState):
		return _states[currentState]
	return null

func HasState(state_name: StringName) -> bool:
	return _states.has(state_name)