## Virtual base class for all nodes that deal directly with states. ## Extend this class and override its methods to implement a state machine. class_name StateMachine extends Node ## The initial state of the state machine. If not set, the first child node is used. @export var initial_state: State ## The state machine's current loaded state @onready var state: State = _get_initial_state() func _ready() -> void: assert(state != null, "initial state is null") for state_node: State in find_children("*", "State"): # fixes duplicate connections (not sure why) if not state_node.finished.is_connected(_transition_to_next_state): state_node.finished.connect(_transition_to_next_state) await owner.ready state.enter("") func _unhandled_input(event: InputEvent) -> void: state.handle_input(event) func _process(delta: float) -> void: state.state_update(delta) func _physics_process(delta: float) -> void: state.state_physics_update(delta) ## Called when initial state is not specified. ## Returns the first child node by default. ## Override if necessary, but don't call it directly. func _get_initial_state() -> State: return initial_state if initial_state != null else get_child(0) ## Transitions the active state out after receiving a finished signal. func _transition_to_next_state(target_state_path: String, data: Dictionary = {}) -> void: print("+++ TRANSITION CALLED: ", target_state_path) print("+++ has node? ", has_node(target_state_path)) print("+++ all children: ", get_children().map(func(c: Node) -> StringName: return c.name)) assert(has_node(target_state_path), owner.name + ": Trying to transition to state " + target_state_path + " but it does not exist.") if not has_node(target_state_path): printerr(owner.name + ": Trying to transition to state " + target_state_path + " but it does not exist.") return var previous_state_path: StringName = state.name state.exit() state = get_node(target_state_path) state.enter(previous_state_path, data)