Unity State Machines

I mentioned in my first Unity Pro Tips blog post that I use GameObjects as states. Let me elaborate on that pattern since I think it is very powerful and allows you to structure your scenes such that it’s easy to tell exactly what is happening at any given time.

I’m going to use the example of the simple state machine I used in “Purgeatory“, my latest Global Game Jam game made in Unity. In Purgeatory, you kill “good” or “bad” people to change your Karma which sends you to heaven or hell. You are constantly trying to change your Karma to warp to the different levels (heaven, earth, or hell). Only one of those levels are active at any time: the level you are in is your state. This is a simple example, but I hope it will demonstrate the concept and you may see how it applies to more complex examples such as animation or AI state machines.

Here is the Hierarchy for the game state:

Each state has particular behaviours. For instance, each state has a camera position, spawns characters, and teleports the character to his spawn location. Here is the layout of GameObjects for that:

Hierarchy layout for the state machine

Since only one state is enabled at a time, you can easily see what’s going on. Here is an example of the hierarchy window during actual gameplay:

Now this is helpful, but let me explain the power of using this setup. Code inside a behaviour is only executed when it is enabled and the GameObject with which it is contained is also enabled. This is true for most Unity functions, but most ‘advanced’ code will break this rule by registering callbacks which can execute while it is disabled. I have named the states with “(OnEnable/OnDisable)” to help guide my fellow programmer on the project. The trick is that Unity has built-in calls to OnEnable and OnDisable, allowing you to create behaviours that respond to these engine-level events. By using this pattern, you can hook into those calls and create behaviours that execute only when the state they are contained in is enabled. You do this by enabling your callbacks in OnEnable and disabling them in OnDisable. Following this pattern, it is easy to create reusable behaviours that can execute in any state machine as there are no dependencies on the state machine implementation. This is a very good thing for code reuse.

Moreover, by using the scene hierarchy to create your states, you can then create states without modifying any code. This can allow non-technical team members to create states how they see fit. They simply drag the behaviours they want to occur inside a new GameObject and add that to the state machine object (named “0. Root Game Object” in this example). This is pretty cool, even for someone as technically minded as myself. It helps me wrap my head around what is happening and is easy to understand months after I have written all the code. With all that said, let’s see the implementation:

public void SetState( GameObject newState )
{
	if ( CurrentState != null )
		CurrentState.SetActiveRecursively(false);
		
	newState.SetActiveRecursively(true);
	CurrentState = newState;
}

But wait, there’s more! One of the tricky parts about state machines is how state switching is handled. If you want to debug what happens when you’re switching from one state to the next, you must normally play the game until the state change happens. With a simple Unity Editor script, you no longer have to do this. Let’s look at “0. Root Game Object” during gameplay now:

The thing to notice is the red variable which is displaying what state we’re in. This is handy by itself, but what’s even more interesting is that because states are just GameObjects, we can actually click on that variable and go directly to the object in the hierarchy. More-over, this is a custom editor; so when I drag another object onto that variable (as you normally do to assign variables in the inspector), the state will actually change at runtime and I can see if there are any errors switching between the two! For instance, I can drag “1. Heavy Play Objects” onto that variable, and be instantly teleported to the Heaven level without actually having to play the game and reach that condition. This is the holy grail of debugging states, and it takes only a few lines of code so let’s take a look at that now.

Here is the code for PurgeatoryGameEditor:

// We are editing PurgeatoryGame
[CustomEditor(typeof(PurgeatoryGame))]
public class PurgeatoryGameEditor : Editor
{
    // Override this to display your custom inspector code
    public override void OnInspectorGUI()
    {
	// First draw the default inspector
	DrawDefaultInspector();
		
	// Now draw our custom fields only
        // if we're playing the game
	if ( !EditorApplication.isPlaying )
		return;
		
	PurgeatoryGame Game = target as PurgeatoryGame;
		
	// Save the color so the
        // GUI doesn't stay red
	Color oldColor = GUI.color;
	GUI.color = Color.red;
		
	// Draw the custom "Current State" field...
	GameObject newState = EditorGUILayout.ObjectField(
		"CurrentState", Game.CurrentGameLocation,
		typeof(GameObject), true ) as GameObject;
		
	// And execute the change state code if
        // we've received a new game object
	if ( newState != Game.CurrentGameLocation )
	{
	    Game.SetState( newState );
	}
		
	// Restore the old color so the
        // GUI doesn't stay red
	GUI.color = oldColor;
    }
}

The ObjectField does the magic of displaying the variable, and the Game.SetState does the magic of setting the new state (as described above). That’s it! Writing a Custom Editor like this takes less than five minutes; such little time I was able to benefit from writing it within the 48 hour game jam. It was definitely a big win when trying to test the game and ensure all the states worked correctly.

I hope you can benefit from this pattern in your day-to-day use of the engine!

Leave a Comment