Due to the rapid pace of change some links or details may no longer be correct.
Create a game of Snake in this new Phaser 3 Tutorial.
This week we're going to try something a little different. Because Felipe had the week off and I mostly spent it doing quite dull input manager planning I thought I would use this week's Dev Log to bring you a tutorial.
I thought it would be quite fun to create a version of the classic Nokia Snake game. It has minimal asset requirements, so it's easy to show the logic and concepts behind it without getting bogged down in the graphics. Plus it's also ripe for potential expansion.
Because embedding code into newsletters is really tricky I have split the tutorial up into 8 parts and you can view the source for each one on the Phaser Labs. The links below will take you to the online code editor, so just hit the 'Run' button to see it in action.
Part 1 - The Basics
To get started I created a basic State structure and game config. The game will be 640 x 480 in size, running under WebGL and has a nice Nokia green background color. I defined a few functions I knew we'd and preloaded the assets.
Part 2 - Enter the Snake
It's time to create the Snake and get it moving around. I create a new Snake class that will look after its own internal properties, such as its position, alive state, speed, direction and heading.
The Snake itself is managed using a single Layer. Layers in Phaser 3 are like Groups on Phaser 2, but they don't impact the display list. The Layer is created in the Snake constructor and one single Sprite is added to it, referenced as 'this.head'.
I added 4 functions to control changing the snakes' direction (turnLeft, turnRight, etc). These are then called from the State update as the cursor keys are pressed. The functions check if the movement is valid. For example, if the snake is travelling UP and you press the down key, the press should be ignored, because the only valid direction when moving up is left or right. The new direction is stored in a local property this.heading and when it comes time to update the snake position it uses this to do so.
If you run Part 2 you are able to move the snake around using the cursor keys and have it wrap off the sides of the screen. The movement is done with a Layer Action called ShiftPosition. This Action works by taking an array of Game Objects and then iterating through them, changing the position of the next in the sequence to be the position of the one that came before it. This means if you move the head Sprite, then call ShiftPosition, all the rest will follow. Click the screen shot below and move the mouse to see the 'raw' effect running:
Part 3 - Dinner Time
In a game of snake you need something to eat. The food image is already loaded so in Part 4 I created a new Food class to handle it. This class simply extends GameObjects.Image and will gain a few extra properties and methods as the game evolves. For now, the food is created and positioned on the screen. You can still move around but you cannot eat it yet.
Part 4 - Let's Grow
Now we've got food we need to be able to eat it. This is done by adding a 'collideWithFood' method to the Snake class. It takes a Food object as its one single argument and then simply compares the position of its head with the food and if they match it's been eaten.
When the food is eaten Snake.grow is called. This simply uses the Layer.create command to add a new Sprite onto the end of the snake's body, into the position that was previously occupied by its tail end. The snake expands by one segment and also Food.eat is called. In order to prove it works all this does is change the position of the Food to a random x/y coordinate within the game. Even at this rudimentary stage, you can see it all coming together and although you cannot collide with yourself, you've still got the formation of a proper game now.
Part 5 - Speed-Up
It would be nice to make the game get slightly harder as you play it. To achieve this when our snake collides with the food it will increase the speed of the snake slightly. This is done in the collidesWithFood method. It first checks the current speed and if there is still room for it to increase it then checks to see if the total amount of food eaten is a multiple of 5. If so the speed goes up. So for every 5 bits of food eaten, the snake gets quicker. It's a simple change but it will help keep things interesting for the player.
Part 6 - The Trouble with Random
Currently, when food is eaten it randomly repositions itself. The problem with this is that as the snake gets longer it's entirely possible the food will reposition itself directly on top of the snake, or even back where it was before. This is no good and would look strange, so I had to prevent it from happening.
The game is basically a 40 x 30 sized grid. The concept for the new method of repositioning the food is to create a temporary 40 x 30 grid, flagging each cell is un-filled. Then loop through each snake body piece and remove those cells from the grid, as they're occupied, so are not good locations for the new piece of food. At the end of this process, we have a grid with only valid positions left in it which we then pass to Phaser.Math.RND.pick() to select one cell from. That cell becomes the new home of the food.
You can see all the code for this in the 'repositionFood' function. It's not that elegant but it does work and prevents our game from breaking, which is the most important part.
Part 7 - No-Body to Love
The traditional way to die in the game of Snake is to eventually make a mistake that causes you to run into your own body. To complete our game we need to implement this and thankfully it's quite trivial to do.
In Phaser 3 there is an Action called 'GetFirst'. It takes an array of Game Objects along with a comparison object. The Action iterates through the array, checking the properties and values of the comparison object against each Game Object, and if it finds a full match it returns it. In code it looks like this:
In the code above all children of the Body Layer are checked. If one is found that has the 'x' coordinate 100, the 'y' coordinate 200 and an alpha value of 0.5 then it's returned. If nothing matches the conditions then you get null back. Although this example is a little convoluted it has many useful applications, such as in our snake body check. In the Part 7 code, we use it to check to see if any of the body elements have the same x and y coordinate of the head. If they do we know the snake ran into its body. The result of this is captured and the game ends accordingly.
With this final check in place, you can now play a complete game of Snake. Collecting food, growing, wrapping around the screen and dying when colliding with yourself as seen in the animated gif below:
Part 8 - Audio Effects
Phaser 3 doesn't yet have a fully Sound Manager (it's on the schedule, but isn't due for a few months). However, a while ago I did add in the ability to generate dynamic audio at run-time. So let's use that to add some effects into our Snake game.
In part 8 you'll see I created a Web Audio context and then in the Food class I define the dynamic audio settings and call it in the Eat method:
The same is done when you die as well, just with a different effect. Feel free to tweak the values to see what kinds of interesting audio you can create! But it definitely feels a lot more alive when playing it now.
I appreciate it's quite a simple game but I feel like it has shown off a number of Phaser 3 features clearly: Layer Actions, which you can easily create your own to extend in all kinds of directions. An easy to use Phaser Class construct and even Dynamic audio.
Because the Snake is a fully self-contained class there is nothing stopping you from taking the code and using it elsewhere or even making a version where you have to control multiple Snakes at once. You could add in objects to avoid, power-ups, flies or different kinds of food. There are a lot of ways it could be expanded.
I hope you enjoyed this little trip into the works of using Phaser 3 for an actual game. Next issue we'll resume with a normal Dev Log again, but if little tutorials like this prove to be popular then I'll gladly write more in the future.
Phaser 3 Labs
Visit the Phaser 3 Labs to view the new API structure in depth, read the FAQ, previous Developer Logs and contribution guides. You can also join the Phaser 3 Google Group. The group is for anyone who wishes to discuss what the Phaser 3 API will contain.