Home · Unity Tutorials

State Pattern using Unity

Learn all about the Finite State Machine design pattern in Unity. Then implement it to control the movement of your own character!

4.9/5 24 Ratings

Version

  • C# 7.3, Unity 2019.2, Unity

While programming your in-game entities, there are cases where they need to behave differently under different conditions, which suggests an idea of a state.

But if you choose to employ a brute-force method, the code will quickly become an untangled mess with a lot of if-else conditions, nested and otherwise.

To solve this problem elegantly, you can utilize the State design pattern and that’s what this tutorial is all about!

In this tutorial, you’ll learn:

  • The fundamentals of the State Pattern in Unity.
  • What a finite state machine is and how to use it.
  • How to implement these ideas to control a character’s movement.
Note: This tutorial is meant for experienced readers and assumes you already have a working knowledge of Unity and intermediate knowledge of C#. In addition, this tutorial assumes you’re using Unity 2019.2 and C# 7.

Getting Started

Download the project materials by clicking the Download Materials button at the top or bottom of this tutorial. Extract the zip file and open the starter project in Unity.

The project includes several folders to help you get started. Within Assets/RW you’ll find: Animations, Materials, Models, Prefabs, Resources, Scenes, Scripts, and Sounds, each named according to the kind of assets they contain.

You’ll only work with Scenes and Scripts to complete this tutorial.

Go to RW/Scenes and open Main. In the Game view, you’ll see a hooded character in a medieval castle grounds.

Game view with character

Click Play and notice the Camera moves to bring the animated Character in frame. Right now, there are no interactions inside this little game. But that’s exactly what you’ll work on in this tutorial.

Playing the starter scene

Exploring the Character

In the Hierarchy, select Character. Next, take a look at the Inspector. You’ll see a similarly named Component that contains the logic which controls the Character.

Inspector pane with selected character

Open Character.cs from RW/Scripts.

There’s a lot going on there but you don’t need to worry about most of it. For now, take note of the following methods.

  • Move: It moves the character by accepting the float values speed as the translation speed and rotationSpeed as the angular speed.
  • ResetMoveParams: This method resets the parameters used for movement animation and the angular velocity of the character. This is simply used for clean-up.
  • SetAnimationBool: This sets a Bool type animation parameter param to value.
  • CheckCollisionOverlap: It accepts a point of type Vector3 and returns a bool indicating if there are colliders around the point within a defined radius.
  • TriggerAnimation: It triggers the input animation parameter param.
  • ApplyImpulse: This applies an impulse force on the Character equal to the Vector3 input parameter force.

You’ll use these methods in the following sections. For the purposes of this tutorial, you don’t need to worry about their inner workings.

Understanding State Machines

State machines are a concept in which a container stores the status of something at any given time. Then, given an input, it can provide an output based on the current state, transitioning to a new state in the process. State machines can be represented by a State Diagram. Preparing a state diagram will help you think about all the possible states of your system, and how you transition between them.

Finite State Machines

Finite state machines or FSMs are one of the four major families of automaton. Automatons are abstract models of simple machines. They’re studied under Automata Theory, a theoretical branch of computer science.

Simply put:

  • An FSM consists of a finite number of states. At a given time only one such state is active.
  • Each state defines which state it’ll transition to as an output, based on the sequence of inputs it receives.
  • The output state becomes the new active state. In other words, a state transition occurs.

Finite State Machine Diagram

To understand this better, consider a character in a platformer game who’s standing on the ground. The character is in the Standing state. This would be its active state until you press a button to make the character jump.

The Standing state identifies the button press as a valid input and transitions to the Jumping state as an output.

Assume there are a fixed number of such movement states and the character can only be in one such state at a time. This is an example of an FSM.

Hierarchical State Machines

Consider a platformer game using FSM, in which several states share the same physics logic. For example, you can move and jump in both the Crouching and Standing states. In this case, several inputs result in the same behavior and output for two different states.

In a situation like this, it makes sense to have some kind of delegation to some other state for the shared or common behavior. Luckily, you can achieve this via hierarchical state machines.

In a hierarchical FSM, there are sub-states which delegate the unhandled inputs to their super-state. This in-turn elegantly reduces the size and complexity of the FSM while maintaining the logic.

The State Pattern

In their book, Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides — the Gang of Four, define the intent for the State pattern as follows:

“Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.”

To understand this better, consider the following example:

  • A script that accepts input for movement logic is attached to an in-game entity.
  • This class maintains a current state variable which simply references an instance of a state class.
  • The input delegates to this current state which handles it and produces the behavior defined within itself. It also handles the required state transitions.

Thus, due to the different states referenced by the current state variable over time, the same script class will seem to behave differently. This is the essence of the State pattern.

In this project, the aforementioned Character class is the one that will behave differently due to the various states. But, you want good behavior only! :]

State Pattern

In general, there are three key points for every state class which help define the overall behavior of the state:

  • Entry: This is where you enter the state and do things you only need to do once when you first enter the state.
  • Exit: Similar to the Entry, this is where you do any clean-ups you only need to do once before the state changes.
  • Update Loop: This is where the core update logic, which executes in every frame, exists. You can divide this into multiple parts such as a loop for physics updates or a loop for handling input.

State diagram

Defining State and State Machine

Navigate to RW/Scripts and open StateMachine.cs.

The State Machine will provide an abstraction for, you guessed it, a state machine. Notice the CurrentState property inside this class. This will store the reference to the current active state of the state machine.

Now to define the idea of a state, go to RW/Scripts and open State.cs in your IDE.

State is an abstract class you’ll use as a blueprint to derive from, for all the state classes in this project. Some code has already been provided for you.

DisplayOnUI only displays the name of the current state on the screen UI. You don’t need to worry about its inner workings but you do need to understand it takes an enumerator of type UIManager.Alignment as an input parameter which can either be Left or Right. This results in the name of the state displaying on either the bottom left or bottom right of the screen respectively.

Additionally, there are two protected variables, character and stateMachine. The character references an instance of the Character class while stateMachine references an instance of the State Machine that associates with the state.

The constructor links the character and stateMachine when a state instance is created.

Multiple instances of Character in the scene can each have their own set of states and state machines.

Now, add the following methods inside of State.cs and save the file:

public virtual void Enter()
{
    DisplayOnUI(UIManager.Alignment.Left);
}

public virtual void HandleInput()
{

}

public virtual void LogicUpdate()
{

}

public virtual void PhysicsUpdate()
{

}

public virtual void Exit()
{

}

These virtual methods define the key points of a state discussed earlier. When the State Machine transitions between states, you call Exit on the previous state and Enter for the new active state.

The HandleInput, LogicUpdate and PhysicsUpdate together define the update loop. HandleInput handles the input. LogicUpdate handles the core logic and the PhyiscsUpdate handles the physics logic and calculations.

Now open StateMachine.cs once again, add the following methods and save the file:

public void Initialize(State startingState)
{
    CurrentState = startingState;
    startingState.Enter();
}

public void ChangeState(State newState)
{
    CurrentState.Exit();

    CurrentState = newState;
    newState.Enter();
}

Initialize configures the state machine by setting CurrentState to the startingState variable and calling Enter on it. This initializes the state machine by setting the active state for the first time.

ChangeState handles transitions between States. It calls Exit on the old CurrentState before updating its reference to newState. Finally it calls Enter on the newState.

With that, you’ve defined a State and a State Machine.

Creating the Movement States

Take a look at the following state diagram which shows the various movement states of an in-game player entity. In this section, you’ll implement the state pattern for the shown movement FSM:

Character movement state machine

Take note of the various movement states, namely Standing, Ducking, and Jumping, and how the input can cause a state transition. This is a hierarchical FSM where Grounded is the super-state for the Ducking and Standing sub-states.

Go back to Unity and navigate to RW/Scripts/States. You’ll find several C# files with names ending in State.

Each of these files defines one single class, all of which inherit from State. As such, these classes define the states you’ll use in this project.

Now open the Character.cs from RW/Scripts.

Navigate to the top of the #region Variables of the file and add the following code:

public StateMachine movementSM;
public StandingState standing;
public DuckingState ducking;
public JumpingState jumping;

This movementSM references the state machine that handles movement logic for the Character instance. You also added references for three states you’ll implement for each type of movement.

Navigate to #region MonoBehaviour Callbacks in the same file. Add the following MonoBehaviour methods and then save:

private void Start()
{
    movementSM = new StateMachine();

    standing = new StandingState(this, movementSM);
    ducking = new DuckingState(this, movementSM);
    jumping = new JumpingState(this, movementSM);

    movementSM.Initialize(standing);
}

private void Update()
{
    movementSM.CurrentState.HandleInput();

    movementSM.CurrentState.LogicUpdate();
}

private void FixedUpdate()
{
    movementSM.CurrentState.PhysicsUpdate();
}
  • In Start, the code creates an instance of State Machine and assigns it to movementSM as well as creating the instances of the various movement states. As you create each of the movement states, you pass references to the Character instance using the this keyword as well as movementSM instance. Finally, you call Initialize on movementSM and pass in Standing as the starting state.
  • In the Update method, you call HandleInput and LogicUpdate on the CurrentState of movementSM. Similarly, in FixedUpdate, you call PhysicsUpdate on the CurrentState of movementSM. This essentially delegates the tasks to the active state, which is what the State pattern is all about.

Now you need to define the behavior inside each of the movement states. Brace yourself for lots of code!

Two Feet Firmly on the Ground

Go back to RW/Scripts/States in the Project window.

Open Grounded.cs and note that this class has a constructor that matches that of the State. This makes sense since this class is inhering from it. You’ll see the same in all the other state classes as well.

Add the following code:

public override void Enter()
{
    base.Enter();
    horizontalInput = verticalInput = 0.0f;
}

public override void Exit()
{
    base.Exit();
    character.ResetMoveParams();
}

public override void HandleInput()
{
    base.HandleInput();
    verticalInput = Input.GetAxis("Vertical");
    horizontalInput = Input.GetAxis("Horizontal");
}

public override void PhysicsUpdate()
{
    base.PhysicsUpdate();
    character.Move(verticalInput * speed, horizontalInput * rotationSpeed);
}

Here’s what is happening:

  • You override some of the virtual methods defined in the parent class. In order to keep any functionality that could exist in the parent you call the base method of the same name from within each overridden method. This is an important pattern you’ll continue to use.
  • In the next line of Enter, the horizontalInput and verticalInput variables are set to their default values.
  • Inside Exit, you call ResetMoveParams method of character for clean-up while transitioning to another state, as described before.
  • In HandleInput method, horizontalInput and verticalInput cache the horizontal and vertical input axis values. As such, you can use the W, A, S and D keyboard keys to move the character.
  • In the PhysicsUpdate, you make a call to Move by passing in the horizontalInput and verticalInput variables multiplied by the respective speeds. The speed variable stores the translation speed whereas rotationSpeed stores the angular speed.

Now open Standing.cs and note that it inherits from Grounded. This is because Standing is a sub-state for Grounded, as discussed previously. There are various ways of implementing this relationship but in this tutorial you’ll use inheritance.

Add the following override methods and save the script:

public override void Enter()
{
    base.Enter();
    speed = character.MovementSpeed;
    rotationSpeed = character.RotationSpeed;
    crouch = false;
    jump = false;
}

public override void HandleInput()
{
    base.HandleInput();
    crouch = Input.GetButtonDown("Fire3");
    jump = Input.GetButtonDown("Jump");
}

public override void LogicUpdate()
{
    base.LogicUpdate();
    if (crouch)
    {
        stateMachine.ChangeState(character.ducking);
    }
    else if (jump)
    {
        stateMachine.ChangeState(character.jumping);
    }
}
  • In Enter you configure some variables inherited from Grounded. You apply the Character’s MovementSpeed and RotationSpeed to the speed and rotationSpeed. They apply to the normal translation and angular speeds intended for the character entity respectively.

    Also, the variables for storing input, crouch and jump, are reset to false.

  • Inside HandleInput, crouch and jump store the user input for crouching and jumping respectively. In the Main scene, if the user presses the Shift key, crouch is set to true. Similarly, the user can use the Space key for jump.
  • In LogicUpdate you check the bool variables crouch and jump. If crouch is true, movementSM.CurrentState changes to character.ducking. Otherwise, if jump is true, it changes to character.jumping.

Save and build the project and click Play. You should be able to move around using the W, A, S and D keys. If you try pressing the Shift or Space keys right now, unexpected behavior will occur since the corresponding states aren’t implemented yet.

Play the standing state

Try moving under the bench shaped platforms. You’ll notice that isn’t possible due to the Character’s collider height. To let the Character do this, you’ll add the ducking behavior.

Sneaking Under the Table

Open Ducking.cs. Notice Ducking also inherits from the Grounded class for the same reasons Standing does. Add the following override methods and save the script:

public override void Enter()
{
    base.Enter();
    character.SetAnimationBool(character.crouchParam, true);
    speed = character.CrouchSpeed;
    rotationSpeed = character.CrouchRotationSpeed;
    character.ColliderSize = character.CrouchColliderHeight;
    belowCeiling = false;
}

public override void Exit()
{
    base.Exit();
    character.SetAnimationBool(character.crouchParam, false);
    character.ColliderSize = character.NormalColliderHeight;
}

public override void HandleInput()
{
    base.HandleInput();
    crouchHeld = Input.GetButton("Fire3");
}

public override void LogicUpdate()
{
    base.LogicUpdate();
    if (!(crouchHeld || belowCeiling))
    {
        stateMachine.ChangeState(character.standing);
    }
}

public override void PhysicsUpdate()
{
    base.PhysicsUpdate();
    belowCeiling = character.CheckCollisionOverlap(character.transform.position +
        Vector3.up * character.NormalColliderHeight);
}
  • Inside Enter, the parameter to trigger the crouch animation is set to true which enables the crouch animation. The character.CrouchSpeed and character.CrouchRotationSpeed properties set the speed and rotation which return the intended translation and angular speed of the character while crouching.
    Next, character.CrouchColliderHeight sets the collider size of the character, which returns the intended collider height while crouching. Finally, belowCeiling is reset to false.
  • Inside Exit, the parameter for crouch animation is set to false. This would disable the crouch animation. Afterwards the collider height is set to normal intended height returned by character.NormalColliderHeight.
  • Inside HandleInput, crouchHeld is set to accept the user input. In the Main scene, holding down Shift will set crouchHeld to true.
  • Inside PhysicsUpdate, the belowCeiling variable is set by passing in the Vector3 point near the Character GameObject’s head to CheckCollisionOverlap by. If there is a collision around that point, it means the Character is below a ceiling of some sort.
  • Inside LogicUpdate, you check to see if either crouchHeld or belowCeiling is true. If neither of them is true, movementSM.CurrentState changes to character.standing.

Build and click Play. Now you should be able to move around. If you press Shift, the Character will crouch and you can move around while crouching.

You should also be able to go under the platforms. If you release Shift while underneath the platforms, the Character will continue crouching until you leave its cover.

Play the ducking state

Up, Up and Away!

Open Jumping.cs. You’ll see a method named Jump. Don’t worry about its specifics but understand it’s used to make the Character jump with physics, animation and magic! :]

Now add the usual override methods and save the script

public override void Enter()
{
    base.Enter();
    SoundManager.Instance.PlaySound(SoundManager.Instance.jumpSounds);
    grounded = false;
    Jump();
}

public override void LogicUpdate()
{
    base.LogicUpdate();
    if (grounded)
    {
        character.TriggerAnimation(landParam);
        SoundManager.Instance.PlaySound(SoundManager.Instance.landing);
        stateMachine.ChangeState(character.standing);
    }
}

public override void PhysicsUpdate()
{
    base.PhysicsUpdate();
    grounded = character.CheckCollisionOverlap(character.transform.position);
}
  • Inside Enter, the SoundManager singleton plays the jump sound. Afterwards, grounded is reset to its default value. Finally Jump gets called.
  • Inside PhysicsUpdate, the Vector3 point near the Character’s feet is sent to CheckCollisionOverlap, which means grounded will be set to true if the Character is on ground.
  • In LogicUpdate when grounded is true, you call TriggerAnimation for the landing animation, a landing audio plays and movementSM.CurrentState changes to character.standing.

Finally, with this, you’ve fully implemented the movement FSM using the State Pattern. Build and play. Use Space to make the character jump.

Play the jumping state
Now you can say, “my code is in a state!” with a smile on your face! :]

Where to Go From Here?

You can use the Download Materials button at the top and bottom of this tutorial to download both the starter and final projects.

While state machines are useful they come with their own limitations. Concurrent State Machines and Pushdown Automata tackle some of those limitations. You can read more about them in the book Game Programming Patterns by R.Nystrom.

You can also delve deeper by exploring behavioral trees for making advanced in-game entities.

I hope you found this tutorial useful and enjoyed learning the topic. Please feel free to join the forum below for any questions or comments.

Average Rating

4.9/5

Add a rating for this content

24 ratings

More like this

Contributors

Comments