Have you made a few games with Phaser in JavaScript?
And now you want to learn about code structure and best practices?
You've probably come across design patterns in researching ways to improve your code.
But which design patterns and for what use cases? 🤔
The standard examples are okay but… who is making a calculator?
We're making games. How do these patterns apply to games?
In this article, we will look at applying the State Pattern to allow switching AI or player control for paddles in a Pong game.
Also, we're glad to see that you want to keep improving. 👏
Starting with the Pong Template
We will be building on top of the Pong Template found here.
This template corresponds to a YouTube series for beginners so it is a great place to see how the code can be improved!
Pong has a left paddle and a right paddle.
In the template, the left paddle is always controlled by the player and, the right paddle is always controlled by the AI.
We will change it so that each paddle can be player controlled or AI-controlled.
The concepts in this article will allow us to make a local 2 player game, a networked 2 player game, or a game played entirely between AI's!
What is the State Pattern?
The State Pattern is a way of structuring code so that a behavior can be dynamically chosen at runtime.
The Pong Template hard codes a behavior for moving the left paddle (player control) and the right paddle (AI control).
There is no easy way to let players pick which paddle they would prefer to use.
To paraphrase Henry Ford, “you can pick any paddle you want as long as it is the left one”. 😅
Using the State Pattern is not the only way to allow the player or AI to control a paddle.
Another way is to have both implementations in a Paddle class and a boolean flag to switch between each.
But then what happens if your AI is more like Pac-Man and has several different states depending on the situation? 🤔
Your paddle class will quickly get large, messy, and confusing!
With the State Pattern, you can take the player input logic or the AI logic and move it outside of the Paddle class.
Then the Paddle won't need to know the specifics of how it should move. Instead, it will be given a behavior to handle moving logic.
A closely related sibling of the State Pattern is the Strategy Pattern. You'd be hardpressed to find a good explanation of the differences between the two because they are very similar in practice.
Note that this is different than a Finite State Machine (FSM). But it is a building block to an FSM.
Creating a Paddle Class
The template currently leaves the control logic of the paddles to the Game Scene. Let's change that.
Start by creating a Paddle
class so that we can more easily reason about changing the state of a paddle.
Create a Paddle.js
file in the same directory as the Game.js
Scene class.
|
|
Notice that we are subclassing Phaser.GameObjects.Rectangle
to continue using rectangles for rendering the paddle.
We add a physics body to the paddle on line 18 in the constructor instead of in the Scene.
Then we add a controlState
property to hold a reference to the state that will control how the paddle is moved.
A setter is created on lines 23 - 26 so that it can be set from outside the class at runtime.
It is then used in the update()
method on line 35.
With this structure, we can pass in different state implementations to change how the paddle should be controlled.
Wrapping Player Input State
The player input logic is currently being handled in the processPlayerInput()
method on the Game Scene.
|
|
This method is then called by the update()
method.
We will want to take this logic and wrap it into a self-contained class that can be passed to our Paddle
class.
Create a PlayerInputState.js
file in a folder named states
in the same directory that you created Paddle.js
.
|
|
We know that we'll need access to the CursorKeys
so we make that a constructor argument on line 8.
Then we have an update(paddle)
method on line 13 that takes a Paddle
instance. This corresponds to the this.controlState.update(this)
call in the Paddle
class above.
Notice that the code in update(paddle)
is almost identical to what was in processPlayerInput()
.
Using PlayerInputState in Game Scene
Code that is not relevant to using states will be omitted but we will include enough context for you to compare against the template code.
|
|
We moved the setting of this.cursors
to init()
because we will need early in the create()
method.
Then we create a new PlayerInputState
on line 20 that is given to the newly created Paddle
on lines 27 - 29.
The update()
method is changed to replace this.processPlayerInput()
with this.paddleLeft.update()
.
Give this a try and you'll see that you can still control the left paddle with the arrow keys even though the old processPlayerInput()
code is not being used.
The State Pattern structure is working! 🎉
Next, we need to update the updateAI()
logic.
Creating a Basic AI State
The AI logic currently looks lke this:
|
|
Just like with PlayerInputState
we can wrap this in a separate class.
Create a BasicAIState.js
file in the same folder as PlayerInputState.js
with the following code:
|
|
The AI logic uses the ball
to determine how to move so we pass that into the constructor as we did with cursors
for the PlayerInputState
.
We also create a class property for paddleRightVelocity
as we had in the Game Scene. Since it was only used for AI movement we no longer need it in the Game Scene.
Then update(paddle)
has the same logic as the updateAI()
method.
This looks much cleaner, doesn't it? 🧐
Using BasicAIState in the Game Scene
Using the BasicAIState
will be similar to what we did for PlayerInputState
earlier.
|
|
This should be pretty self-explanatory based on what we've learned from adding PlayerInputState
earlier.
Give this a try and the right paddle should move as it did before!
Next Steps
And that's the State Pattern! You can try using the BasicAIState
for both paddles to let the game play itself.
To let the player pick which paddle to use, you can have a selection screen that passes the information to the Game Scene as we did here and then set the appropriate states for each paddle.
From here you can use the same concepts for creating a multiplayer game with local or networked players. 😎
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.