In the installment of this three-part series, seasoned console developers, formerly of Criterion, describe the process of moving to HTML5 development, covering a vast array of essentials. Don't miss Part 1 and Part 2.
In this final installment, we continue our description of HTML5 and related interfaces for functionality relevant to games, and look at strategies for dealing with important issues such as resource loading and security. We then give a brief overview of the state of HTML5 for mobile devices and conclude with some of the reasons we feel developers should already be targeting HTML5 for high-end games.
The WebSocket API allows a full-duplex permanent communication between the browser and a server. WebSocket connections start as standard HTTP connections and then get upgraded to the new protocol, which should make WebSocket connections reliable across proxies and firewalls. We found the API very simple to use. Setting a couple of callbacks gives you easy access to a full-duplex communication.
WebSocket connections use TCP as the transport mechanism, which makes it reliable: messages are guaranteed to arrive, and to do so in the order they were sent (unless of course the connection breaks).
As with any other TCP connection, we found latency to be an issue if the browser, server or some Internet node in between tries to optimize for bandwidth instead of optimizing for latency. Some clients or servers will employ Nagle's algorithm to maximize the amount of data they send per packet, and this will introduce delays.
The WebSocket API does not allow connecting directly from one browser to another. One must always connect to a WebSocket server.
The long definition process of the specification created an unfortunate situation where different browsers supported different versions of the protocol, and some changed from one version to another. This means that your servers will need to support several versions of the protocol in order to handle all clients that may try to connect.
As part of the online infrastructure for games, Turbulenz provides a multiplayer server for clients to communicate with each other. This is used in the multiplayer version of Score Rush to synchronize up to four players in a single session.
As not all browsers will support WebSockets, there are emulation libraries such as SockJS which implement a similar API using other mechanisms. These other mechanisms generally require specific server-side support, and the latency of the different fallbacks is likely to be higher than when using WebSockets. The Turbulenz plugin provides WebSocket support for browsers that do not natively support the API.
The audio element provides support for loading and playing sound files. It's addition to the HTML standard created a good opportunity for sound in games without using a plugin. Unfortunately, the quality of the early implementations made it unsuitable for anything beyond basic use cases. For example, Firefox did not support the loop property which required code to figure out, via events, when a sound finished in order to restart it. Even then, the silence between the end of the playback and the restart was easily noticed.
Some workarounds required two audio objects created from the same source file, one of them paused and ready to start playing as quickly as possible when the other one finished. This ping-pong between the two sounds performed better than restarting a single one, but added complexity to the code and was a waste of resources for games with complex sound scenes. Other implementations had issues when playing several sounds at the same time, with stuttering and general low quality output. The situation now has improved, but there are still many of the older browsers active in the wild.
The lack of support for a standard file format across all browsers makes the new API difficult to use. Internet Explorer only supports MP3 files, Safari only supports Wav and MP3 files, Firefox only supports Wav and Ogg files. Chrome seems the only browser that supports all three of these formats. This means that in practice sound files should be encoded as both MP3 and Ogg formats to cover all browsers.
The Web Audio API provides better support for high quality sound. Loosely based on OpenAL, it provides accurately timed playback of sound sources, 3D sound, filters and complex channel manipulation. It is a much more suitable solution for games than the audio element, but unfortunately at the time of writing only Chrome supports it.
Unlike OpenAL, the API does not yet support relative 3D sound sources, requiring constant update of the source location to follow the listener around.
Turbulenz has designed a 3D sound API closely following the OpenAL specification and we provide implementation of this API for several different backends: Web Audio, audio element (simulated 3D sound by setting the sound volume based on distance to listener) and if the browser does not provide support for either then our plugin directly implements the API.
The Web Workers API provides support for running scripts in the background, independently of any foreground page script. These background scripts communicate with the foreground one via messages, and the browser will copy the contents of those messages to avoid shared data issues. Therefore sending large amounts of data can have a performance cost. Future browser versions could support a form of zero-copy when using an ArrayBuffer as the message: one script writes to it, the other reads from it.
As described by the API specification, browsers expect workers to be long-lived. They have a high start-up performance cost, and a high per-instance memory cost.
To create a Worker a URL must be specified for the location of the script code the Worker will execute, although there are ways to create Workers from inlined code.
If the browser does not support Web Workers then its functionality will have to be emulated.
Currently only third party browser plugins support this API. None of the browsers support it natively.
The Mouse Lock API overcomes another of the traditional limitations of browser gaming, namely that if the mouse cursor leaves the browser window, the game stops receiving mouse movement events (effectively making it impossible to fully implement traditional first-person shooter camera controls -- the player must keep dragging the mouse back to the browser). This API decouples the visual position of the mouse cursor from the movement information from the mouse hardware.
Security concerns will also limit which code can lock the mouse and when, to avoid malign code controlling the mouse without the user's consent.
Unfortunately, this API still has limited support on modern browsers, and when supported, sometimes it only applies to fullscreen mode.
Support for input events from gaming-specific input devices such as gamepads, joysticks, driving wheels, pedals, and accelerometers is provided by the Gamepad API. The interface is similar to that already provided for keyboard and mouse, but still has only very limited support among modern browsers.
The Turbulenz plugin provides support for input events coming from gamepads and joysticks.
Loading of resources in the browsers requires HTTP requests, and these requests have high overhead. Depending on the browser the cost can be between 300 and 700 bytes per request, and potentially high latency. The latency will increase with the distance to the servers hosting the data, to between tens of milliseconds to hundreds of milliseconds. Browsers do load multiple resources in parallel but they have a fixed limit of how many resources they load at the same time, usually between 4 to 8 resources in parallel. For example, you can request 1000 resources at the same time but only four may be actively downloading in parallel. Data from these resources will be passed to event callbacks serially as it becomes available.
A good way to reduce latency is to generate requests to servers as close as possible to the user. This requires a network of servers distributed across the world that can serve content to users in their region, usually known as a Content Delivery Network (CDN).
Obviously, connection bandwidth will also affect load times. Average download speeds can range from 200 kilobytes per second to 5 megabytes per second depending on the country.
With these limitations there are two critical recommendations:
#1 requires traditional techniques employed when loading from an slow optical medium:
The most common compression format supported by all browsers is gzip. Your servers will need to respond to the request with special HTTP headers to indicate the compression format and the browser will automatically decompress the data before passing it to game code. Although gzip is a standard format, the size of the compressed files will vary significantly from compressor to compressor. We found that 7-Zip generates the smallest files (this tool supports several compression formats but only gzip will work natively on your browser). Remember that every byte counts, not only because when hosting data in the cloud you will pay for the volume of data stored and transferred, but also loading times will suffer with very slow connections.
The second of our recommendations (persuade the browser to cache data) requires also playing with the HTTP header that the server returns with the requested data. Basically the server needs to tell the browser for how long the data is valid and when the browser should check again for an updated version. Of course the browser will still do whatever it wants in many cases. It may decide to cache only really small files or to reserve a very small amount of disk space for the cache, constantly purging and updating it, but most browsers will try to honor the "time to live" information. There are two main ways to tell a browser for how long it should cache your data:
Servers can return both sets of headers for the same file, but we recommend using only the latter because of its simplicity.
The expire or max-age information gets stored per resource, so if the values are too aggressive the browser may not ask for a new version of the file for a long time. If you are updating data or code then your changes may not be reflected for a long time. In the case of both functionality updates and bug fixes this can conflict with the need to deploy new versions as soon as possible. To avoid this issue, resources are usually given unique names. In this way, new resources will be requested by an updated name, which will bypass the existing cache and force a reload of the new data. Unique names are generated either from an incremental version number or the hash of the contents.
At Turbulenz our resources are named with the hash of their contents, and references are translated at runtime from their logical names (e.g. mymesh.dae) to the unique physical ones (e.g. <hash>.dae.json). This allows us to tell the browser to cache the data for 10 years, which can improve loading times dramatically when playing the game for a second time. As updated resources get new unique names, we can release updates almost immediately. Obviously the resource that performs the translation from logical to physical is not cached at all because that information is dynamic.
The AppCache API is worth noting at this point. This allows developers to declare in advance the resources that will be required so the browser can download them ahead of time. The developer has control over what gets cached and what doesn't, and can use this interface to create web applications that work offline.
Sooner or later a game must save data, and the developer must decide where to store it: remotely or locally.
Remote storage allows data to be retrieved later from another machine or another user.
Storing data on your servers requires either an HTTP POST request or, if using WebSockets, a message to the server. In both cases the actual storage will potentially take a long time to happen depending on the amount of data. We have found upload bandwidth a tenth or less of the download bandwidth so saved data will take 10 times longer to upload than to download. Games will have to cope with that latency.
Turbulenz provides services for remote storing and retrieving of different kinds of data:
Depending on the APIs available, the total amount of data that a game can store locally can vary from kilobytes to megabytes. These local storage APIs do not guarantee better performance than loading HTTP resources that are already in the cache.
Traditional methods for storing data locally were severely limited. Cookies, for example, only allowed a handful of kilobytes, and some other methods were not persistent between browsing sessions. Thankfully new APIs have appeared that support data in the order of a megabyte:
To support older browsers you can use libraries like jStorage that provide a consistent storage API, implemented for different back-ends depending on browser support.
Other limitations are related to what resources can be requested from domains other than the one hosting the current page. For example, if a page from domain-a.com makes a request for resources on domain-b.com, the request will fail unless the servers answering the latter request support Cross-Origin Resource Sharing (CORS). If the browser itself does not support CORS, then JSONP can be used, but it also requires support on the server side. This limitation regarding resources in separate domains will have an effect when using a third party CDN to distribute data across the world.
We've implemented the following solutions:
The content of these articles applies to tablets and mobile phones as much as desktops. The main differences between mobile and desktop platforms relate mostly to the lack of the more advanced APIs on smaller devices and obviously the reduction in hardware resources the browser can make available. Namely, less memory and lower CPU and GPU power. Internet connections using 3G also have a much higher latency and lower bandwidth than those on a desktop or laptop.
These limitations often mean that smaller and / or simpler resources are required for a game running on mobile devices compared to the same game running on a desktop or laptop.
Input methods on mobile devices also require special support, touch control does not offer the same response as a mouse and a keyboard. The touch events API provides support for multi-touch events in a similar way to the mouse events but, instead of a single mouse cursor moving around, multiple points are tracked as they move across the screen. You could interpret movement on different regions of the screen as different controls: left side of the screen for translation, right side of the screen for rotation, for example.
The differences listed here represent problems for all games on mobile devices, whether or not they target HTML5 or more platform-specific APIs. However, it is often the case that browsers on mobile systems do not progress as fast as on their desktop counterparts. For example, desktop browsers supported WebGL much earlier than mobile browsers.
Hopefully this short series of articles has given readers new to HTML5 some insight into its potential. As with any platform there are some issues that must be worked around and some tradeoffs to be made, but through the development of the engine and infrastructure for the turbulenz.com game network we have demonstrated that this technology can already deliver a high quality gaming experience direct to a huge audience, with no download or install required by the end user.
With some appropriate fallback strategies, games can already target a large range of browsers, OSs and devices, from desktop to mobile. As more and more OS and application vendors adopt the standards, games written using HTML5 technology will enable high performance and a better user experience without these intermediate solutions.
In this sense, of all modern cross-platform technologies that are viable for games HTML5 is perhaps the most promising. Combined with the connectivity of the web and the appropriate server infrastructure it opens up all kinds of possibilities for creating, promoting and sharing high-quality content in ways that are not possible with traditional platforms.
Return to the full version of this article
Copyright © UBM Tech, All rights reserved