
Godot State Machine Advanced
- 266 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-state-machine-advanced for development tasks
About
godot-state-machine-advanced: A skill for development. This provides functionality for development workflows.
- godot-state-machine-advanced
Godot State Machine Advanced by the numbers
- 266 all-time installs (skills.sh)
- +28 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,457 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-state-machine-advancedAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 266 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-state-machine-advanced for development tasks
Files
Advanced State Machines
Hierarchical states, state stacks, and context passing define complex behavior management.
Available Scripts
hsm_hierarchical_base.gd
Advanced HSM base delegator for propagating physics and input to sub-states.
hsm_pushdown_stack.gd
Professional Pushdown Automata for interruptive state (Pause/Menu) stacking.
hsm_state_context.gd
Decoupled context object pattern for passing persistent data between states.
hsm_transition_guard.gd
Expert transition validation logic to prevent illegal state changes.
hsm_animation_syncer.gd
Automated Logic-to-AnimationTree syncing with state-based travel logic.
hsm_concurrent_logic.gd
Orchestration for parallel state machines (e.g., Move + Attack).
hsm_resource_state_loader.gd
Data-driven state definition using custom Godot Resources (.tres).
hsm_reentry_aware_state.gd
Handling resume-from-stack logic vs fresh entry events.
hsm_state_history_logger.gd
Debug ring-buffer for tracking state transition history and stack depth.
hsm_state_timer_component.gd
Auto-transition component for finite states like Stun or Dash.
MANDATORY: Read hsm_logic_state.gd before implementing hierarchical AI behaviors.
NEVER Do (Expert State Rules)
Hierarchy & Delegation
- NEVER forget to propagate physics/input to children — In an HSM, failing to call
child.physics_update()from the parent's_physics_processorphans child logic. - NEVER use deep nesting (>3 levels) — Extreme hierarchy creates "State Spaghetti." If logic is that complex, consider a Behavior Tree or Utility AI.
Transitions & Lifecycle
- NEVER call enter() without a preceding exit() — Skipping exit logic leaves timers, tweens, or audio loops running in the background, causing resource leaks.
- NEVER modify state during a transition frame — Re-entrant
transition_to()calls insideenter()cause recursion crashes. Usecall_deferredif immediate sub-transitioning is required. - NEVER hardcode state names as strings — Typos like
transition_to("Idel")are silent killers. Useclass_namebased checks OR Constants.
Architecture & Context
- NEVER use global singletons for state data — Coupling states to
GameManager.player_healthmakes them non-reusable. Pass aContextobject. - NEVER push states indefinitely — In a Pushdown Automaton, every
push_stateMUST have a retirement plan (pop_state) to avoid stack overflow. - NEVER assume state re-entry is always a fresh start — Resuming from a stack pop should often bypass "Entry SFX/VFX"; use re-entry flags.
---
# hierarchical_state.gd
class_name HierarchicalState
extends Node
signal transitioned(from_state: String, to_state: String)
var current_state: Node
var state_stack: Array[Node] = []
func _ready() -> void:
for child in get_children():
child.state_machine = self
if get_child_count() > 0:
current_state = get_child(0)
current_state.enter()
func transition_to(state_name: String) -> void:
if not has_node(state_name):
return
var new_state := get_node(state_name)
if current_state:
current_state.exit()
transitioned.emit(current_state.name if current_state else "", state_name)
current_state = new_state
current_state.enter()
func push_state(state_name: String) -> void:
if current_state:
state_stack.append(current_state)
current_state.exit()
transition_to(state_name)
func pop_state() -> void:
if state_stack.is_empty():
return
var previous_state := state_stack.pop_back()
transition_to(previous_state.name)State Base Class
# state.gd
class_name State
extends Node
var state_machine: HierarchicalState
func enter() -> void:
pass
func exit() -> void:
pass
func update(delta: float) -> void:
pass
func physics_update(delta: float) -> void:
pass
func handle_input(event: InputEvent) -> void:
passBest Practices
1. Separation - One state per file 2. Signals - Communicate state changes 3. Stack - Use push/pop for interruptions
Expert State Machine Patterns
1. HSM Visualizer (Debug Tool)
Use a specialized Control node with _draw() to visualize the current state stack/hierarchy in the viewport for immediate debugging [3, 11].
class_name HSMVisualizer extends Control
@export var state_machine: Node
func _draw() -> void:
var font := ThemeDB.fallback_font
var pos := Vector2(20, 20)
# Recursively draw active state names...
draw_string(font, pos, "Active: " + state_machine.current_state.name)2. State-Based Audio (Decoupled)
Avoid hardcoding audio.play() inside state enter() methods. Use a syncer that listens to state_changed and maps state names to AudioStream resources [12, 13].
class_name StateAudioSyncer extends Node
@export var state_machine: Node
@export var audio_map: Dictionary # { "Jump": preload("jump.wav") }
func _ready() -> void:
state_machine.state_changed.connect(_on_state_changed)
func _on_state_changed(_old, new_state: Node):
if audio_map.has(new_state.name):
$AudioPlayer.stream = audio_map[new_state.name]
$AudioPlayer.play()3. Transition Cost (Utility AI)
Enable states to evaluate their own "weight" based on context. The StateMachine polls sibling costs and transitions to the lowest-cost behavior [17, 18].
# CostState.gd (Base)
func get_cost(context: Dictionary) -> float:
return 10.0 # Default weight
# UtilityStateMachine.gd
func _physics_process(_d: float) -> void:
var best_state: Node = current_state
var low_cost: float = INF
for child in get_children():
var cost = child.get_cost(context)
if cost < low_cost:
low_cost = cost
best_state = child
if best_state != current_state:
transition_to(best_state.name)Reference
- Related:
godot-characterbody-2d,godot-animation-player
Related
- Master Skill: godot-master
class_name HSMAnimationSyncer
extends Node
## Expert Logic-to-Animation coupling.
## Keeps AnimationTree in sync with the current HSM state.
@export var anim_tree: AnimationTree
var playback: AnimationNodeStateMachinePlayback
func _ready() -> void:
playback = anim_tree.get("parameters/playback")
func sync_state(state_name: String) -> void:
if playback:
playback.travel(state_name)
## Rule: Use '.travel()' for smooth blending or '.start()' for instant snaps.
class_name HSMConcurrentLogic
extends Node
## Expert Orchestrator for concurrent state machines.
## Runs multiple state machines in parallel (e.g., Locomotion + Status Effects).
@onready var locomotion_sm := $LocomotionSM
@onready var status_sm := $StatusSM
func update_all(delta: float) -> void:
locomotion_sm.physics_update(delta)
status_sm.physics_update(delta)
## Rule: Ensure parallel machines don't conflict over the same actor properties.
class_name HSMHierarchicalBase
extends Node
## Expert Hierarchical State Machine (HSM) base delegator.
## Propagates physics, input, and updates to the active child state.
var current_state: Node = null
func _physics_process(delta: float) -> void:
if current_state and current_state.has_method("physics_update"):
current_state.physics_update(delta)
func _input(event: InputEvent) -> void:
if current_state and current_state.has_method("handle_input"):
current_state.handle_input(event)
func transition_to(new_state_path: String, msg: Dictionary = {}) -> void:
if not has_node(new_state_path): return
if current_state:
current_state.exit()
current_state = get_node(new_state_path)
current_state.enter(msg)
## Rule: Always delegate processing to children to ensure hierarchical encapsulation.
# skills/state-machine-advanced/code/hsm_logic_state.gd
extends Node
## State Machine Expert Pattern
## Implements Hierarchical Logic (HSM) and Pushdown Automata.
class State:
var parent_state: State = null
var name: String = ""
func enter(_msg: Dictionary = {}) -> void: pass
func exit() -> void: pass
func update(_delta: float) -> void: pass
func handle_input(_event: InputEvent) -> void: pass
# 1. Pushdown Automaton (State Stack)
# Professional pattern: Allow temporary overrides (Stun, Menu) with fallback.
var _state_stack: Array[State] = []
var current_state: State:
get: return _state_stack.back() if not _state_stack.is_empty() else null
func push_state(new_state: State, msg: Dictionary = {}) -> void:
if current_state:
current_state.exit()
_state_stack.append(new_state)
new_state.enter(msg)
func pop_state() -> void:
if _state_stack.size() <= 1: return
current_state.exit()
_state_stack.pop_back()
current_state.enter()
# 2. Hierarchical Logic (HSM)
# Expert logic: Parent-child relationships for sharing common behavior.
func _process(delta: float) -> void:
var state = current_state
while state:
state.update(delta)
# 3. Propagate logic up the hierarchy
# e.g. If 'Jumping' doesn't handle a 'Pause' input, 'InAir' might.
state = state.parent_state
## EXPERT NOTE:
## For 'Save-Game Compatibility', serialize the '_state_stack'
## as an array of 'StateName' strings.
## Use 'Signal-Driven Transitions': Instead of polling 'is_on_floor',
## connect character signals to 'FSM.transition()' calls for
## event-driven architecture.
## Use 'State Data Payloads': Pass complex dictionaries during
## 'push_state(new_state, {"knockback": Vector3.UP})' to initialize
## states without global variables.
## NEVER transition to a state if it is already active; use a
## 'can_transition_to' check to avoid recursive overflows.
class_name HSMPushdownStack
extends Node
## Expert Pushdown Automata implementation.
## Manages a state stack for interrupt-resume behaviors (e.g., Stun, Pause, Menu).
var state_stack: Array[Node] = []
func push_state(state_path: String, msg: Dictionary = {}) -> void:
var new_state := get_node(state_path)
if not new_state: return
if not state_stack.is_empty():
state_stack.back().exit()
state_stack.append(new_state)
new_state.enter(msg)
func pop_state() -> void:
if state_stack.size() <= 1: return # Keep initial state
var old_state := state_stack.pop_back()
old_state.exit()
if not state_stack.is_empty():
state_stack.back().enter({"is_resume": true})
## Tip: Use 'is_resume' in 'enter()' to avoid re-triggering one-shot entry animations.
class_name HSMReentryAwareState
extends Node
## Expert state with Re-entry Awareness.
## Distinguishes between fresh entry and being resumed from a stack.
func enter(msg: Dictionary = {}) -> void:
if msg.get("is_resume", false):
_on_resumed()
else:
_on_fresh_entry()
func _on_fresh_entry() -> void:
# Trigger sound, start animation
pass
func _on_resumed() -> void:
# Keep current animation frame, just resume logic
pass
class_name HSMResourceStateLoader
extends Node
## Expert Data-Driven State Loader.
## Loads state configurations from .tres (Resource) files for modular AI.
@export var state_resources: Array[Resource]
func initialize_states() -> void:
for res in state_resources:
# Assume resource has a path to a script
var state_node := Node.new()
state_node.set_script(res.state_script)
state_node.name = res.state_name
add_child(state_node)
## Rule: Using Resources allows designers to tweak AI values without touching code.
class_name HSMStateContext
extends RefCounted
## Expert pattern: Decoupled State Context.
## Passes dependencies through states without global singletons.
var actor: CharacterBody3D
var target: Node3D
var blackboards: Dictionary = {}
func _init(p_actor: CharacterBody3D) -> void:
actor = p_actor
## Rule: Pass this context object to every state's 'enter' method.
class_name HSMStateHistoryLogger
extends Node
## Expert Debug tool for State Machine History.
## Tracks a ring buffer of recent transitions for troubleshooting.
var history: Array[String] = []
@export var max_history: int = 20
func log_transition(from: String, to: String) -> void:
var entry := "[%s] %s -> %s" % [Time.get_time_string_from_system(), from, to]
history.push_back(entry)
if history.size() > max_history:
history.pop_front()
## Tip: Expose 'history' to the in-game debug console.
class_name HSMStateTimerComponent
extends Timer
## Expert component for State machine auto-transitions.
## Automatically triggers a state exit/transition after a set duration.
signal state_timeout
func start_state_timer(duration: float) -> void:
wait_time = duration
one_shot = true
start()
timeout.connect(func(): state_timeout.emit(), CONNECT_ONE_SHOT)
## Rule: Use timers for finite states like 'Stun', 'WallClip', or 'Dash'.
class_name HSMTransitionGuard
extends Node
## Expert transition validation logic.
## Prevents illegal state changes using 'can_enter'/'can_exit' checks.
func can_transition(from: Node, to: Node) -> bool:
# Example: Prevent jumping while floating or dead
if to.name == "JumpState" and not from.has_method("is_grounded"):
return false
# Transition validation logic...
return true
## Rule: Centralize transition logic in the StateMachine, not the individual states.
# skills/state-machine-advanced/scripts/pushdown_automaton.gd
extends Node
## Pushdown Automaton Expert Pattern
## Stack-based state machine for interrupt-resume behavior (pause menu, cutscene, item pickup).
class_name PushdownAutomaton
signal state_changed(from_state: String, to_state: String)
var _state_stack: Array[Node] = []
var _current_state: Node = null
func _ready() -> void:
for child in get_children():
child.set_meta("_fsm", self)
if get_child_count() > 0:
_transition_to(get_child(0))
func transition_to(state_name: String) -> void:
var new_state := get_node_or_null(state_name)
if not new_state:
push_error("State not found: %s" % state_name)
return
_transition_to(new_state)
func push_state(state_name: String) -> void:
if _current_state:
if _current_state.has_method("pause"):
_current_state.pause()
_state_stack.append(_current_state)
transition_to(state_name)
func pop_state() -> void:
if _state_stack.is_empty():
push_warning("Attempted to pop empty state stack")
return
var previous_state := _state_stack.pop_back()
_transition_to(previous_state)
if previous_state.has_method("resume"):
previous_state.resume()
func _transition_to(new_state: Node) -> void:
var old_name := _current_state.name if _current_state else ""
if _current_state and _current_state.has_method("exit"):
_current_state.exit()
_current_state = new_state
if _current_state.has_method("enter"):
_current_state.enter()
state_changed.emit(old_name, new_state.name)
func update(delta: float) -> void:
if _current_state and _current_state.has_method("update"):
_current_state.update(delta)
func physics_update(delta: float) -> void:
if _current_state and _current_state.has_method("physics_update"):
_current_state.physics_update(delta)
## EXPERT USAGE:
## Enemy AI: Patrol → [push Attack] → [pop back to Patrol]
## Player: Gameplay → [push Pause Menu] → [pop back to Gameplay]
##
## State scripts implement optional: enter(), exit(), pause(), resume()