Today’s article introduces a small but capable and extensible finite state machine (FSM) that you can use to organize your Unity applications’ code. FSMs are commonly used to represent the different modes the app can be in—main menu, world map, gameplay, game over—and how the app transitions from one state to another. The FSM in today’s article follows the pure code methodology by not tying the code to game objects, MonoBehaviours, or scenes. So read on to learn about and make use of this “pure code” FSM for Unity!

There are three main concepts in this FSM:

  • State: a mode of the app that executes over time
  • Transition: the process of moving from one state to another
  • StateMachine: runs the current state and transitions to the next state

Let’s see how this manifests in code by first looking at the state:

/// <summary>
/// A mode of the app that executes over time. This begins the transition process and receives
/// notifications about its progress.
/// </summary>
public interface IState
{
	/// <summary>
	/// Notify the state that the transition process has begun the entering phase
	/// </summary>
	void BeginEnter();
 
	/// <summary>
	/// Notify the state that the transition process has finished the entering phase
	/// </summary>
	void EndEnter();
 
	/// <summary>
	/// Execute the state's logic over time. This function should 'yield return' until it has
	/// nothing left to do. It may also dispatch OnBeginExit when it is ready to begin transitioning
	/// to the next state. If it does this, this funtion will no longer be resumed.
	/// </summary>
	IEnumerable Execute();
 
	/// <summary>
	/// Dispatched when the state is ready to begin transitioning to the next state. This implies
	/// that the transition process will immediately begin the exiting phase.
	/// </summary>
	event EventHandler<StateBeginExitEventArgs> OnBeginExit;
 
	/// <summary>
	/// Notify the state that the transition process has finished exiting phase.
	/// </summary>
	void EndExit();
}

The most important function of IState is Execute. It’s similar to an Update or Tick function that does—or initiates—all of the logic the state performs over time. The other functions relate to starting transitions to other states and being notified about the transition’s progress.

The next concept is that of a transition. Here’s how that looks in code:

/// <summary>
/// A transition from one state to another. This is broken into two parts. The first is the 'exit'
/// process where the current state is left. The second is the 'enter' process where the next state
/// becomes the new current state. Both of these processes are executed over time.
/// </summary>
public interface IStateTransition
{
	/// <summary>
	/// Exit the current state over time. This function should 'yield return' until the exit process
	/// is finished.
	/// </summary>
	IEnumerable Exit();
 
	/// <summary>
	/// Enter the next state over time. This function should 'yield return' until the enter process
	/// is finished.
	/// </summary>
	IEnumerable Enter();
}

The transition is really just made up of two iterator functions—Exit and Enter—to represent the two phases of the transition: exiting the old state and entering the new state.

The final concept is exceedingly simple: the state machine itself.

/// <summary>
/// A finite state machine that runs a single state at a time and handles the transition process
/// from one state to another
/// </summary>
public interface IStateMachine
{
	/// <summary>
	/// Execute the state machine by executing states and transitions. This function should
	/// 'yield return' until there are no more states to execute.
	/// </summary>
	IEnumerable Execute();
}

The state machine simply has a function to execute it over time. This function in turn executes all the states and transitions.

With the three main concepts covered, let’s take a look at the final part consisting of two concrete classes. We’ll start with the EventArgs class that is dispatched when a state wants to transition to another state:

/// <summary>
/// Event args that are dispatched when a state wants to transition to another state
/// </summary>
public class StateBeginExitEventArgs : EventArgs
{
	/// <summary>
	/// The state to transition to
	/// </summary>
	public IState NextState { get; private set; }
 
	/// <summary>
	/// The transition to use to get to the next state
	/// </summary>
	public IStateTransition Transition { get; private set; }
 
	/// <summary>
	/// Create the event args
	/// </summary>
	/// <param name="nextState">The state to transition to</param>
	/// <param name="transition">The transition to use to get to the next state</param>
	public StateBeginExitEventArgs(
		IState nextState,
		IStateTransition transition
	)
	{
		NextState = nextState;
		Transition = transition;
	}
}

StateBeginExitEventArgs is just a wrapper for the two pieces of information that a state machine would need to execute the transition to the next state. That is, which state are we going to and how do we get there?

Finally, the last class is the actual StateMachine that implements IStateMachine to actually execute the states and transitions.

/// <summary>
/// A state machine implementation that executes states and transitions
/// </summary>
public class StateMachine : IStateMachine
{
	/// <summary>
	/// The current state
	/// </summary>
	private IState state;
 
	/// <summary>
	/// The next state (to transition to)
	/// </summary>
	private IState nextState;
 
	/// <summary>
	/// Transition to use to get to the next state
	/// </summary>
	private IStateTransition transition;
 
	/// <summary>
	/// Create the state machine with an initial state
	/// </summary>
	/// <param name="initialState">Initial state. BeginEnter() and EndEnter() will be called
	/// on it immediately</param>
	public StateMachine(IState initialState)
	{
		State = initialState;
		state.EndEnter();
	}
 
	/// <summary>
	/// Execute the initial state and any subsequent states and transitions until there are no more
	/// states to execute.
	/// </summary>
	public IEnumerable Execute()
	{
		while (true)
		{
			// Execute the current state until it transitions or stops executing
			for (
				var e = state.Execute().GetEnumerator();
				transition == null && e.MoveNext();
			)
			{
				yield return e.Current;
			}
 
			// Wait until the current state transitions
			while (transition == null)
			{
				yield return null;
			}
 
			// Stop listening for the current state to transition
			// This prevents accidentally transitioning twice
			state.OnBeginExit -= HandleStateBeginExit;
 
			// There is no next state to transition to
			// This means the state machine is finished executing
			if (nextState == null)
			{
				break;
			}
 
			// Run the transition's exit process
			foreach (var e in transition.Exit())
			{
				yield return e;
			}
			state.EndExit();
 
			// Switch state
			State = nextState;
			nextState = null;
 
			// Run the transition's enter process
			foreach (var e in transition.Enter())
			{
				yield return e;
			}
			transition = null;
			state.EndEnter();
		}
	}
 
	/// <summary>
	/// Set the current state, call its BeginEnter(), and listen for it to transition
	/// </summary>
	/// <value>The state to be the new current state</value>
	private IState State
	{
		set
		{
			state = value;
			state.OnBeginExit += HandleStateBeginExit;
			state.BeginEnter();
		}
	}
 
	/// <summary>
	/// Handles the current state wanting to transition
	/// </summary>
	/// <param name="sender">The state that wants to transition</param>
	/// <param name="e">Information about the desired transition</param>
	private void HandleStateBeginExit(object sender, StateBeginExitEventArgs e)
	{
		nextState = e.NextState;
		transition = e.Transition;
	}
}

The StateMachine is a pretty straightforward implementation of IStateMachine where the Execute iterator just executes the current state and the phases of transition over and over until there is a transition to a null state.

That’s it for the state machine system. Here it is all together so you can put it in a single .cs file to include into your projects. You could also break it into multiple .cs files if you’d prefer. Notice that it doesn’t have any dependencies on Unity, so you’re free to use it in other .NET environments such as command-line apps or Xamarin.

////////////////////////////////////////////////////////////////////////////////////////////////////
// About:   Finite State Machine by Jackson Dunstan
// Article: http://JacksonDunstan.com/articles/3137
// License: MIT
// Copyright © 2015 Jackson Dunstan
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the “Software”), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////////////////////////
 
using System;
using System.Collections;
using System.Collections.Generic;
 
/// <summary>
/// Event args that are dispatched when a state wants to transition to another state
/// </summary>
public class StateBeginExitEventArgs : EventArgs
{
	/// <summary>
	/// The state to transition to
	/// </summary>
	public IState NextState { get; private set; }
 
	/// <summary>
	/// The transition to use to get to the next state
	/// </summary>
	public IStateTransition Transition { get; private set; }
 
	/// <summary>
	/// Create the event args
	/// </summary>
	/// <param name="nextState">The state to transition to</param>
	/// <param name="transition">The transition to use to get to the next state</param>
	public StateBeginExitEventArgs(
		IState nextState,
		IStateTransition transition
	)
	{
		NextState = nextState;
		Transition = transition;
	}
}
 
/// <summary>
/// A mode of the app that executes over time. This begins the transition process and receives
/// notifications about its progress.
/// </summary>
public interface IState
{
	/// <summary>
	/// Notify the state that the transition process has begun the entering phase
	/// </summary>
	void BeginEnter();
 
	/// <summary>
	/// Notify the state that the transition process has finished the entering phase
	/// </summary>
	void EndEnter();
 
	/// <summary>
	/// Execute the state's logic over time. This function should 'yield return' until it has
	/// nothing left to do. It may also dispatch OnBeginExit when it is ready to begin transitioning
	/// to the next state. If it does this, this funtion will no longer be resumed.
	/// </summary>
	IEnumerable Execute();
 
	/// <summary>
	/// Dispatched when the state is ready to begin transitioning to the next state. This implies
	/// that the transition process will immediately begin the exiting phase.
	/// </summary>
	event EventHandler<StateBeginExitEventArgs> OnBeginExit;
 
	/// <summary>
	/// Notify the state that the transition process has finished exiting phase.
	/// </summary>
	void EndExit();
}
 
/// <summary>
/// A transition from one state to another. This is broken into two parts. The first is the 'exit'
/// process where the current state is left. The second is the 'enter' process where the next state
/// becomes the new current state. Both of these processes are executed over time.
/// </summary>
public interface IStateTransition
{
	/// <summary>
	/// Exit the current state over time. This function should 'yield return' until the exit process
	/// is finished.
	/// </summary>
	IEnumerable Exit();
 
	/// <summary>
	/// Enter the next state over time. This function should 'yield return' until the enter process
	/// is finished.
	/// </summary>
	IEnumerable Enter();
}
 
/// <summary>
/// A finite state machine that runs a single state at a time and handles the transition process
/// from one state to another
/// </summary>
public interface IStateMachine
{
	/// <summary>
	/// Execute the state machine by executing states and transitions. This function should
	/// 'yield return' until there are no more states to execute.
	/// </summary>
	IEnumerable Execute();
}
 
/// <summary>
/// A state machine implementation that executes states and transitions
/// </summary>
public class StateMachine : IStateMachine
{
	/// <summary>
	/// The current state
	/// </summary>
	private IState state;
 
	/// <summary>
	/// The next state (to transition to)
	/// </summary>
	private IState nextState;
 
	/// <summary>
	/// Transition to use to get to the next state
	/// </summary>
	private IStateTransition transition;
 
	/// <summary>
	/// Create the state machine with an initial state
	/// </summary>
	/// <param name="initialState">Initial state. BeginEnter() and EndEnter() will be called
	/// on it immediately</param>
	public StateMachine(IState initialState)
	{
		State = initialState;
		state.EndEnter();
	}
 
	/// <summary>
	/// Execute the initial state and any subsequent states and transitions until there are no more
	/// states to execute.
	/// </summary>
	public IEnumerable Execute()
	{
		while (true)
		{
			// Execute the current state until it transitions or stops executing
			for (
				var e = state.Execute().GetEnumerator();
				transition == null && e.MoveNext();
			)
			{
				yield return e.Current;
			}
 
			// Wait until the current state transitions
			while (transition == null)
			{
				yield return null;
			}
 
			// Stop listening for the current state to transition
			// This prevents accidentally transitioning twice
			state.OnBeginExit -= HandleStateBeginExit;
 
			// There is no next state to transition to
			// This means the state machine is finished executing
			if (nextState == null)
			{
				break;
			}
 
			// Run the transition's exit process
			foreach (var e in transition.Exit())
			{
				yield return e;
			}
			state.EndExit();
 
			// Switch state
			State = nextState;
			nextState = null;
 
			// Run the transition's enter process
			foreach (var e in transition.Enter())
			{
				yield return e;
			}
			transition = null;
			state.EndEnter();
		}
	}
 
	/// <summary>
	/// Set the current state, call its BeginEnter(), and listen for it to transition
	/// </summary>
	/// <value>The state to be the new current state</value>
	private IState State
	{
		set
		{
			state = value;
			state.OnBeginExit += HandleStateBeginExit;
			state.BeginEnter();
		}
	}
 
	/// <summary>
	/// Handles the current state wanting to transition
	/// </summary>
	/// <param name="sender">The state that wants to transition</param>
	/// <param name="e">Information about the desired transition</param>
	private void HandleStateBeginExit(object sender, StateBeginExitEventArgs e)
	{
		nextState = e.NextState;
		transition = e.Transition;
	}
}

Now let’s take a look at how to use the system starting with a transition that fades out during the exit phase and in during the enter phase. It uses a full-screen Image UI to accomplish this.

/// <summary>
/// A transition that fades the screen out during the exit phase and in during the enter phase
/// </summary>
public class ScreenFadeTransition : IStateTransition
{
	private Canvas canvas;
	private Image cover;
	private float fadeTime;
 
	/// <summary>
	/// Create the transition
	/// </summary>
	/// <param name="fadeTime">Time in seconds to complete both parts of the phase</param>
	public ScreenFadeTransition(float fadeTime)
	{
		// Set up the fade cover
		var screenFadePrefab = Resources.Load<Canvas>("ScreenFade");
		canvas = UnityEngine.Object.Instantiate(screenFadePrefab);
		var coverGO = canvas.transform.Find("Cover");
		cover = coverGO.GetComponent<Image>();
		this.fadeTime = fadeTime;
	}
 
	public IEnumerable Exit()
	{
		// Fade out
		foreach (var e in TweenAlpha(0, 1, fadeTime / 2))
		{
			yield return e;
		}
	}
 
	public IEnumerable Enter()
	{
		// Fade in
		foreach (var e in TweenAlpha(1, 0, fadeTime / 2))
		{
			yield return e;
		}
 
		// Clean up the fade cover
		UnityEngine.Object.Destroy(canvas.gameObject);
	}
 
	// Tween the alpha of the fade cover
	private IEnumerable TweenAlpha(
		float fromAlpha,
		float toAlpha,
		float duration
	)
	{
		var startTime = Time.time;
		var endTime = startTime + duration;
		while (Time.time < endTime)
		{
			var sinceStart = Time.time - startTime;
			var percent = sinceStart / duration;
			var color = cover.color;
			color.a = Mathf.Lerp(fromAlpha, toAlpha, percent);
			cover.color = color;
			yield return null;
		}
	}
}

Now let’s look at how we’d implement a state starting with a simple main menu:

/// <summary>
/// A state for the main menu
/// </summary>
public class MainMenuState : IState
{
	private Canvas canvas;
	private Text frameCount;
	private int initialFrame;
 
	public void BeginEnter()
	{
		// Create the main menu UI
		var canvasPrefab = Resources.Load<Canvas>("MainMenu");
		canvas = UnityEngine.Object.Instantiate(canvasPrefab);
		var playButtonGO = canvas.transform.Find("PlayButton");
		var playButton = playButtonGO.GetComponent<Button>();
		playButton.onClick.AddListener(HandlePlayButton);
		var frameCountGO = canvas.transform.Find("FrameCount");
		frameCount = frameCountGO.GetComponent<Text>();
		initialFrame = Time.frameCount;
	}
 
	public void EndEnter()
	{
	}
 
	public IEnumerable Execute()
	{
		// Update the frame count until we transition
		while (true)
		{
			var numFrames = Time.frameCount - initialFrame;
			frameCount.text = "Frames spent on menu: " + numFrames;
			yield return null;
		}
	}
 
	public event EventHandler<StateBeginExitEventArgs> OnBeginExit;
 
	public void EndExit()
	{
		// Clean up the UI
		UnityEngine.Object.Destroy(canvas.gameObject);
	}
 
	private void HandlePlayButton()
	{
		// Transition to the play state using a screen fade
		var nextState = new PlayState();
		var transition = new ScreenFadeTransition(2);
		var eventArgs = new StateBeginExitEventArgs(nextState, transition);
		OnBeginExit(this, eventArgs);
	}
}

This main menu is really simple and hardly does anything. It shows a frame count on a Text UI purely as an example of some logic performed over time. When the play Button UI is clicked, it transitions to the PlayState using the screen fade transition above.

Let’s take a look at that play state to see the “game” portion of this example:

/// <summary>
/// A state for the game play portion of the example
/// </summary>
public class PlayState : IState
{
	private GameObject targetsContainer;
	private List<GameObject> targets;
 
	public void BeginEnter()
	{
		// Set up the targets
		targetsContainer = new GameObject("TargetsContainer");
		var targetPrefab = Resources.Load<GameObject>("Target");
		targets = new List<GameObject>(3);
		for (var i = 0; i < targets.Capacity; ++i)
		{
			var target = UnityEngine.Object.Instantiate(targetPrefab);
			target.transform.parent = targetsContainer.transform;
			target.transform.position += new Vector3(i*2, 0, 0);
			targets.Add(target);
		}
	}
 
	public void EndEnter()
	{
		// Turn the targets green to indicate that they're ready to be clicked
		foreach (var target in targets)
		{
			SetTargetColor(target, Color.green);
		}
	}
 
	public IEnumerable Execute()
	{
		// Handle clicks
		while (true)
		{
			if (Input.GetMouseButtonDown(0))
			{
				HandleClick();
			}
			yield return null;
		}
	}
 
	public event EventHandler<StateBeginExitEventArgs> OnBeginExit;
 
	public void EndExit()
	{
		// Clean up the targets
		UnityEngine.Object.Destroy(targetsContainer);
	}
 
	private void HandleClick()
	{
		// Cast a ray where the user clicked
		var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
		RaycastHit hitInfo;
		if (Physics.Raycast(ray, out hitInfo) == false)
		{
			return;
		}
 
		// Check if the user clicked any targets
		foreach (var target in targets)
		{
			if (target != null && hitInfo.transform == target.transform)
			{
				HitTarget(target);
				break;
			}
		}
	}
 
	private void HitTarget(GameObject target)
	{
		// Turn the target red so the user knows they hit it
		SetTargetColor(target, Color.red);
 
		// If the user hit all the targets
		targets.Remove(target);
		if (targets.Count == 0)
		{
			// Transition to the main menu using the screen fade transition
			var nextState = new MainMenuState();
			var transition = new ScreenFadeTransition(2);
			var eventArgs = new StateBeginExitEventArgs(nextState, transition);
			OnBeginExit(this, eventArgs);
		}
	}
 
	private void SetTargetColor(GameObject target, Color color)
	{
		var renderer = target.GetComponent<Renderer>();
		renderer.material.color = color;
	}
}

The “game” is trivial: click three objects. When you do, the play state transitions to the main menu state using the screen fade transition.

Finally, here’s the code to start and run the example:

public class TestScript : MonoBehaviour
{
	void Start()
	{
		// Create the initial state
		var mainMenuState = new MainMenuState();
 
		// Create the state machine
		var stateMachine = new StateMachine(mainMenuState);
 
		// Run the state machine
		StartCoroutine(stateMachine.Execute().GetEnumerator());
	}
}

Here are a couple of screenshots of the example app:

Main Menu State

Play State

That wraps things up for today. If you don’t have a state machine already, perhaps you’ll find this one useful. It’s tiny—under 100 lines without comments—but quite capable and extensible. If you do already have a state machine that you use, let me know how it compares to this one in the comments. How did you tackle problems like transitions? Do you use game objects and scenes or is your state machine “pure code”?