Many have now played, or at least heard of the game Slither.io, one of the original "io" games. If you have played the game, you have probably marveled at its simplicity. The feeling of, "Why didn't I think of that?" hit me when I first played it. But it's not just the idea that counts. A lot of tweaking clearly went into the game's physics, light effects, and performance to make it so enjoyable.
This tutorial series is meant to give you a look into how you could create a game like Slither.io. If you follow this series carefully, it will train you to visualize how you can execute an idea for a game. Like I said, it's the execution, not just the idea, that counts. When a great idea for the next "io" game hits you, you will be ready to prototype it right away after reading this series!
About this Series
There will be 7 parts to this series which follow a logical progression towards the finished product. You can see the finished product here. All the other parts of this series have their own example which build up to the final one.
This series uses the Phaser framework. None of this series has been written by looking at Slither.io source code. All of the code in this series is based purely on my interpretation of the game, and the mechanics of my creation probably do not work the same way as in the real game.
This series does not cover the multiplayer aspect of Slither.io. Because of the way I designed my game, it would not be hard to make a simple server implementation for the game with Node.js and socket.io. If you are interested in this aspect when you are finished with the series, do some research on those technologies. You could figure out how to send and retrieve positions of the snakes' heads with a server.
This first part of the series will show you how the project is organized, and what you will need before you can start.
Once again, the final product of this series can be seen here.
All of the source code can be found on Github. The final version is in the folder slither-io. If you want to test the game on your own computer, you will need to be able to serve the source code with a local web server. You will also need to be familiar with the Phaser game framework. If you are confused about any of these topics, see our tutorial Getting Started with Phaser.
In Phaser, the complex topic that we will deal with is P2 physics. The most important thing to note is that Phaser adds a layer of methods that interface with the core P2 physics engine. You can see the docs for Phaser's layer of methods here: Phaser.Physics.P2.
The key difference between Phaser.Physics.P2 and the standalone P2 Physics: Phaser.Physics.P2 uses pixels as dimensions, whereas P2 Physics uses meters. We use standalone P2 Physics a few times in our game, so we must convert from pixels to meters. Luckily, Phaser.Physics.P2 provides methods for this called pxm and pxmi, or "pixels to meters" and "pixels to meters inverted", respectively.
The Phaser.Physics.P2.Body contains a property data which can be seen in the docs here. This property contains the actual P2 physics body which is controlling it, with docs of pure P2 physics bodies here. The Phaser docs discourage our use of this property, but we can use it with caution to avoid Phaser.Physics.P2.Body limitations.
Here is an example of how we could use both of these complex P2 concepts (and we do in fact do this in our project):
var circle = this.game.add.sprite(0, 0, "circle"); this.game.physics.p2.enable(circle, false); //give circle sprite a circular physics body with the proper radius circle.body.setCircle(circle.width*0.5); //scale the circle up circle.scale.setTo(2); //scale up the circle's P2 physics body with the proper radius circle.body.data.shapes.radius = this.game.physics.p2.pxm(circle.width*0.5);
In this code we initialize the sprite circle, then add a circular physics body with the proper radius. We then scale up the circle sprite, so of course we want to scale up the circle's body as well. The Phaser solution would be to replace the old body with a new one. The pure P2 solution is to change the radius of the physics body, which is what we actually do! Notice we use the data property of circle.body. Then we use the pxm method to convert to meters for P2.
Now that we have gotten all the general information out of the way, we can jump straight into our project!
Rather than focusing on graphical aspects of the game, we will focus on core mechanics and features that we want to recreate.
Firstly, we need a functional snake that is made up of segments. These segments must follow the exact path of the snake's head, and they must be spaced apart equally. The snake must be able to increase length, increase scale, and change speeds. It must be able to act as a bot, or the cursor must be able to control it.
A snake's segments should not be able to collide with one another, but rather they should pass over one another. A snake should have a single point on the front of its head that can collide with the segments of other snakes. When this happens, the snake should be destroyed.
The game should contain food that can gravitate in towards the center of a snake's head, where it should then be destroyed. Food should be dropped where a snake has been destroyed.
And for a few aesthetic requirements, snakes should have eyes that follow the cursor (because the game just would not be the same without them!) and snakes should have shadows beneath them that can light up when the snake speeds up.
We have quite a few requirements for a seemingly simple game! This is why we have broken this series into smaller parts.
For general math operations and utilities, I have created a Util object in util.js.
Our Phaser game has a single state called Game that is started in index.html and runs our game. This state can be found in game.js. This state first loads our assets, which are in the asset folder. Then, it initializes a variety of Snake and Food objects. Beyond that, it contains the main update loop and checks if snakes are destroyed.
The Snake function can be found in snake.js. It creates a group of snake section sprites that form a snake. It gives the snake's head a constant speed and keeps an array of positions that the head has passed through. Then, it spaces the other sections out along this path.
The core Snake class is inherited and extended by BotSnake and PlayerSnake. The BotSnake randomly turns the snake's head left or right. The PlayerSnake turns the snake's head based on arrow keys or the position of the mouse.
The Snake class initializes an EyePair. The EyePair class creates two Eye objects and locks them in the proper positions on the head. I separated Eye and EyePair because it is then easier to give a snake one eye or three eyes by removing the EyePair controller.
The Snake also initializes a Shadow object. The Shadow class holds an array of shadow sprites and has an update method that positions those sprites below the section sprites of the snake. Whenever a snake section is added, a shadow sprite is also added through the Shadow instance.
Finally, there is a Food class which contains a food sprite. It checks if the food sprite's body collides with a snake's head. When it does, it makes the food gravitate in towards the center of the snake's head using a constraint, where it is then destroyed.
Hopefully the organization of our project is now clear to you. In the next part of this series, we will go through the design of the Snake class.
Until next time,
Making Slither.io: Part 2
Continue making Slither.io by creating your first snake
Making Slither.io: Part 3
Extend the snakes in Slither.io to be players or bots
Making Slither.io: Part 4
Learn how to handle snake collisions in Slither.io