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.
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
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
We think you can figure out what
MoveUpState should look like. 😎
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
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
Next, let's implement
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
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
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
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.
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.