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! By Najmm Shora.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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);
    }
}

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

  • 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.