How to make Slither.io with JavaScript: Part 3 - Players & Bots

7/11/17

This is the third part of our tutorial series on creating Slither.io with JavaScript and Phaser! Take a look at Part 1 if you are just starting with this series, or jump back to Part 2 if you need to.

Look at the example and also look at the source code for this part.

This game clones all the core features of Slither.io, including mouse-following controls, snake collisions, food, snake growth, eyes, and more.

Extending the Snake

Our snake from Part 2 can only move forward. Now we need to work on making snake heads turn left and right. Instead of adding this functionality in the Snake class, we will add it in BotSnake and PlayerSnake, which will extend the Snake. Why do we need to split these apart? Because the player's snake will be controlled by the cursor position or arrow keys, whereas the bots will simply make random turns. This differentiation will be clear when we get into the code.

Bot Snake

Let's first look at the BotSnake class in botSnake.js. We start by creating its function, then inheriting Snake:

BotSnake = function(game, spriteKey, x, y) {
    Snake.call(this, game, spriteKey, x, y);
    this.trend = 1;
}

BotSnake.prototype = Object.create(Snake.prototype);
BotSnake.prototype.constructor = BotSnake;

Using the call method of functions, we inherit Snake. We add a field called trend, which is unique to BotSnake. This inheritance does not include the Snake prototype, so we must then clone its prototype with Object.create() and set that equal to the BotSnake prototype.

Now, BotSnake functions exactly as Snake does, so we need to extend its functionality. Specifically, we want to add to its update method. Let's see how we do this:

BotSnake.prototype.tempUpdate = BotSnake.prototype.update;
BotSnake.prototype.update = function() {
    this.head.body.setZeroRotation();

    //ensure that the bot keeps rotating in one direction for a
    //substantial amount of time before switching directions
    if (Util.randomInt(1,20) == 1) {
        this.trend *= -1;
    }
    this.head.body.rotateRight(this.trend * this.rotationSpeed);
    this.tempUpdate();
}

Firstly, we make a copy of the original update method, called tempUpdate. We do this so we can add on to the real update method, then call tempUpdate at the end. As we saw in Part 2, the Snake update method has important functionality that we do not want to lose.

In our new update method, we are simply turning the snake's head left or right, then switching rotation directions after a certain time.

Player Snake

Now let's look look at the PlayerSnake in playerSnake.js.

PlayerSnake = function(game, spriteKey, x, y) {
    Snake.call(this, game, spriteKey, x, y);
    this.cursors = game.input.keyboard.createCursorKeys();

    //handle the space key so that the player's snake can speed up
    var spaceKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
    var self = this;
    spaceKey.onDown.add(this.spaceKeyDown, this);
    spaceKey.onUp.add(this.spaceKeyUp, this);
    this.addDestroyedCallback(function() {
        spaceKey.onDown.remove(this.spaceKeyDown, this);
        spaceKey.onUp.remove(this.spaceKeyUp, this);
    }, this);
}

PlayerSnake.prototype = Object.create(Snake.prototype);
PlayerSnake.prototype.constructor = PlayerSnake;

//make this snake light up and speed up when the space key is down
PlayerSnake.prototype.spaceKeyDown = function() {
    this.speed = this.fastSpeed;
}
//make the snake slow down when the space key is up again
PlayerSnake.prototype.spaceKeyUp = function() {
    this.speed = this.slowSpeed;
}

As we did with the BotSnake, we inherit the Snake and clone its prototype. Then we make this player snake go faster when the space key is down. Notice how we use the addDestroyedCallback method that we created in Part 2 for snakes.

Now, we create our new update method, as we did with the bot snake:

PlayerSnake.prototype.tempUpdate = PlayerSnake.prototype.update;
PlayerSnake.prototype.update = function() {
    //find the angle that the head needs to rotate
    //through in order to face the mouse
    var mousePosX = this.game.input.activePointer.worldX;
    var mousePosY = this.game.input.activePointer.worldY;
    var headX = this.head.body.x;
    var headY = this.head.body.y;
    var angle = (180*Math.atan2(mousePosX-headX,mousePosY-headY)/Math.PI);
    if (angle > 0) {
        angle = 180-angle;
    }
    else {
        angle = -180-angle;
    }
    var dif = this.head.body.angle - angle;
    this.head.body.setZeroRotation();
    //allow arrow keys to be used
    if (this.cursors.left.isDown) {
        this.head.body.rotateLeft(this.rotationSpeed);
    }
    else if (this.cursors.right.isDown) {
        this.head.body.rotateRight(this.rotationSpeed);
    }
    //decide whether rotating left or right will angle the head towards
    //the mouse faster, if arrow keys are not used
    else if (dif < 0 && dif > -180 || dif > 180) {
        this.head.body.rotateRight(this.rotationSpeed);
    }
    else if (dif > 0 && dif < 180 || dif < -180) {
        this.head.body.rotateLeft(this.rotationSpeed);
    }

    //call the original snake update method
    this.tempUpdate();
}

What I have added to the player snake's update method is the ability to first turn based on arrow keys. If arrow keys are not down, the snake will turn in the direction that will point it towards the cursor sooner. Essentially, it finds the side that has the smaller angle between the snake's current direction and the line formed by the head and the cursor.

game.js

Finally, we need to add these snakes to the game! We should do this in the create method of our Game state:

//create player
var snake = new PlayerSnake(this.game, 'circle', 0, 0);
this.game.camera.follow(snake.head);

//create bots
new BotSnake(this.game, 'circle', -200, 0);
new BotSnake(this.game, 'circle', 200, 0);

And with that our player and our bots are working! In Part 4, we will handle collision between the snakes.

Up Next