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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
State Pattern Using Unity
20 mins
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:
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 tomovementSM
as well as creating the instances of the various movement states. As you create each of the movement states, you pass references to theCharacter
instance using thethis
keyword as well asmovementSM
instance. Finally, you callInitialize
onmovementSM
and pass inStanding
as the starting state. - In the
Update
method, you callHandleInput
andLogicUpdate
on theCurrentState
ofmovementSM
. Similarly, inFixedUpdate
, you callPhysicsUpdate
on theCurrentState
ofmovementSM
. 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
, thehorizontalInput
andverticalInput
variables are set to their default values. - Inside
Exit
, you callResetMoveParams
method ofcharacter
for clean-up while transitioning to another state, as described before. - In
HandleInput
method,horizontalInput
andverticalInput
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 toMove
by passing in thehorizontalInput
andverticalInput
variables multiplied by the respective speeds. Thespeed
variable stores the translation speed whereasrotationSpeed
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 fromGrounded
. You apply the Character’sMovementSpeed
andRotationSpeed
to thespeed
androtationSpeed
. They apply to the normal translation and angular speeds intended for the character entity respectively.Also, the variables for storing input,
crouch
andjump
, are reset to false. - Inside
HandleInput
,crouch
andjump
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 forjump
. - In
LogicUpdate
you check thebool
variablescrouch
andjump
. Ifcrouch
is true,movementSM.CurrentState
changes tocharacter.ducking
. Otherwise, ifjump
is true, it changes tocharacter.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.
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.