The .NET framework’s reflection setup can be amazingly useful. At a basic level, it allows you to get just about any information you could want about any type, method, assembly, etc that you could want. In addition, you can programmatically access type constructors and methods, and invoke them directly, allowing you to do all sorts of neat stuff.
One useful application of this is in the creation of state machines. Imagine an entity in a game that flies around in a pattern for a bit, then stops to shoot some bullets, then returns to flying. Such an entity would have two states, “Flying” and “Shooting.”
A First Attempt
You might decide to create a state machine that could handle this entity, but also be usable for all entities that need a state structure. What should a state machine like that need to be able to do?
One such set of possibilities is as follows:
- Be able to assign a state based on a string state name.
- Each state has three functions:
- Begin – This function is called whenever the state is transitioned into, allowing it to initialize anything that needs it.
- Tick – This is the meat of a state, it is called once per game tick while the state is active.
- End – This is called when the state is transitioned out of, allowing any clean up. Note that this is called before the next state’s Begin function is called.
- However, a state shouldn’t have to specify functions that it doesn’t need. If a state has no need for the Begin function, it doesn’t need to implement it.
- If the state changes during a Tick call, the new state will tick as soon as it happens. This allows many states to fall through in rapid succession. For instance, a player character in a 2D Mario-like puzzle might be in the middle of the “Jumping” state, while holding right on the controller. When the character hits the ground, it would transition into the “Standing” state, but from there, it would then transition into “Walking” because the player is holding right. That way, the character would land and immediately start walking, which is what one would expect when playing the game.
So what would a first draft version of such a machine need to look like? First, we’d need a place to store delegates to the three functions needed for a state:
public delegate void StateDelegate(); class StateInfo { public StateDelegate OnBegin { get; set; } public StateDelegate OnTick { get; set; } public StateDelegate OnEnd { get; set; } public void Begin() { if (OnBegin != null) OnBegin(); } public void Tick() { if (OnTick != null) OnTick(); } public void End() { if (OnEnd != null) OnEnd(); } public StateInfo() { OnBegin = null; OnTick = null; OnEnd = null; } }
Given that, we can now store them in a dictionary, indexed by by the state’s name (a string). To initialize it, we would just call AddState with the state name and three delegates (which may be null, to specify that a given function is unnecessary), and it would add that state to the list. We also need to be able to Tick the state machine and change the state. The whole contraption would look something like the following:
public class StateMachine { Dictionary<string, StateInfo> states = new Dictionary<string, StateInfo>(); bool ticking = false; StateInfo currentStateInfo = null; string currentStateName = null; public void AddState(string stateName, StateDelegate begin, StateDelegate tick, StateDelegate end) { StateInfo info = new StateInfo(); info.OnBegin = begin; info.OnTick = tick; info.OnEnd = end; states.Add(stateName, info); // If no states have been set yet, this state becomes the initial state. if(currentStateName == null) State = stateName; } public string State { get { return currentStateName; } set { // End the previous state. if (currentStateName != null) currentStateInfo.End(); // Set the new state currentStateName = value; currentStateInfo = states[currentStateName]; // Initialize it. currentStateInfo.Begin(); // If we're in the middle of the Tick function, go ahead and tick the new // state now, as well. if (ticking) currentStateInfo.Tick(); } } public void Tick() { // Set ticking so that we know that we're in the middle of ticking this machine. // That way, if the state changes in the middle of this, we know to run the next // state's Tick as well. ticking = true; currentStateInfo.Tick(); ticking = false; } }
As you can see, it’s fairly straightforward. An entity can call AddState (probably during its constructor) on the state machine to add a new state. It can assign a new state by using State = “newState”, which will end the current state and begin the new state. Finally, the entity just needs to call Tick to run the current state.
In practice, this might look something like:
public class FlyShooter : EntityBase { /* Entity vars Go here */ StateMachine machine = new StateMachine(); public FlyShooter() { /* Initialize stuff */ // Flying doesn't need an end machine.AddState("Flying", FlyingBegin, FlyingTick, null); // Shooting doesn't need Begin or End. machine.AddState("Shooting", null, ShootingTick, null); } void FlyingBegin() { /* Begin flying state */ } void FlyingTick() { if( IsTimeToShoot ) { machine.State = "Shooting"; return; } /* Tick flying */ } void ShootingTick() { if( DoneShooting ) { machine.State = "Flying"; return; } /* Tick shooting */ } public override void Tick() { machine.Tick(); /* Also tick anything that happens in all states */ } }
There’s not much to it.
One thing to note is that the way that the state fall-through works (that is, changing state in the middle of another state’s Tick function) will still cause the rest of the current function to execute (after it’s already partially run another state), so it’s best to either change states at the very end of the function or to return after setting the new state, as is done above).
Though this works pretty well as-is, say you want to add new states to an entity afterwards. What if the enemy now needs a special “Dying” state. Not only do you have to add new functions (DyingBegin, DyingTick, and DyingEnd), you also have to remember to call AddState at object startup. Wouldn’t it be nice if you could just add the new functions and it would just work? As it turns out, using .NET’s reflection functionality, you can do exactly that!
Mirror, Mirror On the Wall, What Are the States That I Can Call?
There are a number of ways to implement a state machine system using reflection. The one that was settled upon for Procyon was the design with the least redundancy (that is, “don’t repeat yourself”). State names only appear once in a given entity’s codebase (excepting when states are set), and it takes a small number of characters, right where the state code is defined, to mark a state. At the point that it’s added to the code, it’s already ready to use, there’s no additional list of states to update.
Most reflection code works by looking for .NET Attributes. Attributes are class objects that can be added as metadata to a class, a method, a property, or any number of other pieces of your code. Also, you can create your own attributes – program-specific metadata that you can later find.
In the case of the state machine, a very simple attribute is needed.
using System.Reflection; [AttributeUsage(AttributeTargets.Method)] public class State : Attribute { }
It doesn’t need any sort of custom data, so there’s not much to it. The AttributeUsage at the top (which is, itself, an attribute) specifies that the “State” attribute being defined can only be applied to methods. Attempting to add it to anything else (say, a class) would generate a compiler error.
With this attribute, you’ll be able to mark methods as states. In this case, each method will take a StateInfo as a parameter (meaning that the StateInfo class, which was described above, has to be a public class now). The method will then fill in the state information with delegates. The most elegant way to handle this is using anonymous delegates. Here’s that enemy class again, using the new method:
public class FlyShooter : EntityBase { /* Entity vars Go here */ StateMachine machine; public FlyShooter() { /* Initialize stuff */ // Create the new state machine with "Flying" as the initial state. machine = new StateMachine(this, "Flying"); // Note - no states are being explicitly initialized here. } [State] void Flying(StateInfo info) { info.OnBegin = delegate() { /* Begin flying state */ }; info.OnTick = delegate() { if (IsTimeToShoot) { machine.State = "Shooting"; return; } /* Tick flying */ }; // Don't need to set info.End } [State] void Shooting(StateInfo info) { info.OnTick = delegate() { if (DoneShooting) { machine.State = "Flying"; return; } /* Tick shooting */ }; } public override void Tick() { machine.Tick(); /* Also tick anything that happens in all states */ } }
As you can see, the [State] attribute is there. Each of the (up to) three functions for a state are initialized inside of the state function, cleanly grouping the three functions together into one unit. The state function’s name is the state’s name, so it’s only specified once. And, of course, there’s no manual list manipulation; to add a new state, simply add the code for the new state and make sure that it’s marked by a [State] attribute…the runtime will take care of the rest.
To get this whole thing to work, though, there’ll obviously have to be some code to find methods using it. When creating your state machine, you’ll now want to pass in a pointer to the object that the state machine belongs to. From that, it will be able to get the type of object for which you’re creating the state machine. Automatically adding states that are marked with your state attribute goes something like this:
- Scan through each method in the type of the object you passed in (if you want to be able to grab private methods from parent classes of the current object’s type, you’ll need to scan through the classes in the heirarchy one by one)
- If it doesn’t have the State attribute, continue on to the next method.
- If it does have the attribute, get the name of the method (this becomes the state’s name).
- Once you have the name of the method, call the method and let it populate a StateInfo that you hand to it.
- Once that’s done, add the method into the state dictionary, using the state method’s name as the key (and thus, the state name).
The AddState function can go away – it’s no longer necessary, as the state list is now update completely automatically. The StateMachine class’ constructor needs to be updated a bit, to add all of this reflection magic into it. It will now look something like this:
/// <summary> /// Create a new state machine using the given object - it will automatically /// search for State attribute-marked methods and use those to generate its /// state table at creation time. /// </summary> /// <param name="param">The object that the state machine belongs to</param> /// <param name="initialState">The name of the initial state of the machine.</param> public StateMachine(object param, string initialState) { Type paramType = param.GetType(); // Loop through parent types to make sure to pick up any private states. for (Type currentType = paramType; currentType != typeof(object); currentType = currentType.BaseType) { // Now loop through each method in this type (and this type only, no parent methods). foreach (var method in currentType.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public)) { // See if this method is a State. If it's not, go on to the next method. object[] attributes = method.GetCustomAttributes(typeof(State), true); if (attributes.Length == 0) continue; // Get the method name, ensure it's not already in the list. string methodName = method.Name; if (states.ContainsKey(methodName)) continue; // Call into this method to populate the state info... StateInfo info = new StateInfo(); method.Invoke(param, new object[] { info }); // ...and add it into the state list. states.Add(methodName, info); } } if (initialState != null) State = initialState; }
None of the rest of the state machine class’ code needs to change; it all works exactly as it did before. The new constructor means that the list gets created at state machine creation time, but the rest of the internal data remains exactly the same.
Possible Improvements
It works pretty well as-is, but there are ways it could be improved. For instance:
- You could add a type of “Sleep” method to the state machine, to make it delay a certain number of ticks before continuing to tick the current state
- You could add a list of transitions to the StateInfo, so that all transitions to different states can be declared up-front, in an easy-to-read manner. That removes that extra state-setting clutter from your tick routine (and even means that a state may not need a Tick routine at all!)
- If you need multiple sets of states for objects (say the player needs a collection of states for movement and a separate collection of states for weapons fire), you could modify the State attribute to provide a collection name (like [State(“Movement”)]), and specify a state collection when creating your state machine.
This is a small list of possible changes. If you have other ideas, let me know!