Creating a multiplayer game with node.js

Back when I was learning node, I built a simple multiplayer game using WebSockets. Since I had fun coding it, I decided to create this tutorial, explaining the keys to develop something like this.

The game is about tanks. The players go into the game’s website and choose a username and one of the three tank models available.

Play at: rubentd-tanks.herokuapp.com

If there is any other player in the website, they can fight, by shooting cannon balls to each other, until one of them loses all of its energy and explodes.

(If there’s nobody online, you can always test it by opening it in two different tabs, or browser windows)

How to run ‘tanks’ locally

First, you need to install node.js. It works with pretty much any version of node. So the latest should be ok.

Next, you need to install the dependencies of the project, by running npm install on the project’s folder.

For more information about npm and dependencies read this

Once you installed the dependencies, run npm start 
to start the server. It will start running on port 8082. Then go to to http://localhost:8082 in your browser. You should see the start screen of the game.

Code structure

The code has two main parts: a server and a client. These two communicate via WebSockets.

The server

It’s a node.js script which does two main things:

1. Serving the static files needed to play the game online, with express

var express = require('express');  
var app = express();
//Static resources server
app.use(express.static(__dirname + '/www'));
var server = app.listen(8082, function () {  
var port = server.address().port;
console.log('Server running at port %s', port);
});

As you can see, all of the static files are located in the www folder, and it runs on the 8082 port.

2. Creating the WebSockets needed to communicate asynchronously with the clients, using socket.io

var io = require('socket.io')(server);
/* Connection events */
io.on('connection', function(client) {  
console.log('User connected');
    client.on('joinGame', function(tank){
console.log(tank.id + ' joined the game');
var initX = getRandomInt(40, 900);
var initY = getRandomInt(40, 500);
client.emit('addTank', { id: tank.id, type: tank.type, isLocal: true, x: initX, y: initY, hp: TANK_INIT_HP });
client.broadcast.emit('addTank', { id: tank.id, type: tank.type, isLocal: false, x: initX, y: initY, hp: TANK_INIT_HP} );
        game.addTank({ id: tank.id, type: tank.type, hp: TANK_INIT_HP});
});

socket.io enables the communication between the server and the client, based on events. As you can see in the snippet, there is a joinGame event, that gets triggered from the client, when a new user goes to the website. Then the server adds a new Tank to the game, with the game.addTank() event.

The client

Running in each player’s browser, the client script listens to keyboard and mouse events to update the status of the tanks and projectiles. 
The client code is located in the www/js folder.

In this snippet of code we can see the main loop of the client side code, and the sendData() method, used to synchronise the information with the server:

mainLoop: function(){  
if(this.localTank != undefined){
this.sendData(); //send data to server about local tank
}
    if(this.localTank != undefined){
//move local tank
this.localTank.move();
}
},

That main loop is executed every 50 ms.

var INTERVAL = 50;
setInterval(function(){  
g.mainLoop();
}, INTERVAL);

Game main logics

Some of the logic for the game it’s on the client side. 
Specifically on the tanks.js file. 
The rest of the logic is on the server side (index.js)

There is a Game object, a Tank object, and a Ball object, to represent each element of the game.

Both the server and the client have an array of tanks and balls, which are synchronised in the WebSocket events.

To calculate the angle of the cannon and the trajectory of the projectiles, I used some trigonometry.

Every time the mouse moves we need to update the angle of the cannon, mxand my represent the coordinates of the mouse pointer. Then we use the trigonometric inverse tangent function to calculate the angle between the mouse coordinates point, and the center of the tank.

setCannonAngle: function(mx, my){  
var tank = { x: this.x , y: this.y};
var mouse = { x: mx, y: my};
var deltaX = mouse.x - tank.x;
var deltaY = mouse.y - tank.y;
this.cannonAngle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;
this.cannonAngle += 90;
},

To calculate the trajectory of each projectile, we do it on the server side.

Ball.prototype = {
    fly: function(){
//move to trayectory
var speedX = BALL_SPEED * Math.sin(this.alpha);
var speedY = -BALL_SPEED * Math.cos(this.alpha);
this.x += speedX;
this.y += speedY;
}
}

Each instance of the Ball object contains the angle the cannon had when it was shot. And we use the sin and cos functions to get the speed for each one of the vector components.

This way we can update the position of each cannon ball every 50ms and detect collisions with any of the tanks.

Improving the game

“Tanks” is a very simple game, which can be improved in so many different ways. Feel free to fork the repo and work on top of it, to create your own awesome version of the game.

Leave me a comment if you have any doubts or thoughts. Thanks for reading!