Built a real-time multiplayer game with HTML5 in less than 30 minutes

This tutorial is made in combination with the Couchfriends controller API and uses the Couchfriends services for the real-time network traffic between your game and players. It's build in HTML5 and plain javascript. No tools like Phaser or Pixi.js are being used. I assume you're familiar with the following:

  • The idea of Couchfriends. Check and play one of the games to get an idea;
  • A webbrowser on your desktop/pc. E.g. Firefox or Chrome.
  • A decent smart phone or tablet with a browser (Chrome, Firefox or newer versions of Safari works best).
  • Basic knowledge about web programming in Javascript and HTML5.

What this tutorial will not.

  • Setup and explain server-side websockets;
  • Go into deep game development;
  • Make an amazing looking game, but don't worry... you can make it playable :)

The source of all steps are available at https://github.com/Couchfriends/game-tutorial. You can "play" the game at http://cdn.couchfriends.com/games/game-tutorial/4/

Step 1: Setup the basic structure

Demo: http://cdn.couchfriends.com/games/game-tutorial/1/.

In this step we create a basic index.html and the core js file to init our <canvas> and make sure it is always fullscreen.

Create a file index.html and paste the following code in it.

<!DOCTYPE html>
<html>
<head lang="en">
   <meta charset="UTF-8">
   <title>Race game</title>
   <style>
       * { margin: 0; padding: 0; }
       html, body, canvas { width: 100%; height: 100%; }
       body { overflow: hidden; }
       canvas { position: absolute; left: 0; top: 0; }
   </style>
</head>
<body>
  <canvas id="game"></canvas>
  <script src="js/game.js"></script>
</body>
</html>

Above code is adding a canvas to our page and make it 100% width and 100% height then it loads the js/game.js that we will create next.

Next create a file js/game.js and paste the following code in it.

var game = {
       width: window.innerWidth,
       height: window.innerWidth
   },
   elCanvas,
   ctx;
window.onload = init;
window.onresize = resizeGame;

function init() {
   elCanvas = document.getElementById('game');
   ctx = elCanvas.getContext('2d');
   resizeGame();

   // Draw the player
   ctx.beginPath();
   ctx.rect(game.width/2, game.height-60, 50, 50);
   ctx.fillStyle = "red";
   ctx.fill();
}

/**
* Resize the game to fullscreen
*/
function resizeGame() {
   game.width = window.innerWidth;
   game.height = window.innerHeight;
   elCanvas.width = game.width;
   elCanvas.height = game.height;
}

In the code above we make sure the canvas is always the same width and height as the window viewport. We also draw a red square on the center/bottom of the screen. This will be our player.

Step 2: Create small obstacles and animate them

Demo: http://cdn.couchfriends.com/games/game-tutorial/2/.

In this step we will add a few obstacles that will move from top to bottom at a fixed speed. This will make the player feel it is moving up. First edit the index.html and change the following line

<script src="js/game.js"></script>

to

<script src="js/sprites.js"></script>
<script src="js/game.js"></script>

Then create a new javascript file in js/sprites.js. This file will contain all our game objects including the players. Paste the following code and save it.

/**
* Basic class for all game sprites
* @constructor
*/
var Sprite = function() {

   this.name = '';
   this.visible = true;
   this.color = '#000000';
   this.size = {
       width: 10,
       height: 10
   };
   this.position = {
       x: 0,
       y: 0
   }
};

Sprite.prototype = {

   update: function(time) {
       if (this.visible == false) {
           return false;
       }
       return true;
   },

   draw: function() {

       if (this.visible == true) {
           ctx.beginPath();
           ctx.rect(this.position.x, this.position.y, this.size.width, this.size.width);
           ctx.fillStyle = this.color;
           ctx.fill();
       }
   },

   add: function() {
       GameObjects.push(this);
   },

   remove: function() {

       var indexOf = GameObjects.indexOf(this);
       GameObjects.splice(indexOf, 1);

   }

};

/**
* Obstacle that can collide with players
* @constructor
*/
Obstacle = function () {

   Sprite.call(this);
   this.name = 'obstacle';
   this.color = '#ff0000';
   this.size = {
       width: 50,
       height: 50
   }
};

Obstacle.prototype = Object.create(Sprite.prototype);
Obstacle.prototype.constructor = Obstacle;

Obstacle.prototype.update = function(time) {

   if (!Sprite.prototype.update.call(this, time)) {
       return false;
   }

   this.position.y += 1;

   if (this.position.y > (game.height+this.size.height)) {
       this.position.x = Math.random() * game.width;
       this.position.y = -(this.size.height);
   }

};

This file has one base class Sprite and all game elements (Obstacles, Players, Pickups, etc) will extend from this class. This makes it really easy to create new objects without having too much of duplicated code. The most important part of the file for now it the update() function. update() will be executed 60 times a second (60 frames per second). This is our game loop.

The update() function of Obstacle will move the current object down (y+1) while the obstacle is visible. If the obstacle reaches the bottom of the screen it then will be placed in a random position along the x-as and back on top again.

You might noticed the draw() function inside our base class. This is similar code that we used before to draw our player. Yes, you guessed it right: we will move the player from game.js to sprite.js.

Add this code on top of your game.js file.

window.requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
   window.setTimeout(callback, 1000 / 60);
};

Then look for your init() function and replace it with the following two functions.

function render(time) {

   requestAnimFrame(render);
   for (var i = 0; i < GameObjects.length; i++) {
       GameObjects[i].update(time);
       GameObjects[i].draw();
   }
}

function init() {
   elCanvas = document.getElementById('game');
   ctx = elCanvas.getContext('2d');
   resizeGame();
   requestAnimFrame(render);
}

This function will continuously loop through all available objects in the game and update/draw them. Since we removed the player there is nothing to animate. Let's create a simple obstacle. Add the following code in your init() function just before we call the requestAnimFrame() function.

var obstacle = new Obstacle();
obstacle.add();

You should now see a red rectangle moving from top to bottom and reset once it is on the bottom of the screen. If you wait long enough you will probably see your screen full of red colors. This is because the canvas is not 'cleaned' after everything has been drawn. Add the following line on top of the render() function:

ctx.clearRect(0, 0, game.width, game.height);

Step 3: Player, score and collisions

Demo: http://cdn.couchfriends.com/games/game-tutorial/3/.

Ready to finishing the mechanics? In this step we will add a player, make it intractable and make sure the player get and lose score on certain moments of the game.

First, let's add the game to /js/sprites.js. Paste the following code at the bottom of the file.

Player = function () {

   Sprite.call(this);
   this.name = 'player';
   this.color = '#0000ff';
   this.size = {
       width: 50,
       height: 50
   };
   this.speed = {
       x: 0,
       y: 0
   };
   this.position.x = game.width / 2;
   this.position.y = game.height - 60;

};

Player.prototype = Object.create(Sprite.prototype);
Player.prototype.constructor = Player;

Player.prototype.update = function(time) {

   if (!Sprite.prototype.update.call(this, time)) {
       return false;
   }

   if (this.speed.x < -10) {
       this.speed.x = -10;
   }
   else if (this.speed.x > 10) {
       this.speed.x = 10;
   }

   this.position.x += this.speed.x;
   if (this.position.x < 0) {
       this.position.x = 0;
   }
   else if (this.position.x > (game.width-this.size.width)) {
       this.position.x = (game.width-this.size.width);
   }

   this.speed.x *= .95;

   game.score++;

};

The Player extends from the Sprite class and will move the player along the x-as while it has a certain speed. Speed decreases over time and as long the player is visible the score will be added to the games total. Let's head back to the js/game.js and add the score and player movement. First, add an additional variable score in the global game object and add player too. This will be our playable object.

var game = {
       score: 0,
       width: window.innerWidth,
       height: window.innerWidth
   },
   player = {},
   // ...

Head into the init() function and add the following code above the requestAnimFrame() function.

player = new Player();
player.add();
window.addEventListener('keydown', function(e) {
   if (e.key == 'a' || e.key == 'ArrowLeft') {
       player.speed.x -= 1;
   }
   else if (e.key == 'd' || e.key == 'ArrowRight') {
       player.speed.x += 1;
   }
});

This will add a player to the game and increase/decrease its speed with certain keys. Refresh the game and you should have a working prototype already!

Since we're working with simple rectangles we will use a simple AABB (or Axis-Aligned Bounding Box) collision detection. Really easy and really fast. The code is from the Mozilla Developer Network. See https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection for more information.

Add the following code in the in the js/sprites.js just after the remove() function.

// ...
collide: function (target) {

},

checkCollision: function(target) {
   if (this.position.x < target.position.x + target.size.width &&
       this.position.x + this.size.width > target.position.x &&
       this.position.y < target.position.y + target.size.height &&
       this.size.height + this.position.y > target.position.y) {
       return true;
   }
   return false;
}

This allows an object to test a collision with another object. To keep in the spirit of Object Oriented programming we will add an array with collidable objects. Add the following code just below this.position in the Sprite main object. this.collisionList = []; This will be an array with all names the object can collide against. Since collision detection will take place in the update() of our game loop we will add the following code in the Sprite.prototype.update() function. Just before the return true;

if (this.collisionList.length > 0) {
   for (var i = 0; i < GameObjects.length; i++) {
       if (this.collisionList.indexOf(GameObjects[i].name) > -1 &&
           this.checkCollision(GameObjects[i])) {
           this.collide(GameObjects[i]);
       }
   }
}
return true;

We check if the current object has at least one object that is in his array of objects that can collide with it and then loop through all the game objects to see if one of them is currently colliding. Take care: Collision detection can be a heavy process. In our example we only need one-way collision detection. For example: if we test collisions from a player to an obstacles we do not have to do the same check from obstacles to players. If a collision is detected the current object's collide() will be executed. Let's add 'obstacle' to our player's collision list. Add the following code to the Player function:

this.collisionList = ['obstacle'];

Then add the following code on the bottom of the same file.

Player.prototype.collide = function(target) {

   Sprite.prototype.collide.call(this, target);
   game.score = 0;

};

Pretty simple. Reset the score if a player hits an obstacle.

What is left is to display the score somewhere. This can be done with a few lines of code in our render() function in js/game.js. Add the following code at the end of the render() function:

ctx.fillStyle = "#000000";
ctx.font = "bold 32px Arial";
ctx.fillText(game.score, 10, 32);

Next up will be adding multiple players and allow them to play together.

Final step: Put the Couchfriends API in action

Demo: http://cdn.couchfriends.com/games/game-tutorial/4/.

Multiplayer made easy. Let get right to it. Add the following code in the <head> of our index.html.

<script src="http://cdn.couchfriends.com/api/couchfriends.api-latest.js"></script>

Next, change the player = {} variable to players = [] since we might have multiple players at the same time.

players = [],

Then remove all code from player = new Player(); to the end of the addEventListener since we won't use the keyboard anymore!

Now, connect to the Couchfriends service by adding the following code at the end of the init() function.

COUCHFRIENDS.settings.apiKey = '<your couchfriends.com api key>';
COUCHFRIENDS.settings.host = 'ws.couchfriends.com';
COUCHFRIENDS.settings.port = '80';
COUCHFRIENDS.connect();

Of course you might have to add your own apiKey. Couchfriends uses callbacks for most of the request coming from the server. See https://github.com/Couchfriends/Controller-API for a complete list and more information. For now we just need only two callbacks. The first one will be the 'connect' callback. Add the following code at the end of js/game.js.

COUCHFRIENDS.on('connect', function() {
   var jsonData = {
       topic: 'game',
       action: 'host',
       data: {
           sessionKey: ''
       }
   };
   COUCHFRIENDS.send(jsonData);
});

If everything works you get a nice popup in your game that shows a code. You can connect to it but nothing will happen. Let's add the connect and disconnection of players. Past the following code below the 'connect' callback.

COUCHFRIENDS.on('playerJoined', function(data) {
   var player = new Player();
   player.clientId = data.id;
   player.add();
   players.push(player);
});

COUCHFRIENDS.on('playerLeft', function(data) {
   for (var i = 0; i < players.length; i++) {
       if (players[i].clientId == data.id) {
           players[i].remove();
           players.splice(i,1);
       }
   }
});

This will create a new player object and add it to the global players array. If a player left it just removes itself. Piece of cake! If you follow the instructions on the screen you will see that a player will spawn at the moment you join the game. The player will also leave if you disconnect with your phone.

You can't move, but that is just a matter of seconds away! Add the following code and you have your first playable mutliplayer game with Couchfriends!

COUCHFRIENDS.on('playerOrientation', function(data) {
   for (var i = 0; i < players.length; i++) {
       if (players[i].clientId == data.id) {
           players[i].speed.x = data.x * 8.5;
           return;
       }
   }
});

This will loop through all players and add the current device position to the speed of the player. To get more information about the Couchfriends API read the wiki at https://github.com/Couchfriends/Controller-API. Game tip: you might wanna remove the speed reduction for the player since the API only sends data that has been changed.

// Remove this line
this.speed.x *= .95;

Couchfriends API will take care of most of the data/interaction with players. However there is a lot to customize and adjust to your gaming needs.

That's it! You're ready to make some awesome multiplayer games now!

Links & Further read


Have feedback or questions? Let us know at @GamesCouch!

Share with friends