Video summary

LimboAI State Machine Tutorial and Guide for Godot 4 - Beginner Friendly - Part 2/2

Main summary

Key takeaways

Educational

Main ideas / lessons

  • The video demonstrates how to implement a Limbo AI Event-Based Hierarchical State Machine (HSM) in Godot 4.3, and how to connect it to a player character.
  • Core pattern:
    1. Create an HSM node inside the player scene.
    2. Create state nodes (idle, move, jump, fall, attack, air attack, crouch).
    3. Attach scripts to each state implementing enter and update (as needed).
    4. Add signals/transitions in the player script so states can move between each other.
    5. Dispatch signals from state logic based on inputs, physics (e.g., velocity / is_on_floor()), and animation events.
  • The state machine drives animation and high-level behavior, while basic movement/physics can remain in the agent/player code. The tutorial sometimes uses approaches that aren’t best practice, but favors clarity.

Step-by-step methodology

1) Set up the state machine in the player scene

  1. Open the player scene.
  2. Select the Root node (Player).
  3. Add a Limbo AI HSM node:
    • Add child node → search limbo → add limbo HSM (event-based hierarchical state machine).
  4. Add state nodes under the HSM:
    • Right-click the HSM → search limbo State → create/duplicate until you have 7 states.
    • Rename them:
      • idle, move, jump, fall, attack, air_attack, crouch
  5. Note:
    • These states won’t do anything yet because they have no scripts/code attached, and the player script doesn’t yet know about the state machine.

2) Create and configure the idle animation (for the idle state)

  • Add an AnimationPlayer to the player node (if not already present).
  • Place it at the top (preference).
  • Create a new animation:
    • Name: idle
  • Configure frames:
    • The tutorial’s sprite sheet has 8 frames.
    • Enable looping (Loop enabled).
    • Ensure all 8 frames are included in the animation timeline.
  • Test play to confirm the idle animation loops correctly.

3) Update the player script to reference the state machine and initialize it

In the player script:

  • Add a state machine variable exported to the Inspector:
    • export var state_machine : <HSM node type>
  • Add references to state nodes (assigned via drag/drop in the Inspector):
    • idle_state (and similar for other states)
  • Initialize the state machine in ready():
    • Call an internal function (e.g., finalize_state_machine / “initialize state machine”).
  • Initialization tasks inside that function:
    • Set default/initial state:
      • initial state = idle_state
    • Call initialize on the state machine (with self as required by the tutorial).
    • Activate it:
      • state_machine.set_active(true)
  • Attach the first state script:
    • Right-click the idle state node → Attach Script
    • Place scripts in a folder such as:
      • scripts/agents/player/states/
    • Example script name:
      • idle.gd

4) Implement idle state behavior (animation + transitions to move)

Idle state script:

  • Export references:
    • export var animation_player : AnimationPlayer
    • export var animation_name : String (set to "idle" in Inspector)
  • Implement enter():
    • Play the idle animation using the configured AnimationPlayer + animation name.
    • Key idea:
      • enter() runs only once when the state is activated (great for starting animations).
  • Transition logic to move:
    • Implement update(delta) (not process) so it runs only while the state is active.
    • In each frame of idle:
      • if movement_input != Vector2.ZERO
      • dispatch signal to_move from the state machine root to transition to move.

Player/agent responsibilities (movement input):

  • Add movement input variable:
    • movement_input : Vector2
  • In physics_process:
    • movement_input = Input.get_vector(<left>, <right>, <up>, <down>)
  • Configure Input Map actions in Project Settings:
    • left/right/up/down (examples: A/D, W/Up, S/Down)

5) Implement move state (movement application, animation, and transitions back)

Move state setup:

  • Assign the AnimationPlayer reference and create a move animation (loop it).

Move state logic:

  • Transition back to idle:
    • In update(delta), if movement_input == Vector2.ZERO:
      • dispatch to_idle

Movement/velocity:

  • The tutorial uses movement logic in the agent/player code:
    • Add apply_movement(delta) in the agent script
    • Use movement_input + move_speed to update velocity
  • The move state triggers/apply this logic each frame (the tutorial applies it directly from states for simplicity).

Sprite direction / facing:

  • Add a function in player/agent script:
    • update_sprite_direction() (or similar)
  • Flip sprite based on X input direction (ensure the sprite reference exists).
  • The tutorial uses:
    • unready VAR sprite : Sprite3D (assigned in Inspector)

6) Add jump transitions and implement jump + fall logic

Transitions:

  • From idle to jump on to_jump
  • From move to jump on to_jump
  • The tutorial also mentions an alternative:
    • “any state can go to jump,” but it’s not fully optimized.

Input checking:

  • Add Input Map action:
    • jump (example: Space)
  • Add player function:
    • check_jump_input()
  • When jump is pressed:
    • dispatch to_jump (subject to conditions later)
  • Call check_jump_input() from both:
    • idle.update(...)
    • move.update(...)

Jump state:

  • Configure a jump animation.
  • Implement enter():
    • Set jump velocity once:
      • velocity.y = jump_velocity
  • Transition to falling:
    • Add fall animation and transition when falling.
    • In jump.update(delta), if:
      • velocity.y < 0 AND NOT is_on_floor()
    • dispatch to_fall

Landing logic (fallback transitions):

  • In fall.update(delta):
    • if is_on_floor():
      • if movement_input == Vector2.ZERO → dispatch to_idle
      • else → dispatch to_move
  • Also apply movement while jumping/falling by calling apply_movement(delta) from those states as needed (the tutorial repeats this so you can move during jump/fall).

7) Add crouch transitions and implement crouch behavior

Transitions:

  • Add transition:
    • idle → crouch when to_crouch is dispatched.

Crouch input:

  • Add Input Map action:
    • crouch (example: C)
  • In idle.update(delta):
    • call something like check_crouch_input()
    • if crouch pressed:
      • dispatch to_crouch

Animations:

  • Add crouch animation and AnimationPlayer reference.

Crouch exit:

  • In crouch.update(delta):
    • if crouch input is released:
      • dispatch back to idle

Important issue noted by the tutorial:

  • Without an exit transition, the character can get stuck in crouch.

8) Add attack and air_attack transitions and implement animation/event resets

Animations and state setup:

  • Create/assign:
    • attack (ground)
    • air_attack (variant with feet hovering)
  • Ensure each state has AnimationPlayer reference + animation name set correctly.

Transitions (simple rule from the tutorial):

  • Attack from:
    • idle (ground) and jump (air), depending on floor contact.
  • Add transitions:
    • idle → attack on to_attack
    • jump → air_attack on to_air_attack
  • Note/fix mentioned:
    • missing transitions can prevent reaching the intended state (the tutorial highlights a sloppy copy/paste bug).

Attack input:

  • Add Input Map action:
    • attack (example: Ctrl)
  • Implement player function check_attack_input():
    • If on floor and attack pressed:
      • dispatch to_attack
    • If NOT on floor and attack pressed:
      • dispatch to_air_attack

Animation reset / state return after attack:

  • Problem:
    • attack animations may “never really reset” and the state never returns automatically.
  • Fix using AnimationPlayer signal:
    • Connect AnimationPlayer’s animation_finished signal to a player function (e.g., on_animation_player_animation_finished).
  • In that function:
    • if animation_name == "attack":
      • dispatch state machine to idle
    • if animation_name == "air_attack":
      • dispatch to fall

What state functions mean (as emphasized)

  • enter():
    • runs once when the state becomes active (used for one-time setup like starting animations or applying jump impulse).
  • update(delta):
    • runs only while the state is active (used for per-frame decisions like transitions based on input/physics; the tutorial sometimes also applies movement here, although it’s noted as not best practice).

Speakers / sources featured

  • Speaker: The video narrator/instructor (unnamed)
  • Source context: Teaching Limbo AI State Machine in Godot 4.3 across Part 2/2.

Rate this summary

Your feedback will help improve summaries.

Improve this summary

Reprocess with a stronger model when the summary feels incomplete or inaccurate.

Pro

Translate summary in another language

Pro

Ask questions to this video

Chat for follow-up questions, clarifications, and source-backed answers.

Coming soon

Share this summary

Original video