2013-09-26

Implementing a Transition-based State Machine

Warning: this is one of the code heavy posts I once warned about. Those allergic to discussions of programming topics or big listings of code (C# code; it's not that bad), keep walking. Or stay and become harder, better, faster, stronger...

In this article I will introduce a (probably not so) new approach to implementing State Machines, mostly for videogames. The reference is, as usual, Buckland's implementation in his Programming Game AI by Example book. His version of the state machine was and still is the de facto standard when coding small AIs or logical behaviours. Although new and improved systems exist, like Behaviour Trees, FSMs are still pervasive in the video game industry.

FSMs have been slightly improved through time, as languages and techniques developed, offering new possibilities, but the core remains pretty much unchanged. Here I will introduce one major revision to how SMs are implemented and, more important, used.

Basic introduction to the classic State Machine

In Buckland's book, State Machines are composed of several elements:
  • The State Machine itself, which contains
    •  A reference to the machine's owner
    • The current state to execute
    • The global state to execute
    • The previous state, in case a behaviour requires going back to it
  • A base generic abstract class State<T>, with methods Enter, Update and Exit. Enter is called when the state becomes the current one, Exit when it stops being so. Update is called on every execution cycle (of the state machine, not necessarily of the game or entity).
  • A collection of singletons derived from State<T>, one for each behaviour T can display.
  • A messaging system which allows communication between entities. State<T> includes an abstract method to evaluate messages and treat them if appropriate.
Important details:
  • States are, curiously, stateless. That's why a reference to the owner is required: it must contain all the state information the machine needs.
  • It is considered in bad taste (but unfortunately common) to transition from outside the states themselves, but the state machine usually allows this through several hijacking methods.
Thus, each state is responsible for checking its possible transitions. State hijacking is supported via the global states, which check conditions outside the scope of low level ones. If used, global states usually become a state machine of their own, to support all the possible situations.