skills/thedivergentai/gd-agentic-skills/state-machine-advanced

state-machine-advanced

SKILL.md

Advanced State Machines

Hierarchical states, state stacks, and context passing define complex behavior management.

Available Scripts

hsm_logic_state.gd

Expert HSM base class with state stack management and validation.

pushdown_automaton.gd

Stack-based state machine for interrupt-resume behavior (pause menus, cutscenes).

MANDATORY: Read hsm_logic_state.gd before implementing hierarchical AI behaviors.

NEVER Do in Advanced State Machines

  • NEVER forget to call exit() before enter() — Transition without exit? Previous state cleanup skipped = resource leaks (timers, tweens). ALWAYS exit → enter.
  • NEVER use push_state() without pop_state() — Pushed 100 interrupt states? Stack overflow + memory leak. EVERY push needs matching pop.
  • NEVER modify state during transitiontransition_to() called inside exit()? Re-entrant transition = undefined behavior. Flag transitions, execute after current completes.
  • NEVER skip state validationtransition_to("AttackState") but state doesn't exist? Silent failure OR crash. Validate state exists before transition.
  • NEVER forget to process child states — Hierarchical state with sub-states? Parent update() must call current_child_state.update() for delegation.
  • NEVER use string state names in codetransition_to("Idel") typo = silent failure. Use constants OR enums: transition_to(States.IDLE).

# 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:
    pass

Best Practices

  1. Separation - One state per file
  2. Signals - Communicate state changes
  3. Stack - Use push/pop for interruptions

Reference

  • Related: characterbody-2d, animation-player
Weekly Installs
1
GitHub Stars
35
First Seen
Feb 9, 2026
Installed on
amp1
opencode1
kimi-cli1
codex1
github-copilot1
gemini-cli1