Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 34

Understanding HFSMs: Simplifying Complexity

Recommended resources

Hello! Welcome back to our module on Advanced FSM Patterns.

In the previous lesson, we diagnosed the two primary "growing pains" of complex Finite State Machines: state duplication and transition explosion. We saw how adding more behaviors can lead to redundant code and a tangled "spaghetti" web of transitions that becomes nearly impossible to manage.

Now that we understand the problem, it's time to learn the solution. This lesson introduces a powerful extension of the FSM pattern designed to bring order to that chaos. Our learning outcome is to define a Hierarchical Finite State Machine (HFSM) and explain how it solves complexity issues.

This pattern allows us to organize states into parent-child relationships, much like you'd organize files into folders, enabling logic reuse and dramatically simplifying our state diagrams.

What is a Hierarchical FSM?

At its core, a Hierarchical FSM is simply a state machine where states can contain other state machines. This creates a tree-like structure, or hierarchy.

Let's start with a concise video definition.

The AI of Half-Life: Finite State Machines | AI 101

This clip from the AI and Games channel, which we saw briefly in the last lesson, provides a great high-level definition of what an HFSM is.

Please watch from 05:14 to 06:17. The video explains the core idea: you can group states together, allowing a transition to go to a single state or to a whole collection of states.

As the video explains, you can "group states together" into what are essentially modular, nested state machines. This introduces some new terminology:

  • Super-state (or Parent State): A state that contains other states. For example, a Combat state could be a super-state.
  • Sub-state (or Child State): A state that is contained within a super-state. Chase and Attack could be sub-states of Combat.
  • Leaf State: A state at the bottom of the hierarchy that has no sub-states of its own. An FSM is always in exactly one leaf state at any given time.
  • State Configuration: The full "path" to the current leaf state. If the machine is in the Chase state, its configuration is [Combat, Chase].

To dig a bit deeper into these concepts, let's turn to a helpful article.

Hierachical Finite State Machines

This article by Luca Davidian provides a clear, formal definition of the core concepts of HFSMs.

Read the sections titled "HFSM basic concepts:" and "States and state configurations". Focus on the ideas of 'composite states' (super-states) and how the machine is always in a 'state configuration' (the leaf state plus all its parents).


How HFSMs Solve Complexity

Now that we have the basic definition, let's connect it directly to the problems we identified in the last lesson.

1. Solving State Duplication and Code Redundancy

Remember the problem of having Patrol and PatrolWithGun? Or the issue of duplicating logic between Chase and Attack? HFSMs solve this through behavioral inheritance. Common logic can be placed in a parent state, and it will be active for all of its children.

Let's look at a concrete example.

Hierarchical Finite State Machine | by Johan Svensson

This article by Johan Svensson on Medium provides a perfect example of using a parent state to avoid code duplication.

Read the section titled "The hierarchical finite state machine". Pay close attention to the example of a MoveState parent with RunState and JumpState as sub-states. The shared camera logic can live in MoveState, while the specifics live in the children.

This is a powerful concept. The MoveState's update function can handle the camera logic, and then call the update function of its active child (RunState or JumpState). This way, you write the camera code once, and it automatically applies to all movement sub-states. If you add a CrouchState later, it automatically inherits the camera logic just by being a child of MoveState.

2. Solving Transition Explosion

Hierarchies also elegantly solve the "spaghetti FSM" problem. Instead of drawing dozens of lines from every state to a common state (like Dead), you can create one transition from a high-level parent state.

Hierarchical State Machine for NPC AI
This diagram shows an HFSM for an NPC. Notice the 'Non-Combat' super-state. An event like "Enemy visible" only needs one transition arrow leading away from the 'Non-Combat' box, not separate arrows from 'Idling' and 'Patrolling'. Likewise, a "Health low" event could trigger a transition from a 'Combat' super-state to 'Fleeing' without needing a transition from every single combat sub-state.

Imagine your NPC is in combat. It might be in the Chase, Attack, or Taunt sub-state. If the player runs away and is no longer visible, the NPC should go back to patrolling. In a flat FSM, you'd need three transitions:

  • Chase -> Patrol
  • Attack -> Patrol
  • Taunt -> Patrol

With an HFSM, you can group Chase, Attack, and Taunt under a Combat super-state. Then you only need a single transition:

  • Combat -> Patrol (triggered by the "player not visible" event)

This transition is inherited by all of Combat's children. The system checks the current state for a handler for the event; if it doesn't find one, it checks the parent, and so on up the tree. This dramatically cleans up the state diagram and makes the logic much easier to reason about.


How it Works in Practice (A Conceptual Look)

So, how does the machine actually execute this hierarchical logic? While we'll get into the Luau implementation in the next lesson, the core idea is based on traversing the state tree.

A key concept is that a state is an object that can have its own state machine. This allows for a recursive structure.

Code Class - Hierarchical State Machines

This video by AdamCYounis, though using Unity and C#, provides an excellent conceptual breakdown of how to think about executing an HFSM. The principles are directly applicable to our Luau OOP approach.

Watch from 12:45 to 15:54. Focus on the explanation of how states can have their own states, creating a tree. The idea of a doBranch function that recursively calls update on the active states down the hierarchy is the crucial takeaway.

This recursive processing is what enables the behavioral inheritance we discussed:

  • OnUpdate(): To update the FSM, you call update on the root state, which calls update on its active child, which calls update on its active child, and so on. This cascading call ensures all logic in the current state configuration is executed.
  • OnEnter(): When transitioning into a state deep in the hierarchy, OnEnter is called from the top down. To enter Chase, you first enter Combat and then Chase.
  • OnExit(): When transitioning out, OnExit is called from the bottom up. To leave Chase, you first exit Chase and then exit Combat.

This ensures that setup and teardown logic for parent states is handled correctly.


Conclusion

In this lesson, we've formally introduced the Hierarchical Finite State Machine as a solution to the scalability problems of simple FSMs.

Key Takeaways:

  • An HFSM is an FSM where states can be nested inside other states, forming a hierarchy or tree structure.
  • HFSMs solve state duplication by allowing common behavior to be placed in parent states, which is then inherited by all child states.
  • HFSMs solve transition explosion by allowing transitions to be defined on parent states. A single high-level transition can replace many individual transitions from sub-states.
  • The execution of an HFSM involves traversing the state tree, typically with update calls cascading down the active branch and enter/exit calls moving sequentially up or down the hierarchy.

You now have the theoretical foundation needed to build more robust, scalable, and maintainable AI and game logic. You understand why HFSMs are useful and how they solve common design problems.

Next Lesson Preview:
Theory is great, but code is better. In the next lesson, we'll get practical and modify our reusable FSM ModuleScript to support nested sub-states. We'll lay the groundwork for implementing the very patterns we've discussed today.