Friday, 12 November 2010

C# State Machine class

For my little XNA game that I am developping at the moment, the main thing I needed was a simple Finite State Machine class that I could use as the basis for prtty much any game object, especially for all the GUI and GameState logic.

I didn't want to spend too much time on a framework or grab one of the many libraries available here and there and then spend some time learning how to use it. Instead, I had the feeling that using the reflection capabilities of C#, I could easily and quickly come up with a simple piece of code that would fulfill my needs for this project. Functionnality-wise, my inspiration was Steve Rabin's article about a set of C++ macros for writing simple state machines. I used them quite a lot in the past and liked the fact the simple feature set, perfectly suited for small scale projects.

Basically, a State Machine can be in a certain number of differnt states and transition from one to another. Each state has:
  • an Enter() function callled when this state becomes active. This is the perfect place to do per-state setups
  • an Update() function that should be called once per frame
  • an Exit() function, called when leaving a state for another one. This is the perfect place to do some cleanup before leaving the state
State and handlers are defined via a simple naming convention that applies everywhere:
   StateName__Handler(...)


As a quick example to make that obvius, let's have a look at the Default state (which does nothing at all):
   public virtual void Default__Enter(string _OldState){}
   public virtual void Default__Exit(string _NewState){}
   public virtual void Default__Update(GameTime _GameTime){}



On top of that, each state can define message handlers for any message it wants to support. Message are defined by a simple string. For instance, the handler for a message called TakeDamage in the Default state and parametrized with an amount of damage points would imply the following bits:

   public virtual void Default__TakeDamage(int _Damage)
   {
      Health -= _Damage;
   }

And game code would send the message like this:
   Target.ProcessMessage( "TakeDamage", 10 );

As you can see this is all really simple. But here comes the goodness :)
  • You can have handlers that handle the same message differently per state:
   public virtual void Default__TakeDamage(int _Damage)
   {
      Health -= _Damage;

   }

   public virtual void Invincible__TakeDamage(int _Damage)
   {
   }
  • You can derive a state machine class from an existing class and override handlers so they do different things:
   class Soldier : StateMachine
   {
      public virtual void Default__TakeDamage(int _Damage)
      {
         Health -= _Damage;

      }
   }

   class SoldierFragile : Soldier
   {
      public virtual void Default__TakeDamage(int _Damage)
      {
         Health -= _Damage * 5;

      }
   }

The implementation might be rather simple and compact but it'still quite powerfull as you can see.

However, nothing is perfect in this world and I am sure some design choices are arguable ... but they suit my needs so I'm fine with it. I think the major concerns would be:
  • Weak state referencing as it is all done by strings and naming convention. So if you write typos or rename stuff around, this is likely to break. More error handling could be added but ultimately, this is just a string and this is rather fragile.
  • State specific variables will live at instance scope, not state scope. This is because I want to be able to acces the state machine members from anystate (this reduces the nb of accessors and stuff you need to write)
If you are interested in giving it a try or looking how it is done, you can find the source code with a stupid test case here. Note that I personally use it with XNA but this is rather generic and could be used without XNA. In fact, the provided implementation doesn't use XNA although it is straightforward to adapt it. In XNA, the State machine class should derive fron GameComponent.

Feedback welcome !

-m

No comments:

Post a Comment