Building a Modular Character Controller: State and Interfaces

I love building character controllers. I love doing the vector math. I love testing and tweaking until everything feels super tight, even if I am just controlling a capsule model. I love seeing how the systems I come up with could be expanded to accommodate different game features. For example, one of my favorite variable names I have used is “TractionFraction.” It was a float I used to modulate the acceleration and deceleration of the character. While this variable was mainly there to allow me to tweak the feel of the character’s motion, it also allowed for the implementation of slick surfaces.

As fun as they can be to make, character controllers can also be a big headache. For one, they can get rather complex the more moves or actions the character can perform. The character’s available actions may be limited or altered based on what the character is already doing. Perhaps a charging tackle attack is performed when the attack button is pressed while in a full sprint, where there same input produces a quick jab while stationary. Perhaps the character will not be able to move or attack when lying on the ground dazed, but every input serves to shorten the time the character is rendered a useless layabout.

The Super Smash Brothers franchise is a beautiful example of the complexity possible when each action (normal attack, special attack, grab, or shield) are altered based on the current movement and footing of the character.

Something like this could be attempted using a lot of Boolean flags and some very branched input logic. However, this gets very messy very fast. If you want to make a change, you first have to familiarize yourself with the tangle of if statements and the statements which set their flags. Also, this method could lead to strange bugs by allowing invalid combinations of flag values. It does not make much sense to say a character is in free-fall and also sprinting. So why use a programming structure that would allow for such a nonsensical combination?

The State programming pattern handles this problem quite nicely. The basic definition of this pattern is to “allow an object to alter its behavior when its internal state changes. The object will appear to change classes.” In this post we will discuss a finite state machine. The defining factors of this model will be:

  • The state machine will have a set number of possible states.
  • The state machine can only be in one state at a time.
  • Inputs and events will be passed to the states.
  • Transitions tied to inputs and events will be defined for each state.

This model uses two types of objects: the state and the state machine. The state machine is in charge of keeping references to states, keeping an active state, and calling the virtual methods of states. The state itself holds the all behavior. It responds to input and initiates state transitions.

I have produced some simple base classes which can be inherited from when building a controller in Unity.

The State Class

Bitbucket Link

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class State {
    public StateMachine Machine;

    public State () {

    }

    public static implicit operator bool (State state) {
        return state != null;
    }

    public virtual void OnStateInitialize (StateMachine machine) {
        Machine = machine;
    }

    public virtual void OnStateEnter () {

    }

    public virtual void OnStateExit () {

    }

    public virtual void Update () {

    }
}

To create a new state inherit from this class and override its virtual methods to define your desired behavior. Just be sure to call base.OnStateInitialize if you override it, or the state will not have the reference to StateMachine required to change states. Initialize will be called once when the state object is created by the StateMachine. OnStateEnter and OnStateExit are called during state transitions, first the old state’s OnStateExit, then the new state’s OnStateEnter. The Update function will be called by the StateMachine each time Unity calls its update. Note that the State is not a Monobehaviour while StateMachine is. Because of this, the State object does not inherently have a reference to a GameObject. It should operate on the components of the StateMachine’s GameObject. Another reason why the reference to the StateMachine should never be broken. (In fact, Machine should probably be private with a protected getter.)

The StateMachine Class

Bitbucket Link

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class StateMachine : MonoBehaviour {

    protected List<State> statesList = new List<State> ();
    protected State currentState;

    void Update () {
        currentState.Update ();
    }

    /// <summary>
    /// Switch the currentState to a specific State object
    /// </summary>
    /// <param name="state">
    /// The state object to set as the currentState</param>
    /// <returns>Whether the state was changed</returns>
    protected virtual bool SwitchState (State state) {
        bool success = false;
        if (state && state != currentState) {
            if (currentState)
                currentState.OnStateExit ();
            currentState = state;
            currentState.OnStateEnter ();
            success = true;
        }
        return success;
    }

    /// <summary>
    /// Switch the currentState to a State of a the given type.
    /// </summary>
    /// <typeparam name="StateType">
    /// The type of state to use for the currentState</typeparam>
    /// <returns>Whether the state was changed</returns>
    public virtual bool SwitchState<StateType> () where StateType : State, new() {
        bool success = false;
        bool found = false;
        //if the state can be found in the list of states 
        //already created, switch to the existing version
        foreach (State state in statesList) {
            if (state is StateType) {
                found = true;
                success = SwitchState (state);
                break;
            }
        }
        //if the state is not found in the list, 
        //make a new instance
        if (!found) {
            State newState = new StateType ();
            newState.OnStateInitialize (this);
            statesList.Add (newState);
            success = SwitchState (newState);
        }
        return success;
    }
}

The Update method of the StateMachine class simply calls the update of the active State object. Make sure to call base.Update if you override the Update when inheriting this class.

The StateMachine class holds the SwitchState method. This will switch which state is the active state and call the OnStateEnter and OnStateExit methods of the appropriate states. This is a generic method, limited to State types. A new state object will be created if one of the same type has not already been created for this StateMachine. If one has already been created, it will be reused. This allows states to transition to one another without knowing anything about the target state other than its type. This helps minimize reference wrangling.

Handling Input

So, this looks good. It has the basics of a finite state machine. We can make different State classes for each possible state of our controller, safely encapsulating data and behavior that is only relevant in that state. There is one glaring fault, which may not be obvious until you smack into it. The states don’t really have a good way of handling input or events. Input, is not too big of an issue using Unity’s Input class in each state’s Update. But that could get repetitive fast. You could put it in the CharacterStateMachine and use it to call extra input Methods you define is State. That muddies up the state machine code with input code though. That is, it mixes code containing high-level abstractions with very specific low-level code. The StateMachine gets its power from being able to hot-swap behavior. It defeats the purpose to tie it down with:

if (Input.GetKeyDown(Keycode.Space)){
    currentState.SpaceWasPressed();
}
float MoveX = Input.GetAxis("Horizontal");
currentState.MoveHorizontally (MoveX);
...

Gross…

The solution: interfaces! (You could use delegates too!)

This solution requires a new class and some interface or delegate definitions. You will need to make a ControllerInput class where all of your if (Input.GetWhatever () )s will live. Within these if-statements, you will either invoke your delegate for that type of input, or iterate through a list of interface objects listening for that input. In your States’ OnStateEnter and OnStateInitialize you either subscribe one of the State’s methods to the delegate, or add the state to the list of interface objects. In the OnStateExit method, unsubscribe or remove the State object from the list, depending on your approach.

Not only can this setup be used for input, but also triggered events. For example, a trigger zone may move your character from the walking state to the cut-scene state. Or walking on ice may make a call to a method implemented on the interface, ITractionInteraction, causing a swift decrease in the character’s TractionFraction. (Though you will get fired if you use this naming convention.)

It’s not any fewer lines of code. In fact, it is probably more. But it has the advantage that everything is highly modular. Just as you can easily pop different states in and out, you can swap out ControllerInput scripts too. By untangling the code, not only can you focus on your target feature, you can also recycle your code in different places. Ultimately, you spend less time typing:
if (Input.GetButtonDown (KeyCode…..
and more time making games.

 

Update 3/7/18:

Comments on a design mistake:

The previous implementation makes one big compromise. In order to maintain that only one state could ever be active, I ditched the Monobehaviour inheritance that Unity scripts default to when writing my State class. The idea was that the state objects would be contained in the state machine with no outside access, protected from outside meddling. This is a valid concern. My other assumption was less valid. Looking at the second SwitchState method, it is clear I was assuming no previous setup. Any state could be added at a whim by simply stating the type. But in practice this won’t come up much, especially when building a character controller, which requires tweaking values until the feel of the motion is perfect. Not to mention it is a FINITE state machine, so providing a way to add states willy-nilly seems like a divergence from the intentional bounds we are setting by choosing such a pattern. My assumptions (valid or not) blinded me to two major problems.

First, it removed one of the most powerful features of Unity from the workflow: the inspector. When you inherit from Monobehaviour, fields get serialized and displayed in the inspector and can be edited in real time. That real time editing is indispensable when building and tweaking a character controller. Storing States in a serialized List on StateMachine is not enough to make States and their fields visible and editable in the inspector. Not even an inspector as powerful as Odin handles serialization of list elements when inheritance is involved.

Second, a lot of functionality is built into Monobehaviour. In my example above, I showed how to call the State’s Update from the StateMachine’s Update. But what about OnCollisionEnter, OnCollisionExit, OnCollisionStay, OnTriggerEnter, OnTriggerExit, OnTriggerStay, and all of their 2D variants? What about FixedUpdate, LateUpdate, and every other message listed here? Handling all of these manually would be a massive, unnecessary pain. The functionality is already there. Don’t be a hero. Just use it.

A bonus side effect of inheriting MonoBehaviour is that if all of the states are components of the GameObject housing the StateMachine, it is very simple to see what state is active via that blue checkmark.

How To Fix It and Additional Improvements:
State:
  • Change State to inherit MonoBehaviour.
  • Remove its empty constructor.
  • Handle unwanted tampering in OnEnable and OnDisable. Only one State should be active at a time. Check States against their Machine’s currentState to determine if outside tampering in evident. If it is, undo the change and issue a Debug.Warning.
  • Require a StateMachine component and set Machine via GetComponent in OnValidate. OnValidate calls whenever you change something in the inspector. So as soon as you drag a new state onto a GameObject, it will ensure there is a StateMachine component, then grab it to use as its very own Machine.
  • Machine can be made protected. StateMachine does not need access if the State keeps track of the machine on its own.
  • Make non-virtual Initialize, StateEnter, and StateExit methods that the Machine calls which in turn call virtual OnStateInitialize, OnStateEnter, and OnStateExit methods. This way, you can enable/disable the State component in StateEnter and StateExit without worrying that someone will override them without calling the base method first. Additionally, UnityEvents for OnStateInitialize/Enter/Exit can be invoked in Initialize/StateEnter/Exit, so that behaviour can be set up in the inspector without coupling.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class State : MonoBehaviour {
public StateMachine Machine;

public static implicit operator bool (State state) {
 return state != null;
}

public void Initialize (StateMachine machine) {
 Machine = machine;
 OnStateInitialize (machine);
}

protected virtual void OnStateInitialize (StateMachine machine = null) {
}

public void StateEnter () {
 enabled = true;
 OnStateEnter ();
}

protected virtual void OnStateEnter () {
}

public void StateExit () {
 OnStateExit ();
 enabled = false;
}

protected virtual void OnStateExit () {
}

public void OnEnable () {
 if (this != Machine.GetCurrentState) {
  enabled = false;
  Debug.LogWarning ("Do not enable States directly. Use StateMachine.SetState");
 }
}

public void OnDisable () {
 if (this == Machine.GetCurrentState) {
  enabled = true;
  Debug.LogWarning ("Do not disable States directly. Use StateMachine.SetState");
 }
}
}
StateMachine:
  • Remove the requirement for a constructor in our generic SwitchState method. We will still keep the method because it is useful to not have to keep references for target states.
  • Use AddComponent instead of the constructor.
  • In addition to checking the List, call GetComponent just to be sure it doesn’t exist before creating another instance of a state.
  • Remove Update. States update themselves now.
  • Add a getter for currentState. States need to be able to check whether they are indeed the current state since they are now exposed to tampering.
  • Add an oldState variable to keep track of it when switching. Because we are now checking against the currentState OnDisable, currentState needs to be changed before the oldState is disabled. But we don’t want to lose track of which one we need to disable when currentState is set to the new state.
  • Rename SwitchState to SetState. The original name tends to be a stumbling point whenever I use this framework. This is only a matter of preference.
  • The “found” bool can be removed and functionally replaced by return structure.
  • I also added a void setState (State) method so that it can be called from a UnityEvent.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StateMachine : MonoBehaviour {

public List<State> statesList = new List<State> ();
public State StartingState; 
protected State currentState;

public void Start () {
 SetState (StartingState);
}

public State GetCurrentState { get { return currentState; } }

/// <summary>
/// Switch the currentState to a specific State object
/// </summary>
/// <param name="state">
/// The state object to set as the currentState</param>
/// <returns>Whether the state was changed</returns>
public virtual bool SetState (State state) {
 bool success = false;
 if (state && state != currentState) {
  State oldState = currentState;
  currentState = state;
  if (oldState)
   oldState.StateExit ();
  currentState.StateEnter ();
  success = true;
 }
 return success;
}

public void setState (State state) {
 SetState (state);
}

/// <summary>
/// Switch the currentState to a State of a the given type.
/// </summary>
/// <typeparam name="StateType">
/// The type of state to use for the currentState</typeparam>
/// <returns>Whether the state was changed</returns>
public virtual bool SetState<StateType> () where StateType : State {
 bool success = false;
 //if the state can be found in the list of states 
 //already created, switch to the existing version
 foreach (State state in statesList) {
  if (state is StateType) {
   success = SetState (state);
   return success;
  }
 }
 //if the state is not found in the list,
 //see if it is on the gameobject.
 State stateComponent = GetComponent<StateType> ();
 if (stateComponent) {
  stateComponent.Initialize (this);
  statesList.Add (stateComponent);
  success = SetState (stateComponent);
  return success;
 } 
 //if it is not on the gameobject,
 //make a new instance
 State newState = gameObject.AddComponent <StateType> ();
 newState.Initialize (this);
 statesList.Add (newState);
 success = SetState (newState);

 return success;
 }
}