State Machine that does not use switch statements

I am personally not a huge fan of switch statements in c# code, and get a little annoyed when I see state machines running with them. Using them is not wrong, I just have my own gripes against it and never implement it that way when I see it in a course. Sometimes it requires a little more thinking, but what I do for simple state machines is to use a delegate pattern instead.

Level 1

Let’s take a simple example. Let’s say we have an enemy that is randomly roaming the world and will chase the player if the player gets within a specific range. It will also stop chasing when the player is no longer in range and start roaming again. Very simple example. I left out the code that doesn’t matter here

public enum EnemyState
{
    Roaming,
    Chasing
}
private EnemyState _currentState = EnemyState.Roaming;

private void Update()
{
    switch (_currentState)
    {
        case EnemyState.Roaming:
            RoamTheLand();
            break;
        case EnemyState.Chasing:
            ChaseThePlayer();
            break;
    }
}

private void RoamTheLand()
{
    // Code to roam the land
    
    // some code to detect the player
    if (PlayerIsInRange())
    {
        // Switch to chasing state
        _currentState = EnemyState.Chasing;
    }
}
private void ChaseThePlayer();
{
    // Code to chase the player

    // some code to detect the player
    if (!PlayerIsInRange())
    {
        // Switch to roaming state
        _currentState = EnemyState.Roaming;
    }
}

That’s it. On every update the enemy will roam the land until the player gets within a certain distance (or whatever the code does) and then switch over to chasing the player until the player is no longer within that condition. Then it switches back to roaming.

I have written this in a way that is realy easy to convert to the delegate pattern I mentioned earlier. Delegates are like addresses to methods. Instead of having an enum that holds the state we’re currently in, we’ll just change the address of the method that is doing the work. We’ll use the Action delegate found in the System namespace

private Action _currentState;

private void Awake()
{
    // Set the starting state. Note that this is a delegate, we're not calling the method here (no parentheses)
    _currentState = RoamingTheLand;
}

private void Update()
{
    // Call the current state delegate
    _currentState?.Invoke();
}

private void RoamTheLand()
{
    // Code to roam the land
    
    // some code to detect the player
    if (PlayerIsInRange())
    {
        // Switch to chasing state - we change the delegate to the other method
        _currentState = ChaseThePlayer;
    }
}
private void ChaseThePlayer();
{
    // Code to chase the player

    // some code to detect the player
    if (!PlayerIsInRange())
    {
        // Switch to roaming state - we change the delegate to the other method
        _currentState = RoamingTheLand;
    }
}

That’s it. A simple state machine without switch statements.


Level 2

Of course you don’t need to use a delegate, you can use a little more complex system as well. Instead of the delegate, we will create a State class that will be able to perform some actions when the state is entered and exited. For smaller state machines this is not critical, but i will create an interface for this state. Note that this is partly based on the state machine used in the Third Personal Combat and Traversal course and if you haven’t checked it out yet, you should definitely try. It’s the Level 3 that won’t be in this post

public interface IState
{
    void Enter();
    void Tick(float deltaTime);
    void Exit();
}

The interface here has an Enter and Exit method that we will call when switching to and from states. This allows for some state setup and cleanup. The Tick method will be executed on every update

Next, our states. We will stick with the above example

public class RoamingState : IState
{
    // We keep a reference to the enemy this state belongs to
    private Enemy _parent;

    // Constructor that takes this state's parent
    public RoamingState(Enemy parent)
    {
        _parent = parent;
    }

    public void Enter()
    {
        // some code to setup the roaming state, like setting the correct animation, etc.
    }

    public void Tick(float deltaTime)
    {
        // Code to roam the land
    
        // some code to detect the player - this method is likely on the enemy but could be in the state, too
        if (_parent.PlayerIsInRange())
        {
            // TODO: Switch to chasing state - we'll get to this in a moment
        }
    }

    public void Exit()
    {
        // some code to cleanup the roaming state
    }
}

public class ChasingState : IState
{
    // We keep a reference to the enemy this state belongs to
    private Enemy _parent;

    // Constructor that takes this state's parent
    public ChasingState(Enemy parent)
    {
        _parent = parent;
    }

    public void Enter()
    {
        // some code to setup the chasing state, like setting the correct animation, etc.
    }

    public void Tick(float deltaTime)
    {
            // Code to chase the player

        // some code to detect the player
        if (!PlayerIsInRange())
        {
            // TODO: Switch to roaming state - we'll get to this in a moment
        }
    }

    public void Exit()
    {
        // some code to cleanup the chasing state
    }
}

I have left two huge TODO’s in the states here which we will get to in a moment. Now that we have the two states, we need to update our enemy to use it

private IState _currentState;

private void SwitchState(IState newState)
{
    // Exit the current state if there is one
    _currentState?.Exit();
    // Set the new state
    _currentState = newState;
    // Enter the new (now current) state
    _currentState?.Enter();
}

private void Awake()
{
    // Set the initial state
    SwitchState(new RoamingState(this));
}

private void Update()
{
    // Call the tick on the state
    _currentState?.Tick(Time.deltaTime);
}

This adds a method we will use to change states. This method will ensure that the Exit() method is called on the current state before switching to the new state and calling the Enter() method on the new state. We also call the Tick method in update, allowing the state to do whatever it does

This now brings us back to those TODOs. We will use the new method to switch to the relevant state (Unchanged code omitted)

public class RoamingState : IState
{
    public void Tick(float deltaTime)
    {
        // Code to roam the land
    
        // some code to detect the player - this method is likely on the enemy but could be in the state, too
        if (_parent.PlayerIsInRange())
        {
            // Switch to chasing state
            _parent.SwitchState(new ChasingState(_parent));
        }
    }
}

public class ChasingState : IState
{
    public void Tick(float deltaTime)
    {
            // Code to chase the player

        // some code to detect the player
        if (!PlayerIsInRange())
        {
            // Switch to roaming state
            _parent.SwitchState(new RoamingState(_parent));
        }
    }
}

Now we have a slightly more complex state machine. But it can get even more complicated


Level 3

For level 3, check out the Third Person Combat and Traversal course. It’s worth it if you want to get into FSMs a little more - This one changed my ‘gamedev’ life…


Level 4

Level 4 will introduce transitions. This gets quite involved. Check out Jason Weimann’s video regarding that here:

There is, of course, a Level 2.5 as well; Using a stack to hold the state. With a stack, we can pop and push states. An example usage would be if the enemy needs to return to whatever state it was in before chasing the player. Let’s say the enemy can roam the land or be fishing if they’re not chasing the player. We may want to return to whichever state the enemy was in before they started chasing the player - roaming or fishing. Instead of keeping track of that state manually we would then just push the ChasingState onto the stack, and when the enemy stops chasing the player, we pop it off again and whatever the previous state was becomes the active state again. It would be a minor change to the Enemy

// we no longer keep a _currentState, we'll use a stack
private Stack<IState> _states = new Stack<IState>();

// we will need to change SwitchState a little. It will still switch states, though
private void SwitchState(IState newState)
{
    // If there's currently a state on the stack, exit it
    if (_states.Count > 0)
    {
        IState oldState = _states.Pop();
        oldState.Exit();
    }
    // put the new state on the stack
    _states.Push(newState);
    // enter the new state
    _states.Peek()?.Enter();
}

// Update also needs a small tweak
private void Update()
{
    _states.Peek()?.Tick(Time.deltaTime);
}

// And then the reason we're using a stack - the pop and push
private void PopState()
{
    // Remove the state from the stack
    if (_states.Count > 0)
    {
        IState oldState = _states.Pop();
        oldState.Exit();
    }
    // if there was a previous state, enter it again
    _states.Peek()?.Enter();
}
private void PushState(IState newState)
{
    // exit the current state, if any
    _states.Peek()?.Exit();
    // push the new state and enter it
    _states.Push(newState);
    _states.Peek()?.Enter();
}

Note that the PopState does not push a new state, but returns to whichever state was previously in the stack, and PushState does not remove the current state, but leaves it in the stack.

The stack-based FSM does require a bit of thinking, though, because more complex states could be pushing states when it should be switching them and then we end up having active states that are not the ones we are supposed to have, etc.

My own implementation of the FSM used in the Third Person Combat and Traversal course has been modified to use this approach, though, allowing me to easily return to whichever ‘locomotion’ state I was in before

Privacy & Terms