How To Build A Multiplayer Browser Game (Part 2)

Written by omgimanerd | Published 2017/02/16
Tech Story Tags: javascript | nodejs | multiplayer | games | programming

TLDRvia the TL;DR App

This post is a follow up to a post I made a while ago about building a multiplayer browser game. In this techsploration, I’m going to quickly brush over how latency can affect your game and how to combat that. I will briefly discuss a couple of game frameworks, including one I built at BrickHack3, and how to incorporate sound into your game.

Latency and Laaaaaaaaaaaaaaaaaaaaaaag

So firstly, what is latency? Well, if you’ve played any online game before, you might have experienced that phenomenon where you click or try to perform an action, and the nothing happens in the game until a bit later. Typically known as “lag”, this is incredibly frustrating to deal with for real time games like multiplayer shooters.

Latency occurs simply because of the physical distance between your computer and the server you’re connected to. Remember how in part one we talked about authoritative server determination? That’s what causes the lag since it takes time for your input to be sent to the server, verified, and sent back.

http://gabrielgambetta.com has fantastic resources and explanations on why this occurs and how to deal with it, and I will be linking lots of resources from his site.

A single client-server interaction (image from gabrielgambetta.com)

To combat lag, I’m going to introduce an idea called client prediction/extrapolation. This concept basically involves playing the animation before an action on the client side BEFORE it has been acknowledged by the server to emulate seamless gameplay. The client is basically predicting the state of the server after the acknowledgement.

Especially for player movement, this becomes easy to implement if you have a deterministic model for the world. If your game world is governed by predictable and calculated physics, then the client side can make educated assumptions that its input is valid so that the player move animation can begin before the acknowledgement from the server has been received.

Client prediction (image from gabrielgambetta.com)

Aside from these awesome diagrams, Gabriel Gambetta’s site has a lot of cool visualizations of how this works and an explanation of some of the problems with this as well. Definitely check out his live demo. Since this post is mostly for introducing the idea in the abstract, I highly recommend that you check out his sample code as well if you’d like to implement this in your own code.

incheon.gg

The homepage to incheon.gg

In the comments of How to Build a Multiplayer Game (part one), Opherv recommended the incheon.gg framework to me as a way to abstract away most of the problems that I discussed in the first part of this article. This framework is pretty cool and allows for games with discrete and continuous physics, all while handling most of the client-server synchronization logic. I’ve been checking it out and playing with their sample code and I highly recommend using this framework as a start if you don’t want to implement the network code yourself. It is extremely well documented and easy to read.

Granted, it was a little difficult to get started (mostly since I had to learn ES6 style classes) but since its such a dense topic with a lot of great discussion points I’ll make another post soon on how I explored the framework and what I made with it. (But huge shoutout to Opherv for linking me to this!)

Writing my own game framework at BrickHack3

If you don’t care too much about network lag and you want to try customizing and writing everything including the physics, sound, and animation, you can also check out the game code that I use.

Karl Coehlo (left), me (center), Dan Giaime (right) at BrickHack3

I participated in BrickHack3 at the Rochester Institute of Technology this weekend, and since my team submitted a multiplayer game (we won the Best Game Award), I actually spent some time writing my own framework to build the multiplayer game with.

I build multiplayer games a lot and over the years I’ve recycled a lot of the code. This framework is simply a culmination of what I’ve learned over the years. Unlike incheon.gg, my framework is much more tailored to the type of open world multiplayer games I like to build and doesn’t have any of the aforementioned networking functionality built in. It’s geared more for people who want to learn about game architecture by writing the code for every part themselves.

While it’s still a work in progress, my framework works much closer to the node.js architecture that powers it and allows for more of it to be customized. If you’re a game dev beginner or node.js beginner and you just came from part one, then I’d probably recommend you take a look at this code and tinker with it to understand what it does.

Here’s a link to the GitHub repository. This repository contains a refactored, commented, and cleaned up version of the mockup we built in part one. In the next section, I’ll explain each part of this framework in detail. I suggest cloning the repository if you’d like to follow along.

git clone https://github.com/omgimanerd/game-framework

Files included in the game framework

Let’s start with package.json, the bread and butter of any node.js project. The main dependencies I have are express, hashmap, morgan, pug, and socket.io. The only ones absolutely necessary to this project are socket.io, and hashmap though. morgan is a just a useful logging tool, express is a widely used middleware framework, and pug is a template engine, but if you wanted to, you could very easily swap them out for something you’re more comfortable with. The hashmap package is used to map each client’s socket ID to their Player instance, while socket.io is needed of course to power the real-time communication.

Next is bower.json, which contains two client side dependencies, jquery and howler.js. Neither is really necessary, but jquery makes your life easier, and howler.js is a cool library that powers the sound for the game. I’ll discuss how I use howler.js later.

The “lib” folder contains the server side files that handle and store the state of the game and its entities. Entity2D.js and Entity3D.js are ES5 style classes that encapsulate an entity with basic physics and a circular/spherical hitbox. If you wanted to get ambitious, you could implement more elaborate physics, gravity, hitbox meshes, etc into the class.

Entity2D.prototype.update = function(deltaTime) {var currentTime = (new Date()).getTime();if (deltaTime) {this.deltaTime = deltaTime;} else if (this.lastUpdateTime === 0) {this.deltaTime = 0;} else {this.deltaTime = (currentTime - this.lastUpdateTime) / 1000;}for (var i = 0; i < DIMENSIONS; ++i) {this.position[i] += this.velocity[i] * this.deltaTime;this.velocity[i] += this.acceleration[i] * this.deltaTime;this.acceleration[i] = 0;}this.lastUpdateTime = currentTime;};

Note how we implemented the framerate independent updating discussed in part one here. Updates to the position and velocity are done using a deltaTime so that we get consistent behavior. Player.js is just an extension of Entity2D.js and simply handles user input on top of the basic physics for the purpose of the mockup demo.

Game.js is a bit more interesting.

function Game() {this.clients = new HashMap();this.players = new HashMap();}

On top of this basic constructor, we have methods that facilitate the connecting and disconnecting of players. Essentially all this class does is handle the updating and broadcasting of the server state.

Game.prototype.update = function() {var players = this.getPlayers();for (var i = 0; i < players.length; ++i) {players[i].update();}};

Game.prototype.sendState = function() {var ids = this.clients.keys();for (var i = 0; i < ids.length; ++i) {this.clients.get(ids[i]).emit('update', {self: this.players.get(ids[i]),players: this.players.values().filter((player) => player.id != ids[i])});}};

Game.prototype.update() simply updates every entity in the game, and Game.prototype.sendState() broadcasts the game state to every client connected to the server. The only interesting thing to note (and this is specific to my implementation) is that we filter out the currently connected player and send them separately. You could implement this your own way however. That’s just the way I chose to do it to since it makes my life easier on the client side.

Moving on to the “public” directory, this folder contains all the statically served client-side JavaScript files.

Remember how we mentioned that it was a good idea to refactor stuff in part one? This is just one way of doing it. Building a set of modules that each specialize in handling a specific part of the functionality is always a good idea and good software design in general.

Drawing.js, given a HTML5 canvas context object, will handle the drawing of all the game sprites onto it. The client side Game.js uses window.requestAnimationFrame() to run a game loop on the client side for sending user input to the game server as well as rendering the received game states.

Game.prototype.animate = function() {this.animationFrameId = window.requestAnimationFrame(Util.bind(this, this.update));};

Game.prototype.update = function() {if (this.selfPlayer) {// Emits an event for the containing the player's input.this.socket.emit('player-action', {keyboardState: {left: Input.LEFT,right: Input.RIGHT,up: Input.UP,down: Input.DOWN}});// Draws the state of the game onto the canvas.this.draw();}this.animate();};

This snippet shows a bit of how that functionality works. Here’s a link to the requestAnimationFrame documentation if you don’t know what it does. In case you were wondering, Util.bind() creates a function that binds the context of the Game object to the update() function so that it can be called by requestAnimationFrame(). It’s a nitty hack that goes into the technicalities of JavaScript, so I won’t really discuss that since you can easily achieve the same thing with an anonymous function.

Input.js is a file I reuse very commonly in my games (with some variation). It binds the necessary event handlers to track user keyboard/mouse input and stores them for easy access. By abstracting away this functionality and keeping it contained in a single class, I don’t have to worry about fetching user input anywhere else.

Although Sound.js isn’t used in the mockup demo, I’ll explain it quickly here. This file wraps some of the functionality of the amazing library Howler.js, which I use to play sounds in my games. Upon initialization, it creates Howl objects for every sound that will be used in the game, which can then be played using Sound.prototype.play(). The way I have it implemented, all your sound files must be stored in a folder called “sound” inside the public directory, but this can easily be changed and tinkered with however you see fit. I highly recommend looking through the howler.js documentation.

Moving on, the “shared” directory only contains one file, Util.js.

Util.js is another one of those files that I recycle frequently between my projects. It contains a variety of useful utility functions that are used on both the client and server side. The “shared” folder is served statically so that I can include the script from an HTML file and require() it from the server side wherever I need. Feel free to relocate this file if you wish; this is just the organization that I found worked for me.

Another way I use the “shared” folder is to store game constants that need to be accessed by both the server and client. For example, I’ll have a class that looks like this:

function Constants() {}

Constants.PLAYER_HITBOX = 10;

Constants.WORLD_SIZE = 2500;

if (typeof(module) === 'object') {module.exports = Constants;} else {window.Util = Util;}

This allows the class to be handily loaded into the client or server side.

The “views” folder is relatively uninteresting and contains the template for the mockup demo.

doctype html

htmlheadtitle Game!bodycanvas(id='canvas' width='800px' height='600px')script(src='/socket.io/socket.io.js')script(src='/public/bower/jquery/dist/jquery.min.js')script(src='/shared/Util.js')script(src='/public/js/game/Drawing.js')script(src='/public/js/game/Game.js')script(src='/public/js/game/Input.js')script(src='/public/js/game/Sound.js')script(src='/public/js/client.js')

I use the pug templating engine, but you could easily plug in any template of your choice.

And finally, we have server.js in the root directory. This file is a pretty standard node.js server using the express middleware framework. There are two important snippets to take note of though.

const FPS = 60;

io.on('connection', (socket) => {socket.on('player-join', () => {game.addNewPlayer(socket);});

socket.on('player-action', (data) => {game.updatePlayerOnInput(socket.id, data);});

socket.on('disconnect', () => {game.removePlayer(socket.id);})});

setInterval(() => {game.update();game.sendState();}, 1000 / FPS);

The socket event handlers were covered in part one, this is the refactored and cleaned up implementation, where we take advantage of the methods we defined in the Game class from earlier. The setInterval() game loop simply runs the game updating and state broadcasting over and over at approximately 60 FPS.

Next Steps

From here, I highly suggest you take this framework and tinker with it. Make it your own, write your own code, print it out and burn it, I don’t care. Try upgrading the physics or writing your own animation engine. I hope you learn something from messing around it and I hope you make something cool.

If you do make something cool, please share it with me! I’d be super interested in checking out anything you make. That’s all I have for now. Thanks for reading! Please hit the ❤ button down below if you enjoyed this article :)

Follow me on Twitter: @omgimanerd


Published by HackerNoon on 2017/02/16