Build an HTML5 Game: A Developer’s Guide with CSS and JavaScript (2015)
Part II. Enhancements with HTML5 and the Canvas
Chapter 8. Next Steps in HTML5
In addition to graphical advances, HTML5 has a host of other features that make it a powerful game development environment. In this chapter, I’ll discuss a few of them so you’re aware of what features are available, and I’ll point you to some useful resources for further reading. Some of these features, such as WebGL, are subjects worthy of their own books, whereas others will be useful only for certain types of games. For these reasons, I’ll only introduce the concepts here and leave more thorough exploration up to you.
Saving and Retrieving Data
People play games like Bubble Shooter in short sessions with little or no persistent data; in fact, our game saves only the high score from one session to the next. At present, the high score is stored in Web Storage, so it’s unique to the browser the game is played on. To save a global high score and display a high score table, we’d need to write a server-side component that sends the score to a server and retrieves a list of high scores.
Games with more complex states should have server-side access, too. When you store state on the server, players can return to the same game from multiple devices. For our purposes, we’ll use two main ways to save and retrieve data on a server: AJAX and WebSockets.
AJAX
AJAX (Asynchronous JavaScript and XML) provides a technique for sending a request to a server and receiving a response. AJAX is not a single technology but rather a method by which a number of tried-and-tested browser features are combined to make server-side calls and manage the responses. All of the major browsers have supported AJAX for a number of years.
Although the X stands for XML, you can use AJAX to retrieve HTML data, string data, and JSON strings that can be parsed and interpreted. The code for making AJAX calls is well documented, and multiple libraries are available so you don’t have to handcraft the calls. For example, here’s how you’d send an AJAX request to a server with the $.ajax call in jQuery:
$.ajax({
➊ url : "save_data.php",
➋ data : "high_score =" + highScore,
➌ type : "POST",
➍ complete : function(data){
console.log(data);
}
});
This $.ajax call makes a POST request to the relative URL save_data.php, sends the value contained in highScore to the server under the name high_score, and logs the server’s response to the console. I set the URL target for the request ➊, the data to send ➋, the type of request ➌, and a function to run after the request completes➍, but you can set many other properties, including functions to run in case of an error, timeout settings, and so on. These are listed in the jQuery documentation at http://api.jquery.com/.
NOTE
The A in AJAX stands for asynchronous, because other JavaScript operations will continue while the server deals with the data and sends the response. That means you can’t be sure when the complete function will run: it’ll happen whenever the response comes back, but the user interface will remain responsive while it happens. It’s possible to make synchronous calls, but because this effectively freezes the entire page until the request is complete, the user experience is generally so poor that doing so is considered bad practice.
WebSockets
Most modern browsers also have WebSockets available to make client to server calls. WebSockets are a relatively new technology incorporated into the HTML5 specification. If you want to learn how they work at a lower level than I describe here, a good place to start is with the Mozilla Developer Network documentation at https://developer.mozilla.org/en/docs/WebSockets/.
WebSockets are similar to AJAX, but whereas AJAX sets up a call-and-response relationship between the client and server, a WebSocket maintains a persistent connection between them. The client deals with responses as they come in, and the JavaScript code can listen continuously for further responses. The server also constantly listens while the socket is open; therefore, WebSockets are much better than AJAX when conversations between the client and server involve lots of small data transactions.
A persistent connection is especially useful in multiplayer gaming environments. Before WebSockets, the main way to update game state elements shared by multiple clients, such as player avatars within an environment, was to continuously poll the server with AJAX and check for updates. This would be coded to happen every few seconds, which obviously isn’t sufficient for a real-time game. People tried various hacks—such as a technique called long-polling, which effectively tricks the client into maintaining a connection to the server—to improve the process, but these were often inefficient in terms of server resources. Now, you can just leave a WebSocket open, and whenever one client updates the game state, the server can immediately update all of the other clients’ information without waiting for the next update cycle.
Mainstream browsers have ever-improving support for WebSockets, and as with AJAX, I recommend using a library to eliminate some of the nitty-gritty of opening connections, sending and listening for data, and handling errors. Libraries will also often have a fallback to AJAX or other server communication methods for cases in which WebSockets aren’t supported; however, the fallbacks may not replicate the performance features that you’re using WebSockets for in the first place, so be aware that they’re not a magic solution.
Socket.IO (http://socket.io/) is one of the most popular WebSocket libraries. Here’s how you can use it to make a call:
var socket = io.connect("http://localhost");
socket.emit("new_high_score", {
high_score : highScore
});
});
This code uses a call to the library with io.connect to open a new WebSocket and then socket.emit sends the highScore value as an event named new_high_score.
WebSockets and libraries such as Socket.IO have much greater capabilities than AJAX, but the libraries that make them easy to use often assume a specific server-side environment. If you plan to use WebSockets, check that the library you plan to use has a backend component that matches your server environment. Libraries for most platforms are readily available, whether you’re using Node.js, .NET, or Java.
Along with sending and receiving data to and from the server, you might also want to process certain data outside your main game program. That’s where Web Workers will come in handy.
Web Workers
JavaScript in a browser is generally considered a single-threaded environment, meaning that only one script can run at a time. This won’t cause problems most of the time but can be an issue if a particularly large computational process blocks the processor from animating, responding to user input, and performing other important tasks.
For example, let’s say processing game-level data takes the browser 1 or 2 seconds, and this happens every 30 seconds or so. The overall load may not be high, but you can’t pause the game every 30 seconds! In this situation, consider using a Web Worker.
Web Workers (https://developer.mozilla.org/en/docs/Web/API/Worker/) allow you to run code in a separate thread without blocking your main JavaScript operations. They’re called “workers” because you can essentially hand them a task and tell them to report back when they’re finished. The browser will determine how much CPU time to give them so as not to interfere unduly with other processes. Workers can be dedicated or shared, but you’ll generally find dedicated workers most useful, especially while support for Web Workers is being developed across browsers.
Web Workers follow a couple of rules that differentiate them from regular JavaScript. Most important, they have no access to the DOM, the browser document, or the browser window. Workers also operate within their own scope, so you’ll need to pass data explicitly and then retrieve the result when complete. I’ll illustrate how they work with the following example.
Workers are initialized by passing the name of a script to load to the new Worker command:
var worker = new Worker("work.js");
This will start a new worker, and that worker will run the script inside work.js.
A worker runs when you send it a message via postMessage:
worker.postMessage();
The postMessage command can contain a JavaScript object or be empty.
You can handle responses—values a worker returns when it completes a task—by adding event listeners to the worker within the calling script:
worker.addEventListener("message", function(e) {
console.log(e.data);
}, false);
Here, e contains the data that worker sent back. The event to listen to, labeled "message", is any valid string. Therefore, a worker could send back different responses in different circumstances, or it could just keep working and sending messages.
Inside the worker, the model of event listeners is similar, with the worker referring to itself as this or self. As an example, work.js could contain the following to return the message:
self.addEventListener("message", function(e) {
self.postMessage({
message : "I'm done now"
});
}, false);
This code listens for an event marked "message", and on receipt, it immediately posts a response in the form of an object.
At present, not all the major browsers support Web Workers well enough to make it reliable. Polyfills do exist for Web Workers, but these will often negatively affect your user’s experience if a long-running process that you assumed would be nonblocking suddenly freezes the game for a few seconds. However, the situation is constantly improving, and hopefully, Web Workers will soon be considered a core part of the HTML5 game developer’s arsenal.
But managing your data more effectively is just a start to making your game more fun. Appearance matters, too, and for a graphics upgrade, you can go 3D with WebGL or even use it to beef up your rendering power for 2D games.
WebGL
For the canvas version of Bubble Shooter, we used the 2D rendering context, accessed with calls along the lines of
var context = canvas.getContext("2d");
As I touched upon in Chapter 6, the specification of "2d" implies that other options are available, and sometimes, depending on browser support, that’s true. The third dimension is accessed through WebGL, an API that provides a set of 3D manipulation functions for creating scenes, adding lighting and textures, positioning cameras, and so on, taking advantage of the acceleration that modern graphics cards provide. (Visit https://www.khronos.org/registry/webgl/specs/1.0/ to learn about WebGL in more detail.) To start using WebGL, we first instantiate a 3D context with the following:
var context = canvas.getContext("webgl");
This is sometimes retrieved as "experimental-webgl", so the most compatible call is this:
var context = canvas.getContext("webgl")
|| canvas.getContext("experimental-webgl");
Accelerated WebGL is powerful enough to display fully rendered 3D scenes rivaling those of native games. The downside is that working in three dimensions and manipulating and creating scenes requires a lot of math and a lot of low-level code that involves writing programs directly to the graphics processor. The concepts are the same as when creating 3D games in native code, such as C++, and require low-level knowledge of 3D modeling to describe the shape of an object; textures to define surface patterns; and shaders, which describe how to render a surface when light hits it. As such, I highly recommend working with existing libraries to handle model rendering, any physics requirements, and basically any features you can get off the shelf. Babylon.js (http://www.babylonjs.com/) and PlayCanvas (https://playcanvas.com/) are two libraries that make working with WebGL in the browser much simpler.
Using WebGL also brings up the question of how to import objects and textures into 3D scenes. Typically, you create models in modeling software, such as 3D Studio or Maya, and then export to a commonly supported format. WebGL libraries generally won’t work with those formats, so you’ll usually need to convert from the original 3D modeling file format to JSON using another set of tools, such as the 3DS Max-to-Babylon.js exporter (https://github.com/BabylonJS/Babylon.js/tree/master/Exporters/3ds%20Max), which exports from Autodesk’s 3D Studio product into Babylon.js.
Creating and converting 3D models is a large enough task that WebGL game development quickly becomes a project for teams of developers and designers rather than for a sole developer; however, many very impressive demos have been made entirely solo, and the Babylon.js website has a great set of showcases.
A secondary advantage of the WebGL context is that you can use it to render 2D scenes, which can then take advantage of the huge speed available through GPU acceleration. Particle effects and rendering large numbers of onscreen elements in accelerated WebGL far outperform the same tasks in the canvas.
I recommend that you look for off-the-shelf libraries that enable 2D rendering in WebGL. One such library is Pixi.js (http://www.pixijs.com/), which also provides a fallback to the canvas.
Browser support for WebGL is growing and includes the latest versions of Chrome, Firefox, and Internet Explorer, although older versions of Internet Explorer are incompatible at the time of this writing. For this reason, WebGL isn’t considered ready for mass-market development, but this situation continues to improve.
Building a slick game is all well and good, but a game is nothing without players. To reach players, you need to deploy your game somewhere publicly accessible. Depending on where you deploy, you should consider some changes to improve the player’s experience.
Deploying HTML5 Games
In this section, I’ll give a brief overview of the process behind deploying a game running inside desktop and mobile browsers, and I’ll explain how you’d wrap up an HTML5 application as a native mobile application.
Running Fullscreen in a Desktop Browser
One way to deploy an HTML5 game is to just create a website and upload it. In fact, just upload Bubble Shooter to the Web to make it accessible to anyone who accesses the index.html file. Deploying an HTML5 game to the Web is no different from deploying any other website; however, players often complain about a lack of immersion when they are running games in a browser, because it’s easy to become distracted by tabs showing notifications from Facebook, email, instant messages, and so on. The HTML5 arsenal has a trick to fix these interruptions: the Fullscreen API.
Where supported, the Fullscreen API lets a web page fill the entire width and height of the screen, removing the address bar and other browser frame elements. You can implement fullscreen capabilities by running the following JavaScript code. For security reasons, you need to run this code inside a user-generated event handler; that is, you will usually make a button for the player to click or specify a key for them to press to activate fullscreen mode.
if(document.body.requestFullScreen) {
document.body.requestFullScreen();
} else if(document.body.mozRequestFullScreen) {
document.body.mozRequestFullScreen();
} else if(document.body.webkitRequestFullScreen) {
document.body.webkitRequestFullScreen();
} else if(document.body.msRequestFullScreen){
document.body.msRequestFullScreen();
}
Note the use of vendor prefixes while the requestFullScreen API is being implemented (mozRequestFullScreen for Firefox, webkitRequestFullScreen for Chrome, and so on). When you call requestFullScreen, the user should see a dialog from the browser asking whether to allow or deny your game’s request to go fullscreen. If the player allows fullscreen, pressing the ESC key should return them to the regular view.
You can also apply fullscreen mode to a single element inside the DOM. You might want to do this if you have a game running within a website with navigation to other pages, for example. Then, players can go into fullscreen mode to remove the distractions of navigation bars and other page clutter. You could even apply fullscreen mode to Bubble Shooter. Just add a new toolbar button that runs the following code when a player clicks the button:
if(document.body.requestFullScreen) {
$("#page").get(0).requestFullScreen();
}else if(document.body.mozRequestFullScreen) {
$("#page").get(0).mozRequestFullScreen();
}else if(document.body.webkitRequestFullScreen) {
$("#page").get(0).webkitRequestFullScreen();
}else if(document.body.msRequestFullScreen){
$("#page").get(0).msRequestFullScreen();
}
I’ll leave this as an exercise for you to implement, and I suggest you add it to ui.js to keep it with the other user interface code. But if you’d rather not deploy to your own website, try a hosting service. You could set up an application on Facebook or upload a game to a dedicated game website, such as Kongregate.
Of course, the promise of cross-platform development and deployment is one of the biggest attractions of HTML5, and because most desktop browser features have been ported to mobile browsers, Bubble Shooter should work just as well on both. However, the behaviors aren’t quite identical between platforms, and I’ll discuss those differences next.
Running in a Mobile Browser
Even if you’re still running Bubble Shooter on a local or development web server, you should be able to load the game in a mobile browser and play it. It should function just as well as it does on a desktop browser. Congratulations, you’ve just made your first mobile game!
NOTE
In case you haven’t deployed the game yet, you can also play it at http://buildanhtml5game.com/bubbleshooter/.
When developing games for mobile devices, it’s more likely you’ll need to make usability and interface changes than technical ones, but that’s not to say you can ignore implementation changes completely. You’ll benefit from knowing the subtle differences in behavior and how to optimize the experience for mobile users, so let’s get started.
Touch Events
First, touchscreen-specific events are implemented by browsers on touchscreen devices. Two of those events are touchstart and touchend, which are roughly equivalent to mousedown and mouseup, respectively. However, the click event differs slightly in a touchscreen environment. Mobile browsers wait a few hundred milliseconds to determine whether the user double-taps, which is a zoom operation, to make absolutely sure that the user intends a single click. This won’t make much difference in Bubble Shooter, but for rapid-reaction games, those few hundred milliseconds will be noticeable to the player.
You can use mobile-specific events, and they’ll be ignored on desktop devices without a touchscreen, although for the most part, using mousedown will have the same effect as touchstart and mouseup will be equivalent to touchend. For example, in Bubble Shooter, we could usemousedown instead of click to detect when the player wants to fire a bubble, which would turn this line from game.js:
$("#game").bind("click",clickGameScreen);
into this line of code instead:
$("#game").bind("mousedown",clickGameScreen);
The only effect would be that the bubble will fire when the user clicks the mouse button down or touches the screen rather than waiting for the mouse button to be released or the finger removed from the screen.
NOTE
Using only the mouse and touch events will remove keyboard accessibility if you have a game that could conceivably be controlled by the keyboard. In some games, you might want to continue using the click event so a player could still, for example, navigate a menu system using the keyboard or other input device.
If you know that your game will be played only on a mobile device, you could also use touchstart:
$("#game").bind("touchstart",clickGameScreen);
This should work the same way as mousedown.
You may be wondering, then, why touchstart and touchend exist at all if they’re virtually equivalent to mousedown and mouseup. The answer is that in most cases you can treat them as conceptually equivalent, but touch events can be useful if you want to detect more than one touch point simultaneously. The user will (usually) have only one mouse pointer, but it’s possible to make contact with a touchscreen in multiple places. If you’re building a game that requires this kind of input, touch events are the ones to use, and you’ll have to find a way to make them work in a mouse environment.
Scaling
Another interaction difference comes into play with zooming. You probably don’t want players zooming into the game area at all, whether they double-tap or not. Fortunately, you can restrict this by adding <meta> tags to the HTML head:
<meta name="viewport" content="user-scalable=no, initial-scale=1,
maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
This example tells the browser to render the page at a scale of 1:1 and set the viewport width to the default for the device. The content of the <meta> tag specifies the size of the display and restricts (or allows) zooming. Apple originally introduced this <meta> tag, and other browsers use it as a basis for their own behavior. Hence, Apple’s own documentation (https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html) is the best place to look for a description of the various options. However, using this tag is very much a case of looking up what’s expected to happen in any particular mobile browser and then testing it to see how it works in practice. Work is underway to standardize viewport sizing using CSS (http://www.w3.org/TR/css-device-adapt/), although it has minimal browser support at present.
The most common option you’ll use in the <meta> tag is user-scalable=no, which simply prevents the user from zooming. But changing the other values in the <meta> tag can greatly affect how the browser displays your game, too. The settings in the <meta> tag are as follows:
§ user-scalable. Can be yes or no. Allows or disables zooming.
§ initial-scale. A decimal number specifying the zoom factor at which to draw the viewport.
§ maximum-scale. A decimal representing the maximum zoomable scale to allow the user to zoom to.
§ minimum-scale. A decimal representing the minimum zoomable scale to allow the user to zoom to.
§ width. Specify this in pixels, or use device-width.
§ height. Specify this in pixels, or use device-height.
If the game is designed with a width of, say, 760 pixels, you could set width to 760, and the browser would keep the page at that width and eliminate any extra pixels of spacing on either side. Unfortunately, by scaling the viewport, you’ll almost certainly have to solve problems with image scaling and aspect ratio; trying to draw 760 pixels on a screen that’s made up of 1024 pixels means some aliasing will need to occur.
Aspect ratios also vary much more between mobile devices than desktop screens. For example, the iPad 1 and 2 have a resolution of 1024×768, the iPad 3 is 2048×1536, the iPhone 6 is 750×1334, the iPhone 6 Plus is 1080×1920, and there are almost as many Android resolutions as there are devices. Unfortunately, no simple solution exists. Be sure to test continually on a wide range of devices, and experiment with a combination of <meta> properties and CSS layouts to ensure your game looks good on a variety of screen sizes and aspect ratios.
Of course, even after you solve the aspect ratio problem, if users are still playing your game through a mobile browser, they may not be able to play while offline. To really get an HTML5 game onto the device, you need to wrap up the code in a native package. When your game is a native application, the user should be able to play it whether online or offline, unless your game requires an Internet connection anyway. Let’s look at using a wrapper service next.
Deploying as a Native Application
You have two main ways to deploy your HTML5 game as a native web application. You can write a wrapper using Objective-C, Java, or whichever language the target platform requires, or you can use an existing wrapper service. Unless you’re very proficient with native mobile coding, I highly recommend that you look at a wrapper service.
Wrapper services, such as PhoneGap/Cordova (http://cordova.apache.org/) and Ludei (https://www.ludei.com/), give you less control, but they often provide access to native features, such as accelerometers and in-app purchases. Sometimes they even offer accelerated graphics capabilities and bespoke APIs. They require less time and effort, too, making them an excellent way to build test deployments so you can quickly see your game running on a device. I’d advise using a service unless you have a very good reason not to.
Using a third-party wrapper often involves uploading your HTML5 code through an online service and downloading a compiled version for each device. These services effectively do the same work as custom wrappers, but they’ve been optimized over iterations, usually for multiple platforms. They also continue to add support for newer handsets and operating systems, which is very time consuming to keep on top of yourself. In addition, a community is usually writing plug-ins to provide extra functionality, such as offering in-app purchases or accessing the device’s camera.
Just remember that no matter how you decide to wrap your HTML5 application, the files will all be running in a local environment; that is, your game won’t need to download assets over the Web or from a server. As a result, your game will be playable even when no web connection is available. If you’re developing a multiplayer game, it will need an Internet connection to be active, but even then your game will benefit from faster startup times and (if your game is a hit) it will save on bandwidth costs. As always, perform constant iterative testing to intercept problems before they become major issues.
That’s the end of my mobile tour, but on a desktop browser, Bubble Shooter is simple enough that unless you’re playing on a very low-powered machine, you shouldn’t run into performance problems. But at some point, as you develop more complex games, you’ll find that some piece of code runs slower than intended, and then you’ll want to optimize that code.
Optimization
Two main areas to look at when you’re optimizing a game are memory management and speed. In particular, you should ensure your game doesn’t consume increasing amounts of system resources the longer it runs, and you’ll want to make the most of available hardware and coding tricks for speed.
Whether or not you encounter visible problems, such as slowing animation, continually checking your game’s performance is good practice. You’ll likely only need to optimize for speed as a result of a specific problem, but keeping an eye on memory utilization is good practice in all cases. For example, a game may run fine when you play for five minutes, but if you leave it open in a browser tab for hours on end, you may return to find your nice animation loop eating up tens or hundreds of megabytes of memory because of a leak. For less powerful mobile devices, this can be a real problem.
Fortunately, browser tools can help identify and diagnose problems, and you can implement coding techniques to fix those problems or minimize the risk of them happening. Good memory management is particularly important, so we’ll look at that before we move on to speed optimization.
Memory Management
You might not expect a small JavaScript game to run into memory issues on systems that happily run massive 3D games, but memory management is actually a pressing concern for HTML5 game developers. The problem is less about running out of memory (although it is possible, with some effort, to use up a vast amount of memory) and more about the way JavaScript allocates memory and frees it up later. Rather than constantly allocating and freeing memory, browsers run through periodic sweeps to clear memory, which can cause jittery animations, unresponsive interfaces, and general interruption of game flow.
Writing JavaScript in ways that minimize memory usage is a large subject, and browser vendors often publish papers on how to get the best out of their systems. For example, check out Mozilla’s documentation on memory management at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management/. You can also read an excellent introduction to memory-efficient JavaScript, written by one of the Chrome engineers, Addy Osmani, at http://www.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/.
The key to dealing with memory issues is identifying them in the first place. You may suspect you have a problem, but you need to know where it is. The main desktop browsers have tools to help. Those tools are constantly evolving, so I won’t discuss them in depth. But a search through the documentation for each browser should bring up relevant documents and tutorials, such as the one for Chrome at https://developer.chrome.com/devtools/docs/javascript-memory-profiling/.
Here’s where to start in the three major browsers:
§ In Chrome, open Developer Tools and click Profiles. Select Take Heap Snapshot and click Take Snapshot to examine objects in memory, including DOM elements. Figure 8-1 shows how this looks for Bubble Shooter.
§ In Firefox, you can use Firebug and other plug-ins to examine objects in memory. You can also type about:memory into the address bar for a snapshot of what’s currently in the browser’s memory.
§ In Internet Explorer 11, open the Developer Tools and select the Memory tool.
Figure 8-1. A snapshot of Bubble Shooter in memory, as displayed by the Chrome browser tools
Another useful tool is to visualize when garbage collection is occurring. This takes the form of a graph across time, and you can see what range of memory your game is occupying. Figure 8-2 shows Bubble Shooter’s memory usage over time.
The sawtooth line represents memory used when objects are created. The line rises, and then it sharply drops when garbage collection occurs. Although we’re not creating and destroying many objects, there’s a definite sign that if we saw problems with animations not running smoothly, we could look at using more object pools.
The key to maintaining fast animations is to test and iterate. This is especially true when developing for mobile devices, where debugging tools are usually slightly harder to access and where memory and processing power are also usually less abundant. If you notice intermittent slowdowns and animation freezes that are difficult to reproduce, it’s likely that you have a memory issue to identify and address.
Figure 8-2. Memory usage by Bubble Shooter
Optimizing for Speed
Memory may or may not be an issue, depending on your game’s needs, and memory fixes occasionally require coding techniques somewhat at odds with writing readable, reusable code. However, optimizing for speed is more achievable as a side effect of following general best practices.
JavaScript engines are improving in speed all the time and so are browsers’ rendering engines (especially with the addition of WebGL). But, as with garbage collection, you should still be aware of the pain points. The browser vendors won’t solve all your performance problems for you. In reality, JavaScript interpreters are becoming so fast that speed problems are more likely to occur while rendering than anywhere else; however, coding techniques can make the translation between JavaScript and machine code more efficient and speed up operations, such as passing image data to the rendering engine.
Each time you add or change an element in the DOM, the browser has to work out what to draw and where to draw it. HTML documents were originally designed as flowing, text-based documents, and the browser will assume the content you send it is meant to be laid out like any other web page.
But actions that cause the browser to repaint the display, such as adding new elements to the screen or changing an element’s coordinates, are very common in games. In Bubble Shooter, we can get away with adding and removing elements as we want because relatively few elements are onscreen. Multiply the number of items onscreen by 10 or 100, and you’ll start to see problems. Remember that the garbage collector needs to sweep away any element deleted from the scene, and DOM elements tend to be complex.
By contrast, the canvas copes with graphical additions without an expensive paint operation because no reflowing occurs inside a canvas element. The browser considers canvas elements to be images, which are just streams of pixels that go from memory to screen.
NOTE
Changing properties of the canvas element, such as its position or transparency, rather than pixels within it, is just as expensive as changing any other DOM element.
You can see how much time the browser spends painting a scene by loading Bubble Shooter in the Chrome desktop browser, pressing F12 to open Developer Tools, and navigating to the Timeline tab. Click Record in the bottom control bar, reload the game, and then click the Events bar at the top to see a view like Figure 8-3.
Figure 8-3. The browser events involved in playing Bubble Shooter in Chrome
All of the paint events ➊, like those in Figure 8-3, should be highlighted in green on your screen. In the canvas version of the game, a few paint calls occur once a level has been loaded, whereas in the CSS version, such calls occur constantly.
You can use the Timeline tool to identify when paint events happen and minimize them to speed up your game’s rendering. Just remember that different browsers may repaint the scene at different times. As always, use the tools available, but also test on your target platforms and devices as the main guide to performance.
In general, minimizing DOM manipulation is the key to minimizing paint operations. Search for articles on minimizing browser reflow and browser paint operations for more detailed and up-to-date information on the inner workings of rendering engines.
Security
If your game has any kind of scoring or progression system, someone will try to cheat it. But the key is to assess the ramifications of having cheats slip through the system and decide whether or not those ramifications are critical. For Bubble Shooter, this isn’t an issue: if someone wants to set a high score on their local machine, that’s up to them. However, for games with an online competitive element or where buying power-ups is a revenue stream, you need to ensure that cheating is difficult to impossible.
We can try to address security in HTML5 games in a few ways.
Trust No One
The simplistic approach to security in any games that run on the client, whether they’re built with HTML5, Flash, or even native code, is to not trust anything that the client sends to the server. A POST back to the server with, say, a high score value (as we used in the examples on AJAX and Web-Sockets) is easily forged. The score may be valid, the POST may be forged, or someone may even use a debugging tool to change the high score while the game runs. The server only sees the data as it’s received and can’t differentiate between a genuine and a cheat POST.
Unfortunately, not trusting the client is often the correct approach: there’s no way to completely guarantee the security of code running on the client. The only way to make a game secure is to have all game logic processed by the server. To completely secure Bubble Shooter, we’d pass mouse clicks to the server, have the collision and popping logic run on the server, and then pass the results back to the client to animate. This is more difficult to develop and test, and the user would need a constant (and fast) Internet connection to even play the game.
Obfuscation
The server-side approach is essential when a game includes financial transactions, but for many games, obfuscation is good enough. The idea behind obfuscation is to make cheating as difficult as possible, essentially making the effort involved greater than the reward. For example, if a high score is posted to the server as an encoded value, passed with a checksum, and takes hours of reading through code to decipher how it was created, cheaters are unlikely to go through all of that effort just to get to the top of a high score table.
Of course, obfuscation usually comes at the price of readability for you as well as hackers. But there are a number of ways to make code difficult to read, and you can even apply some as a post-build process.
The simplest option is running your code through a minifier before you package it for a live environment. Minifiers shorten all long variable names in your code and eliminate whitespace. For example, code such as this:
var highScore = 0;
highScore += 20;
becomes something like this after minifying:
var highScore=0;highScore+=20;
Effectively, minifying removes the whitespace and puts everything onto one line. Such minified code quickly becomes difficult to read. You can easily un-minify the line breaks. Many minifiers will also rename variables inside functions. For example, this function:
var highScore = (function(){
var highScore = 0;
highScore += 20;
return highScore;
});
could become much smaller:
var highScore=function(){var a=0;return a+=20};
The property that you’ve been calling highScore in your code becomes much harder to find if it’s now called a instead!
NOTE
Minifying code also has the added advantage of creating smaller code that should therefore load faster, which is an important consideration when deploying in a web environment. In fact, you should consider minifying your JavaScript code in all web applications.
Google released a tool called the Closure Compiler, which acts as a minifier along with providing a number of other benefits. It attempts to optimize code, even rewriting it in places, and outputs smaller and sometimes even faster code than the original version. The compiler generates JavaScript, analyzes your code, and raises errors. Declaring variables, keeping track of scope, and maintaining other good practices pay off when you use a minifier such as the Closure Compiler, because the compiler provides greater benefits the clearer and simpler your coding structure is.
You can use the Closure Compiler online or download and run the Java application from https://developers.google.com/closure/compiler/. Once you have access to it, paste the Closure Compiler in the JavaScript code that you want to compile and then copy the output. It’s recommended that you keep a copy of your original code, because the compiler output is far too difficult to work with if you need to make further changes.
Using Private Variables
Along with post-build processes, you can also code in ways that make it harder for cheaters to follow through code and change it on the fly. For example, private variables make manipulating internal values on the console more difficult. The following has a private variable for highScore:
var Game = function(){
var highScore = 0;
var getHighScore = function(){ return highScore;};
return this;
};
The variable is considered private because it only exists inside the scope of a Game object. We could have made the variable public as follows:
var Game = function(){
this.highScore = 0;
var getHighScore = function(){ return this.highScore;};
return this;
};
This would allow the value of highScore to be changed on a Game object just by changing the value of its highScore property. However, in the private version, there’s no way to access the value of highScore from outside the object.
If highScore is private, cheaters will have difficulty changing its value without using a program like Firebug to add a breakpoint within the object. They’ll have even more trouble if the code is minified and obfuscated. highScore is actually labeled "a", and it’s difficult to even find where the high score is updated in the first place.
With a couple of relatively simple steps (making some variables private and minifying our code), we’ve already narrowed down the potential cheaters from those who know a small amount of JavaScript to those who know it quite well and are willing to take the time to reverse engineer our code. Now, let’s look at one more way to prevent cheating.
Validating with Checksums
You can also secure information passed to the server by using checksums to validate the variable passed. The simplest techniques just encode a value so there is at least some check that the number is correct. This won’t eliminate cheating, and checksums don’t need to be very complicated, but it will ensure that anyone who wants to cheat needs to read and understand your JavaScript code first. For example, if we passed highScore to the server, we could POST something like this:
{
highScore : 9825,
check : 21
}
The value 21 is 9,825 modulus 129 (or highScore%129 in code), where 129 is a number I chose as being big enough to create a range of check values but also being a factor smaller than likely high scores. This almost trivial check actually increases the level of security because now the barrier to posting a fake high score is not only knowing how to POST but also being able to follow through the code to the point where the check value is created. A seasoned JavaScript programmer might find those steps simple, but the average meddling game player probably won’t.
The preceding example may be too trivial for your liking, and you can use any process you like for generating a checksum. Common approaches include using hash functions such as MD5, SHA, or CRC32, although the disadvantage of these is that programmers will often recognize the structure well enough to know they are looking at a standard hashing function.
In principle, any process you create that can generate a range of check values will significantly slow down, and possibly discourage, a large number of potential cheaters.
Of course, you may still get a few cheaters whatever you do, because some hackers enjoy the challenge of beating the programmer more than the challenge of beating the game. You can obfuscate as much as you like, and you may end up with code that’s almost impossible to read. But remember that you can never guarantee security in client-side code and never fully trust the information passed from the client to the server.
Summary
As you may have gathered from this chapter, browser support for HTML5 is an ever-changing landscape. The good news is that browsers are generally converging on the same standard rather than adding their own features. Also, support for HTML5 is improving all the time.
With the rate of change, it’s important to keep up-to-date on which browser features are ready for mainstream usage as well as what’s on the horizon. Whether in terms of improved performance, memory management, sound, or 3D features, the capabilities of HTML5 games are constantly advancing.
Further Practice
1. Add fullscreen capability to Bubble Shooter in a desktop browser. To make the switch as easy as possible, add a button to the top bar that is visible only if fullscreen mode is supported. Also, change the CSS so that when the page is displayed fullscreen, the game is centered.
2. Write a routine to post the player’s score to a fictional server address using jQuery’s ajax method. Post the score at the end of each level and write a checksum function to add basic security using your method of choice.
3. Find and test some online minifier and obfuscation services. Compare the file size of the output code to the size of the original source.