Snake in JavaScript with the Phaser Game Engine
I've been wanting to get back into game programming recently, so I decided to build the classic video game Snake. I chose Snake because it has simple mechanics and I had never programmed it before. As a web developer I know the browser can be a great platform to get up and running quickly, so I decided to make it in JavaScript. I hadn't built a browser game since before the <canvas>
tag became widely supported, but luckily GitHub has a handy list of JavaScript game engines. I chose Phaser because it's at the top of the list, and after looking through the documentation it looked like it would suit my purposes. You can play the finished product here.
Phaser Basics
Phaser makes it easy to get up and started. I read through the making your first game tutorial to get a feel for how games are set up with the framework. There are three main functions that comprise a Phaser game:
-
preload()
This is where you load all of your assets. You can even show a fancy loading bar while this is happening. Phaser has support for images, spritesheets, audio, and other types of assets. If you need to load configuration files or other JSON or XML files you could do that here as well. -
create()
This is the function where you set up the game world. You can add sprites to the game world from the images you loaded earlier, and store them in variables for later use. Sprites have x and y coordinates so that you can move them around. You can also enable one of the physics engines Phaser comes with, so that velocity, gravity, collisions, and more can be handled out of the box. -
update()
This function is called once each frame (by default 60 times per second). This is where the main game logic will go. Here you can respond to user input, animate things on the screen, remove sprites, add sprites, test for collision, etc.
I created these three functions in a snake
object, like so:
var snake = {
preload: function() {
// ...
},
create: function() {
// ...
},
update: function() {
// ...
}
};
These functions are then passed to the Phaser.Game constructor, in order to instantiate a new instance of the game. We can give the id of a div that we have prepared for the game, or the game will be automatically appended to the end of the body.
var game = new Phaser.Game(600, 400, // width, height
Phaser.AUTO,
'game', // id of container
{
preload: snake.preload,
create: snake.create,
update: snake.update
});
Phaser is capable of using either WebGL or just the <canvas>
tag, depending on what is available. The line Phaser.AUTO
means that it will try to use WebGL but will fall back to the HTML5 Canvas. Other options are Phaser.WEBGL
and Phaser.CANVAS
, if you explicitly require one or the other.
There is excellent documentation available for Phaser as well as a bunch of official tutorials for examples of how to use all the features that Phaser has to offer.
Building Snake
The only assets to load for this game are a red circle for the food that the snake tries to eat, and a black circle for the snake's body. I would have preferred to use SVG or to draw the circles in code to minimize dependencies, but for simplicity and since Phaser makes it easy I fired up GIMP and made two 20 pixel diameter circles, one red and one black. In the preload()
function both of these are loaded with:
game.load.image('food', 'assets/food.png');
game.load.image('body', 'assets/body.png');
Then in the create()
or update()
functions, the 'food' and 'body' strings can be used as keys to add sprites to the game world with those corresponding images. Initially there is a snake body of length 1 in a random position with a random initial direction. There is also a food sprite spawned in a random position on the screen. The game waits for the user to hit the spacebar or tap the screen before putting the snake into motion. Sprites can be added to the game world and manipulated like so:
// Adds a sprite at to the game world at position
// (0, 0) with the 'food' image we loaded earlier
var food = game.add.sprite(0, 0, 'food');
// Sets the food sprite's position to (300, 400)
food.position.x = 300;
food.position.y = 400;
// Removes the food sprite from the game world
food.kill();
Checking for input is simple as well. You can either check once every frame in the update()
function, or you can use a listener.
// Create an object to check for arrow key presses,
// usually done in the create() function
var cursors = game.input.keyboard.createCursorKeys();
// Check for arrow key presses, done every frame
// in the update() function
if (cursors.right.isDown) {
// Handle right arrow key down
}
// Alternatively add a listener for right arrow
// key presses, done in the create() function
cursors.right.onDown.add(callback, this);
After the user puts the snake into motion by hitting the spacebar or tapping the screen, they need to grow their snake by eating food. Using the arrow keys or by swiping, the user controls the snake's direction. When the snake overlaps with the food, the food sprite is removed and another is spawned in a new random location. The snake's length is increased by 1 for each food eaten, so the length of the tail gives an indication of the player's score. Hitting space at any point will reset the game. When the game ends because the user ran into a wall or ran into their own tail, the background changes from white to red and the game waits for the spacebar or a tap to restart.
The only complaint I had with Phaser was the lack of touch support. For swiping functionality I had to use the current mouse position and the position where the mouse was clicked to determine if the user had swiped, and in what direction. I used a minimum length of swipe, so the user has to drag for at least so many pixels before a swipe will register. Additionally, the swipe duration has to be for a minimum number of milliseconds. I used 75 pixels and 50 milliseconds, respectively, but these are arbitrarily chosen values and more experimentation might yield better results.
EDIT: I've changed the minimum swipe distance to 30 pixels for recognition of smaller swipes. I've also changed the width and height of the game to be not more than the screen size so that it's not cut off on phones.
Playing the Finished Product
Hit space or tap to start. Use the arrow keys or swipe to direct the snake, and don't hit your tail or run into the walls! My apologies to mobile users, it's difficult to be precise enough with your swipes.
Photo credit to Jonathan Fotino
EDIT 2: I have made a few changes to the game in response to feedback. The player still starts in a random location, but the initial direction is determined by the wall that is farthest away. This gives you much more reaction time to start and prevents you from running into a wall straight away. Second, a score has been added in the top left via a call to game.add.text()
. Lastly, multiple speed levels have been added so that as your score increases, the difficulty will increase as well. The snake moves faster at scores of 5, 15, 25, and 50. Let me know of any more tweaks you'd like to see in the comments below.