Real-time synchronous multiplayer 3D gaming with HTML5
by Ian Langworth
on 14 May 2012
Six weeks ago we set out to see if we could build a real-time, “twitch” 3D game in the browser using HTML5. We built a few games and were pleasantly surprised with what we achieved.
You can test one of our creations here: Air Hockey
WebGL has pretty good browser support at the moment, and three.js is an essential library because of the WebGL boilerplate it handles for you. It has a clean API, built-in functionality, and community support, but documentation is sparse. However, there are so many examples that you’ll inevitably find one which shows how to build what you’re trying to do.
Many of the examples make use of stats.js and dat.GUI for monitoring the frame rate and adjusting settings, which are good tools to include when developing your game. dat.GUI was especially helpful in fine-tuning our latency compensation algorithm and tweaking the game’s appearance, such as light strength and other material properties.
We bought the air hockey, mallet and puck models from TurboSquid, and getting them into the browser was easy thanks to three.js’s Blender exporter plugin, which exports Blender scenes as JSON. We honed our Blender skills to separate the models, tweak the materials and reduce the number of polygons to make the resulting file sizes smaller. (One humorous bug was that the puck wasn’t touching one side of the air hockey table — after an hour of debugging the physics engine we found that the 3D puck model was actually offset a few units on the X and Y axes and wasn’t centered on the origin. Oops.)
If you’re new to 3D, WebGL and three.js can be daunting since there’s a lot of terminology and unfamiliar concepts. Since none of us are 3D artists it took a lot of time to get lighting and materials just right. We did a lot of experimentation by tweaking the graphics, reloading the game and playing against ourselves. A better approach, which we realized when we watched Mr.doob’s presentation on the RO.ME project, is to separate parts of the presentation into small, stand-alone demos which can be tweaked quickly and easiliy before being included in the final result.
We wanted to see if we could make a responsive, low-latency game that challenges players’ reflexes, and air hockey seemed like a good fit. Moving mallets and a puck around requires snappy networking performance, and we knew that the game would be unplayable if the networking was even a little bit slow. Fortunately, WebSockets provided great performance.
Our approach to networking was relatively unsophisticated but it was good enough to prove our point: “twitch” gaming in HTML5 is totally possible! Our networking code worked like this:
- The client sends the position of the player’s mallet to the server.
- The server computes the velocity of both mallets, based on their previous and current positions, and then advances the game simulator, which detects collisions and computes the new velocity and location of the puck.
- The server sends out the latest puck and mallet locations to the clients, which display it to the players.
Of course, latency complicates this process. If it takes 50ms for a packet to get from the server to the client, then the puck position that the player sees will be 50ms out of date. This makes it harder for the player to hit the puck. Here’s how we solved this problem:
- The client measures the latency between the client and the server — For example, 50ms.
- When the client receives the position and velocity of the puck from the server it uses the same physics simulator that the server uses to predict where the puck will be 50ms from now.
- It then displays the puck at this predicted location. Now the player doesn’t have to “lead” the puck in order to hit it reliably.
There are other modern, well-established techniques for lag compensation that could improve the playability of our game even more, but we didn’t use any of them for our demo. Using only '90s-era game networking techniques was enough to build a fun, real-time, multiplayer browser game.
Also, it’s worth noting that the server is authoritative about the state of the game. All real-world multiplayer gaming has to be done this way to prevent cheating.
We tried using RequireJS to write client-side code, but wrapping all of our code in AMD-style module definitions felt clumsy and alien, and we couldn’t get the whole shared-code thing to work. Later on we discovered Browserify, which was life-changing. Not only did Browserify work great, but it provides wrappers for many standard Node libraries so we could write
events = require 'events' and
class Game extends events.EventEmitter and have it work on the client-side. The only thing we couldn’t get to work with Browserify was Backbone.js, so we wrote 50 lines of CoffeeScript to replace the parts we needed from it.
Performance and optimization
As already mentioned, we run our game server on Node.js. We felt that there was no other sensible option given that Node.js gives us the ability to run the same game code on the client and the server. So far we’ve been pretty pleased with Node’s performance, and in our load tests we found that we can run about 100 concurrent games on a single 512MB Rackspace VM before performance starts to degrade. We spent almost no time optimizing our code so this performance was a rather pleasant surprise.
Any discussion of HTML5 performance will inevitably include garbage collection, and we were initially concerned that GC pauses might cause noticeable pauses in game play. With Chrome this turned out not to be a problem. When playing Air Hockey on Firefox, unfortunately, there are noticeable blips in smoothness which we think is related to garbage collection. However, we made no effort at all to reduce the amount of garbage we generate, and we think it’s probably possible to get great performance on Firefox by being more careful about how much garbage your code generates.
The biggest thing holding back browser game development is the lack of tools. We see a lot of frameworks, but they either constrain you into making your game a certain way, or they get acquired and are never released. There are some great libraries and components, but it’s still up to you to string them together.
Flash and Unity have well-established development environments, and iOS and Xbox Live Arcade are platforms which provide end-to-end solutions for building and publishing a game. But the browser is still very, very new, and building an HTML5 game is still analogous to assembly language. In our game there’s no slider we can use to tweak the color of the puck or adjust the brightness of a light — if we wanted any of that we’d have to build it or buy in (literally) to a heavier framework.
We are excited about the possibilities in terms of bringing near-console-like experiences into the browser with synchronous, multiplayer gaming using HTML5. As fans of StarCraft and Halo ourselves, we’re ready to see those experiences come into the browser, and we hope we can inspire you to want them as well.
If you would like to get early access to our games and game development tools, please sign up at http://artillerygames.com.
- What every programmer needs to know about game networking
- Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization
- A Stream-based Time Synchronization Technique For Networked Computer Games