The Hierarchical State Machine System provides a flexible and extensible state management solution. The system supports state nesting, state history, event handling, and variable sharing, making it particularly suitable for game AI, UI interactions, and game flow control.
- Represents a specific behavior or condition
- Can have entry/exit logic
- Supports update (per frame) and physics update (fixed rate)
- Can handle events
- Can access and modify shared variables
- Manages transitions between multiple states
- Maintains current active state
- Handles state transition logic
- Supports state history
- Manages shared variables
- States can contain sub-state machines
- Child states can access parent state variables
- Events can propagate through state hierarchy
- Supports state inheritance and reuse
# Create a simple state
class_name IdleState extends BaseState
func enter(msg := {}):
super.enter(msg)
print("Entering idle state")
func update(delta: float):
if agent.is_moving:
transition_to("move")
# Use the state machine
var state_machine = BaseStateMachine.new()
state_machine.add_state("idle", IdleState)
state_machine.add_state("move", MoveState)
state_machine.transition_to("idle")# Create a state with sub-state machine
class_name CombatState extends BaseState
var sub_state_machine: BaseStateMachine
func _init():
sub_state_machine = BaseStateMachine.new(self)
sub_state_machine.add_state("attack", AttackState)
sub_state_machine.add_state("defend", DefendState)
func enter(msg := {}):
super.enter(msg)
sub_state_machine.transition_to("attack")
# Use in main state machine
main_state_machine.add_state("combat", CombatState)
main_state_machine.add_state("explore", ExploreState)# Handle events in state
class_name PlayerState extends BaseState
func _on_damage_taken(amount: int):
if amount > 50:
transition_to("hurt")
elif parent_state:
parent_state.handle_event("damage_taken", [amount])
# Trigger event
state_machine.handle_event("damage_taken", [30])-
State Organization
- Organize related states in the same state machine
- Use meaningful state names
- Keep state logic simple and clear
-
State Transitions
- Switch states at appropriate times
- Use msg parameter to pass necessary transition information
- Make good use of state history feature
-
Variable Management
- Use shared variables appropriately
- Pay attention to variable scope
- Clean up unnecessary variables
-
Event Handling
- Use event propagation mechanism appropriately
- Avoid event handling loops
- Keep event parameters simple and clear
-
State Machine Initialization
- Ensure proper initialization before use
- Set necessary initial states
- Configure state machine agent correctly
-
Performance Considerations
- Avoid intensive calculations in update
- Use physics update appropriately
- Clean up unnecessary states and variables
-
Debugging
- Use state machine signals for debugging
- Monitor state transitions and event propagation
- Check variable changes
The State Machine System provides a robust and flexible way to manage game states and transitions. It's designed to handle complex game logic while maintaining code clarity and maintainability.
- 🔄 Hierarchical State Machines: Support for nested state machines
- 🎮 Game-Specific States: Built-in support for common game states (Menu, Gameplay, Pause)
- 📊 State Management: Clean API for state transitions and updates
- 🎯 Input Handling: Integrated input processing per state
- 🔍 Debugging: Built-in debugging features for state tracking
The foundation class for all states. Provides:
- State lifecycle methods (enter, exit, update)
- Input handling
- State transition management
class MyState extends BaseState:
func _enter(msg := {}) -> void:
# Called when entering the state
pass
func _exit() -> void:
# Called when exiting the state
pass
func _update(delta: float) -> void:
# Called every frame
pass
func _handle_input(event: InputEvent) -> void:
# Handle input events
passManages a collection of states and their transitions:
- State registration and switching
- State updates and input propagation
- Support for hierarchical state machines
class MyStateMachine extends BaseStateMachine:
func _ready() -> void:
# Register states
add_state("idle", IdleState.new(self))
add_state("walk", WalkState.new(self))
# Set initial state
start("idle")Optional: Central registry for multiple BaseStateMachine instances and unified driving of update / physics_update / handle_input. For a single state machine, you can call state_machine.update(delta) from the owner's _process without registering.
- Central registration of state machines
- Global updates (per-registration flags can disable physics/input forwarding)
- Debug information and monitoring (
is_active/get_current_state; passroot_idwhen multiple roots)
# Default behavior matches older versions (all three drivers enabled)
CoreSystem.state_machine_manager.register_state_machine("player", player_state_machine)
# Only logic tick: disable physics and input forwarding from the manager
CoreSystem.state_machine_manager.register_state_machine(
"flow", flow_state_machine, self, &"", {}, true, false, false
)
CoreSystem.state_machine_manager.set_registration_drive_flags("flow", true, false, false)Here's a simple example of a character state machine:
# Character state machine
class CharacterStateMachine extends BaseStateMachine:
func _ready() -> void:
add_state("idle", IdleState.new(self))
add_state("walk", WalkState.new(self))
add_state("jump", JumpState.new(self))
start("idle")
# Idle state
class IdleState extends BaseState:
func _enter(msg := {}) -> void:
owner.play_animation("idle")
func _handle_input(event: InputEvent) -> void:
if event.is_action_pressed("move"):
transition_to("walk")
elif event.is_action_pressed("jump"):
transition_to("jump")
# Register with manager
func _ready() -> void:
var character_sm = CharacterStateMachine.new(self)
CoreSystem.state_machine_manager.register_state_machine("character", character_sm)-
State Organization
- Keep states small and focused
- Use hierarchical state machines for complex behaviors
- Consider using state factories for dynamic state creation
-
State Transitions (hierarchical)
BaseState.transition_to: transition within the layer owned bystate_machine(call onstate_machinewhen you mean “my owner’s table”).BaseStateMachine.transition_local: transition only inside this machine’sstates(prefer this name in nested state-machine scripts when you mean “inner only”).BaseStateMachine.transition_tois equivalent totransition_localon this class (leaftransition_toeventually reachestransition_local).- Use message passing for state communication
- Validate state transitions
- Handle cleanup in _exit()
-
Driving
- When using
StateMachineManager, turn offrun_physics/run_inputif unused to avoid per-frame overhead - For a single machine, you may skip the manager and call
updatefrom the owner_process
- When using
-
Debugging
- Enable debug logging for state transitions
- Use the built-in state monitoring tools
- Add state validation checks
enter(msg: Dictionary): Enter the stateexit(): Exit the stateupdate(delta: float): Update state logichandle_input(event: InputEvent): Process inputtransition_to(state_id: StringName, msg: Dictionary = {}): Transition within the layer managed bystate_machine(implemented viaBaseStateMachine.transition_local)
add_state(name: String, state: BaseState): Register a new stateremove_state(name: String): Remove a registered statestart(initial_state: String): Start the state machinestop(): Stop the state machinetransition_local(state_id: StringName, msg: Dictionary = {}): Transition only within this machine’sstates(explicit “inner only” API)transition_to(state_id: StringName, msg: Dictionary = {}): Equivalent totransition_localon this class; leaftransition_todispatches here
register_state_machine(id, state_machine, agent = null, initial_state = &"", msg = {}, run_update = true, run_physics = true, run_input = true): Register; the three booleans control forwarding from_process/_physics_process/_input(defaults preserve legacy behavior)set_registration_drive_flags(id, run_update, run_physics, run_input): Change drive flags at runtimeunregister_state_machine(name: String): Unregister a state machineget_state_machine(name: String) -> BaseStateMachine: Get a registered state machine