Writing clean and well-organized code is something all game developers aspire to.
We want code to be reusable and easy to maintain. That way we can spend the limited time we have on exciting gameplay features instead of drowning in bugs created by fragile code.
Design patterns help us achieve this goal by standardizing how a game is structured so that we can more easily find our way around projects and avoid common pitfalls that lead to unstable games.
In this article, we will look at using the State Pattern to create a reusable and scalable system for controlling a character in Phaser 3.
What about a State Machine?
A Finite State Machine, often simply called a State Machine, is one advanced application of the State Pattern.
We are not going to create a full-blown State Machine in this article. Instead, we'll show how the State Pattern can be used to create a generic State Machine.
This example will be building on top of the Memory Match tutorial series.
Memory Match is a Mario Party-inspired memory game where you control a character to move around and pick boxes to find all matches.
Get the source code from the tutorial here to more easily follow along.
Creating a State
The character in Memory Match can move up, down, left, and right. Each of these can be considered a movement state.
Here's what the player movement in Memory Match looks like:
|
|
We set the player's velocity and animation depending on which state of movement the player is in.
A separate State class can be created from each direction of movement so that when we are moving left we can be in the MoveLeftState
and when we are moving right we can be in the MoveRightState
.
Create a MoveLeftState.js
file in a states
folder with the following code:
|
|
This State class takes in an instance of the player in the constructor
. Then it has an enter()
method that will be called each time the State is first entered into.
It is important that enter()
is only called once each time the State starts. This lets us omit the second argument to this.player.play()
. Previously, We passed in true
to tell Phaser to ignore if the animation is already playing.
The other 3 movement states will look similar to MoveLeftState
. Here's an example of the MoveDownState
:
|
|
We think you can figure out what MoveRightState
and MoveUpState
should look like. 😎
Idle State
Along with the 4 movement states, we also have an idle state where the player is just standing still.
It should look similar to the movement states but we'll include it here just in case:
|
|
Take a minute to compare these State classes with the Memory Match code we had in the updatePlayer()
method.
You'll see that we've basically encapsulated each movement logic into a separate class.
Now, we need a class that controls the switching of states.
Controlling Player States
Let's make a class called PlayerController
with this set up:
|
|
We have a states
property that will hold instances for each of the 5 states we previously created. Then the currentState
property will hold a reference to the currently active state.
It will be used in the setState(name)
method to ensure that each state's enter()
method is only called once.
Let's start by creating the states:
|
|
We'll be able to reference each state by name like 'idle'
, 'moveLeft'
, 'moveDown'
, etc.
Next, let's implement setState(name)
:
|
|
Notice that we check if the desired state is already this.currentState
and early exit if that is true.
Then if the desired state is different than this.currentState
, we set this.currentState
to the new state and call the enter()
method.
Using PlayerController in the Game Scene
Let's see our use of the State Pattern in action by creating an instance of PlayerController
in the Game
Scene like this:
|
|
We've omitted a bunch of code from the Game
Scene but the main goal is to create a new instance of PlayerController
in the create()
method.
Next, we'll update the logic in updatePlayer()
like this:
|
|
You should see that Memory Match functions exactly the same as before. The difference is only in how we structured and organized the code.
With the logic for each movement state moved into separate classes, you can add more complex logic without cluttering the Game
Scene.
The PlayerController
class has the basic building blocks of a State Machine. It can be said that it is a simplified and non-generic State Machine implementation.
Next Steps
The PlayerController
class in this example is only handling movement states but it can be updated to neatly encapsulate all player input as well.
This would remove keyboard input logic and the updatePlayer()
method from adding clutter to the Game
Scene. We have more details on how to do that in our Memory Match Extras course.
If you are not yet ready for a course then be sure to sign up for our newsletter so you don't miss any future Phaser 3 game development tips and techniques!
Drop your email into the box below.
Don't miss any Phaser 3 game development content
Subscribers get exclusive tips & techniques not found on the blog!
Join our newsletter. It's free. We don't spam. Spamming is for jerks.