How to implement State Machine in Unity

Did you ever heard of term “design pattern”? Do you know what it means? Or do you know examples of it?

Today we will find out about one of the most important design patterns in game development! But beware, this might change you forever! 😱

What design pattern even is?

But first things first! What is the design pattern you might ask? I’m going to prepare a separate post only about that subject, so, for now, I have just a quick explanation.

A design pattern is a high-level idea or proposition on how you can write code. Each design pattern has its pros and cons, so you have to learn about them as much as you can to know where and when to use them in your project.

The most commonly used design pattern in game development is State Machine. This one is the easiest and the most powerful thing that you can implement! But you have to be aware that when you create your first state machine, you will never see programming in the same way! You will start seeing state machines everywhere!

So what exactly State Machine is?

As a name imply, it’s a machine with different states. And with a machine, I mean some script that will hold these states.
The most straightforward example that I can think of would be simple AI what will patrol some area on a map. This AI might have 2 general states like Wait State and Move State.
Another example could be a state machine for a whole game, and that might have states like Menu State, Game State, GameOver State.
We can go even deeper with states inside other states, but let’s don’t do it here! πŸ˜…

State Machine requires you to have 2 very basic scripts:

  • StateMachine – which will hold and update our states.
  • BaseState – which will implement some virtual functions that we will later override.

StateMachine Script

Before I show you how you can implement it, let me explain what we are going to do.
This StateMachine will only store the reference to currently operating state, and as soon as we change state, the old one will be removed or reference will be overridden.
Besides the reference, this script will also contain 2 functions:

  • ChangeState with new state as a parameter.
  • Update function because we will need it for our states, but about that later! 😜

With that said this is an example of such a State Machine πŸ”₯

using UnityEngine;

/// <summary>
/// State Machine implementation.
/// Uses BaseState as base class for storing currently operating state.
/// </summary>
public class StateMachine : MonoBehaviour
{
    // Reference to currently operating state.
    private BaseState currentState;

    /// <summary>
    /// Unity method called each frame
    /// </summary>
    private void Update()
    {
        // If we have reference to state, we should update it!
        if (currentState != null)
        {
            currentState.UpdateState();
        }
    }

    /// <summary>
    /// Method used to change state
    /// </summary>
    /// <param name="newState">New state.</param>
    public void ChangeState(BaseState newState)
    {
        // If we currently have state, we need to destroy it!
        if (currentState != null)
        {
            currentState.DestroyState();
        }

        // Swap reference
        currentState = newState;

        // If we passed reference to new state, we should assign owner of that state and initialize it!
        // If we decided to pass null as new state, nothing will happened.
        if (currentState != null)
        {
            currentState.owner = this;
            currentState.PrepareState();
        }
    }
}

It’s fairly easy even to understand, isn’t it? Great! Now let’s implement a base class for our future states!

BaseState Script

As you already have seen StateMachine script, you can probably guess how BaseState script might look like. πŸ˜…
This script will be even easier to implement!

One important notice here!

This BaseState implementation is not using MonoBehaviour as a base class, which means that we can’t easily use methods like Start, Update or any other Unity methods that MonoBehaviour is providing us with!

So now you know that we can go to implementation. Our state will need reference to our StateMachine to access other references that we might have there. Also, it will have 3 virtual methods:

  • PrepareState – which will be our “Start”.
  • UpdateState – which will be our “Update”.
  • DestroyState – which will be our “OnDestroy”.

Without further ado, BaseState script! ❀️

/// <summary>
/// This is base state script implementation.
/// StateMachine uses these virtual methods to call state when it needs to prepare itself for operating, updating or even being destroyed.
/// </summary>
public abstract class BaseState
{
    // Reference to our state machine.
    public StateMachine owner;

    /// <summary>
    /// Method called to prepare state to operate - same as Unity's Start()
    /// </summary>
    public virtual void PrepareState() { }

    /// <summary>
    /// Method called to update state on every frame - same as Unity's Update()
    /// </summary>
    public virtual void UpdateState() { }

    /// <summary>
    /// Method called to destroy state - same as Unity's OnDestroy() but here it might be important!
    /// </summary>
    public virtual void DestroyState() { }
}

There is almost nothing! I know, but this is the whole purpose of it! We can implement logic in our new scripts which will override BaseState script! πŸ”₯


StateMachine example

To demonstrate how you can use this design pattern I’ve created a little demo of UFO patrolling screen area.
I’ve used this example on my presentation at Unity Las Vegas Meetup which you can see here.

Besides having these 2 scripts mentioned above, I’ve created 2 new states for my state machine and another 2 to simplify my code further.

WaitState script

This state will just wait for time to pass by.

using UnityEngine;

/// <summary>
/// Wait state.
/// Its basically waits for X seconds.
/// </summary>
public class WaitState : BaseState
{
    // Example in StateMachine script - how you can configure state before it will start operating.
    // Minimal waiting time
    public float minWait = 1;

    // Stores how much time left in waiting state.
    private float waitTime;

    public override void PrepareState()
    {
        base.PrepareState();

        // Randomize waiting time.
        waitTime = Random.Range(minWait, 2.5f);
    }

    public override void UpdateState()
    {
        base.UpdateState();

        // After each frame we are subtracting time that passed.
        waitTime -= Time.deltaTime;

        // When wait time is over it's time to change state!
        if (waitTime < 0)
        {
            // Find new place to go!
            owner.ChangeState(new MoveState());
        }
    }
}

MoveState script

This one will tell our movement component in which direction to go.

using UnityEngine;

/// <summary>
/// Move state.
/// Its picking new position to go to and than go back to wait state.
/// </summary>
public class MoveState : BaseState
{
    // Store picked position to go to
    private Vector2 targetPosition;

    public override void PrepareState()
    {
        base.PrepareState();

        // Picking random position
        targetPosition = new Vector2(Random.Range(-8.0f, 8.0f), Random.Range(-5.0f, 5.0f));
    }

    public override void UpdateState()
    {
        base.UpdateState();

        // Calculating direction in which UFO has to go to get to the destination
        var direction = targetPosition - new Vector2(owner.transform.position.x, owner.transform.position.y);
        if (direction.magnitude > 1)
        {
            direction.Normalize();
        }

        // Passing direction to our SimpleMovement component
        owner.Movement.Move(direction);

        // Destination reached!
        if (direction.magnitude < 0.2f)
        {
            // Now wait!
            owner.ChangeState(new WaitState());
        }
    }
}

SimpleMovement component

And this component will move UFO in direction that it received.

using UnityEngine;

/// <summary>
/// Simple Movement component.
/// Uses rigidbody to move around.
/// Its receiving input as a direction where it should go.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class SimpleMovement : MonoBehaviour
{
    // Reference to rigidbody
    private Rigidbody2D rb;

    // Storing input for movement in FixedUpdate
    private Vector2 input;

    // Movement speed
    [SerializeField]
    private float moveSpeed = 10;

    /// <summary>
    /// Initialization of movement.
    /// </summary>
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();

        rb.constraints = RigidbodyConstraints2D.FreezeRotation;
        rb.gravityScale = 0;
    }

    /// <summary>
    /// Physics Update.
    /// Used to move UFO around.
    /// </summary>
    private void FixedUpdate()
    {
        input *= moveSpeed * Time.fixedDeltaTime;
        rb.MovePosition(rb.position + input);
    }

    /// <summary>
    /// Passing direction as input where UFO should fly.
    /// </summary>
    /// <param name="input">Input - direciton.</param>
    public void Move(Vector2 input)
    {
        if (input.magnitude > 1)
        {
            input.Normalize();
        }

        this.input = input;
    }
}

Results!

Awesome! As we have all of that our scripts now it’s time to see the results in action! πŸ”₯

Simple patrolling logic with only 2 states!

If you want to see how this whole thing is working under the hood, I’ve created a public repository that you can access here.

Happy state machining!

PS. Kenney.nl is a great resource for game assets! I highly recommend you to check them!

0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments