Skip To Main Content

Building Multiplayer HTML5 Games with Cloak

Posted by Greg Smith

Oct 28 2013

Here at Bocoup I’ve been building a lot of multiplayer HTML5 games using Node.js and Socket.io. This stack has been working great for us! We’ve used Socket.io in our work with Game Show Network, PBS and MIT, and we build all kinds of stuff on Node.

My experience on these projects has led me to identify multiple chunks of code that I write again and again. If only there was a software library that did the exact things I needed! Well, thanks to Open Source Time at Bocoup, now there is!

Introducing Cloak

Cloak is a library for multiplayer HTML5 games that handles client/server messaging, room/lobby management, and the fiddly edge cases that you don’t want to think about. It’s the plumbing you don’t want to fret over when you’re too busy designing your game. It has both a server and a client library and handles all the communication between them. It’s a lot like writing Socket.io code, but with added features specific to game development.

The rest of this article will take you through an example of some server code with and without Cloak. If you’d rather play a two-player card game that uses Cloak, check out Grow21 (source here).

Writing some server code without Cloak

When making a game with Socket.io, one of the first things you’ll do is write some server code to handle incoming connections. It might look something like this:

io.sockets.on('connection', function(socket) {
  var game = new MyGame();

  socket.on('move', function(data) {
    game.move(data);
  });
});

Now, what happens if the user’s connection drops for a moment? With this code, a new game would be started. Let’s use a global games map and socket IDs to try to resume games:

var games = {};

io.sockets.on('connection', function(socket) {
  var id = socket.id;
  var game = games[id];

  if (game === undefined) {
    game = new MyGame();
    games[id] = game;
  }

  socket.on('move', function(data) {
    game.move(data);
  });
});

This is a start. But this highlights a bunch of edge cases we haven’t considered yet. What happens to the game if the user never reconnects? How long do they have to reconnect? What happens when a player reconnects after it’s been too long?

Even if we solved all those problems, we wouldn’t even have multiplayer yet! Let’s skip those problems for now and modify this code for a two-player game.

var waiting = null;
var games = {};

io.sockets.on('connection', function(socket) {
  var id = socket.id;
  var game = games[id];

  if (game === undefined) {
    if (waiting !== null && waiting !== id) {
      game = new MyGame();
      games[id] = game;
      games[waiting] = game;
      waiting = null
    }
    else {
      waiting = id;
    }
  }

  socket.on('move', function(data) {
    game.move(data);
  });
});

This may work, but now we have a whole slew of new questions. How does the client know if they’re waiting or in a game? How long might they be waiting? Will someone end up in the waiting state again when their game is done? Should waiting be a queue?

And of course, when you do all this yourself, there will be bugs. Did you notice the rather serious bugs in the above code? First, the game variable is never set for users that go into waiting. Second, socket.id is different on a new connection, so the client would have to save an id and use it to resume.

It goes on and on. Who wants to do that stuff, wouldn’t you rather be making the game itself? That’s why Cloak handles users, reconnection, lobbies, and rooms.

Try this on, it’s warm…

Let’s look at how you might start writing the same game in Cloak:

cloak.configure({

  // New users go to the lobby, a special
  // room that exists by default.
  autoJoinLobby: true,

  // Create a new room automatically when there are
  // enough users in the lobby. Users are added
  // to the new room when it's created.
  autoCreateRooms: true,

  // How many people are needed before a room is
  // created.
  // If a room falls below 2 users, the remaining
  // user is kicked back to the lobby.
  minRoomMembers: 2,

  // Only wait 15 seconds for a user to reconnect.
  // If they reconnect in this time, they stay in
  // the same room and nothing changes. Otherwise,
  // they are removed from any rooms they're in.
  reconnectWait: 15000,

  // Rooms automatically close after 30 minutes,
  // no matter what. Any users in this room would
  // go back to the lobby.
  roomLife: 1800000,

  // Event handlers for the lobby
  lobby: {
    newMember: function(user) {
      user.message('waitingForAnotherPlayer');
    }
  },

  // Event handlers for normal rooms
  room: {
    init: function() {
      this.data = new MyGame();
    },
    newMember: function(user) {
      user.message('gameStart');
    }
  },

  // Event handlers for messages from the client
  messages: {
    move: function(msg, user) {
      user.getRoom().data.move();
    }
  }

});

cloak.run();

As you can see, Cloak already handles a lot of the concerns that were brought up as we started writing our server code.

You probably noticed that this server code sends a few messages to users. Specifically, it sends waitingForAnotherPlayer and gameStart. Let’s look at the Cloak client code that would run in the browser and handle those messages:

var messageElement =
  document.querySelector('#message');

cloak.configure({
  messages: {
    waitingForAnotherPlayer: function() {
      messageElement.innerText =
        'Waiting for another player...';
    },
    gameStart: function() {
      messageElement.innerText =
        'The game has begun!';
    }
  }
});

cloak.run(serverURL);

And of course communication is two-way. You may remember that the server handles a move message from the client. The client could send this message simply with cloak.message('move', data);

Conclusion

It stands to reason that as I make more games I’ll add more cool stuff to Cloak. I hope it’s useful for you too, and I hope you send a pull request on GitHub if you add anything nifty!

Posted by
Greg Smith
on October 28th, 2013

Comments

We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.

Contact Us

We'd love to hear from you. Get in touch!