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.
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
We will want to take this logic and wrap it into a self-contained class that can be passed to our
PlayerInputState.js file in a folder named
states in the same directory that you created
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
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
init() because we will need early in the
Then we create a new
PlayerInputState on line 20 that is given to the newly created
Paddle on lines 27 - 29.
update() method is changed to replace
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
Creating a Basic AI State
The AI logic currently looks lke this:
Just like with
PlayerInputState we can wrap this in a separate class.
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
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.
update(paddle) has the same logic as the
This looks much cleaner, doesn't it? 🧐
Using BasicAIState in the Game Scene
BasicAIState will be similar to what we did for
This should be pretty self-explanatory based on what we've learned from adding
Give this a try and the right paddle should move as it did before!
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.