Mobile Game Primer
Until recently, performance of browser-based games for mobile devices has lagged significantly behind desktop performance. If you wanted to create a Canvas-based mobile game, performance was terrible and you needed to build your game around the limitations of the platform. Developing a usable game meant using tricks like scaling up from very low resolutions or doing partial screen updates instead of full refreshes, meaning your game design was limited by the platform’s weaknesses rather than your creativity.
Luckily, this has changed. Browser vendors are rapidly improving their mobile performance:
- Chrome for Android browser is now available on Android 4 (Ice Cream Sandwich)
- Windows Phone 7 Mango launched IE9 mobile with hardware acceleration
All feature a GPU hardware-accelerated HTML5 Canvas element and both feature multi-touch support inside of the browser, a must for any but the simplest games. While Chrome for Android still have some significant work to do on Canvas performance, this now seems like an inevitability rather than a hopeful prayer.
Developing HTML5 games that work in the mobile browser comes with its own special set of requirements. You’ll first need to have an idea of the number of sprites you can push on the screen at once. Next you’ll want to maximize your game to the size of screen so that users don’t have to squint to play your game. Next you’ll need to add touch controls so the game will be playable without a keyboard. Finally you’ll want to consider adding a number of pieces of meta-data to allow your page to be used a home-screen application and have it show up with the right icon.
While mobile HTML5 game performance has increased significantly, you do still need to be aware of the limitations of the two dominant platforms: iOS and Android.
You have two primary options when building an HTML5 game: either build it with Canvas or use CSS sprites. For example, before iOS 5, you’d achieve much better performance in general using CSS sprites, provided you weren’t moving too many of them each frame. This was (and generally still is) the case with Android. Browsers have been optimizing the rendering of HTML elements for a long, so unless you are moving a full page’s worth of tiles and sprites, CSS sprites are still something to consider.
Now with hardware acceleration, Canvas performance has reached a level where where full screen action games at CSS pixel resolution are possible.
The rub is that most of these devices have resolutions that far exceed CSS pixel resolution. In iPhone 4, the resolution of the retina display exposed to web developers was unchanged at 320x480, while the actual screen resolution is 640x960. Since the resolution of the canvas tag is independent from how it’s rendered on the screen, you can create a full resolution canvas, set it to 320x480 CSS pixel dimensions and be pushing 4 times as many pixels through to the display. This will look crisp and nice, but you unless you are doing partial screen refreshes, pumping that many pixels through the Canvas tag is currently out of the reach of HTML5 mobile games.
These limitations means that as of the current generation of devices, you should be targeting a CSS pixel resolution in your game (many native apps still don’t necessarily all have Retina-display scaled assets, so this shouldn’t be viewed as a huge failing).
The good news is that at this resolution you have a little bit of breathing room with how much you render on the screen. I’ve written a simple test script (runnable at http://cykod.github.com/mobile-html5-tests/) that renders a number of moving 30x31 pixels sprites onto an area 320x320 pixels in size using a number of different methods (including CSS sprites and plain Canvas). Running this test script on a iPhone 4, an iPad 2 and a Galaxy Nexus in Chrome for Android results in the following performance for CSS sprites (background images) and Canvas sprites:
All three devices are able to draw over 100 sprites per frame (with a full canvas clear) and render at least 30 fps. While 100 sprites is not a limitless number of sprites, it’s enough for many uses cases. For games with tiled backgrounds, you’ll still want to prerender to offscreen canvas buffers instead of redrawing every tile each frame, but a fullscreen animated game is certainly doable.
Given that every platform can still render double digit frames per second at 1000 sprites per frame, you have some extra leeway to do some significant pre-rendering and offscreen rendering every few frames without grinding your game to a halt. The rest of this article will assume you’re working on a Canvas based game as that’s where the performance boosts are going to be. If you’re working on a CSS sprite-based game, some of the pieces (such as canvas resizing) won’t apply, but much will still be applicable.
Once you’ve got an HTML5 game spec’d out within the performance capabilities of the platform (or are converting an existing game for mobile play) you’ll need to consider how that game will actually look and play on mobile. Many web games on the desktop are generally confined to a play area of a specific size with additional stuff (ads!) wrapping around the play area.
On mobile, this won’t fly. Mobile devices have such limited screen real estate as it is that when a player is playing your game, it behooves you to maximize the play area to the extent that you can. This might seem easy in principle, but since different devices have different resolutions and aspect ratios there’s often only so much you can do.
Your main options are either:
- Resize the canvas to fill the entire screen.
- Resize the canvas to fill majority of the screen, keeping the existing aspect ratio.
Option 2 will give you infinitely less trouble than option 1, as it means your game code doesn’t need to worry about having a different sized playing area depending on the device. On the flip side, it also means you’ll be constraining the viewport of your game smaller than the full size of the device. For some games you’ll want the size and aspect ratio of the game to stay consistent so that enemies appear and disappear in consistent ways. For a 2d platformer, you might consider going with option 1 as adjusting slightly the amount of the level that the player sees while playing isn’t going to dramatically affect gameplay but will make the game look (and play) more like a native game.
Either way, you’ll probably want to pull your game out of its current location on the page and absolutely position it to take up the full size of the screen, hiding anything else on the page. Many HTML5 game engines will do this for you (Impact and Crafty to name a couple), but if you are using your own engine or an engine that doesn’t do this for you, there are a number of issues you’ll want to take into consideration.
The first issue is the browser’s viewport. When faced with a normal, non-mobile-optimized web page, mobile browsers generally allow the user to zoom in and out of the page. To allow for this, the mobile browser pretends it has a screen size that is larger than it actually is (980 pixels wide on iOS and 900 pixels on Android) While this works fine for many websites, it’s a disaster for games. First of all, you don’t want the user zooming in and out of the web page (even if your game supports pinch zooming, you’ll want to handle that in the game itself.) Secondly, you’ll be forcing your game to push more pixels to fill up the screen. Instead, you’ll want to add a meta tag to your page telling the mobile browser to set the width of the screen to the actual (CSS) pixel width of the device.
This limits the width of the width of the screen and prevent the user from zooming in or out.
The next step is to get the game to take up as much screen real estate as possible. Your first job is to get rid of the pesky address bar. This can usually be done by calling window.scrollTo(0,1); on the iPhone (more on Android below). There are a couple of caveats to this though. For the address bar to disappear you need to have a page that’s longer than the screen. Let’s assume you have your canvas element set up inside another container along the lines of the following HTML:
We can now start to put together a setupMobileFull method that will set up the page with a resized canvas. This method will follow option 1 described above, maximizing the canvas to the full size of the page.
You can explicitly set the height of the containing container element to a height that’s larger than the size of the page (the example below uses jQuery for succinctness):
Next you’ll need to regrab the height of the page as it actually changes after the call to window.scrollTo. Because of the scrollTo command, the height of the page actually needs to be a couple of pixels larger than the page itself to prevent a gap at the bottom of the page. You’ll also want to reset the container to the height and width of the page to prevent any scrolling on orientation changes. If you had any padding or margin on the container for the desktop versions, this is also a good time to remove it.
Next you can set the width and height properties on the canvas to resize it to match the window and then set the position to absolute and place it in the top left of the page.
If you run setupMobileFull() on most mobile devices, you should now have a resized canvas that fills up whatever mobile device you run it on.
For many games, resizing the canvas itself isn’t an option. To get around this you can also rescale the CSS size of the canvas by setting the CSS width and height instead of the width and height attributes on the Canvas tag itself. The downside to this is that you’ll take a slight performance hit as the browser needs to rescale the canvas to draw it on the screen, but with GPU accelerated canvas, this shouldn’t be noticeable.
Let’s write a function called setupProportionalCanvas() that does just that. The beginning of the function is identical as we still want to get rid of the address bar.
However, when it comes time to set the canvas, you’ll instead need to calculate the limiting dimension and then maximize the canvas size based on that.
This will center the canvas horizontally on the screen (if necessary) and scale it via CSS to take up as much of the screen as possible. You can try this out on your device by pulling up resize-test.html on your device.
A real game might use a combination of the two options depending on the pixel size of the device it’s running on to prevent the devices from having to push too many pixels per frame.
It’s important to note that the simple code to scroll away the address bar is iPhone specific. To achieve the same thing on Android (where possible) is a good deal more work when it’s possible. You’ll want to check out a pre-built tool like Zynga’s viewporter if you really want get the address bar out of the way.
Next up is handling the addition of touch controls to the page. This is something that should generally only be done when the game supports touch events, which you can check by looking at the window object for the ontouchstart property.
You can also check Modernizr.touch if you’ve got Modernizr loaded.
Mobile devices support the standard click event but this should generally be avoided on touch devices even for game interface elements as there’s a significant delay (300ms-500ms) before triggering a click event that mobile browsers add to prevent accidental clicks. Most users will be used to having an immediate response when clicking on an interface element in a game (as opposed to on a web page) and leaving that delay in there will make your game feel sluggish.
You can use the existing mouse events (mousedown, mousemove, mouseup) on mobile browsers, but these don’t provide any support for multitouch.
A better option is to use the four touch events (touchstart, touchmove, and touchend, touchcancel) which are supported on iOS and Android 2.1+. Responding to the touch events (especially touchmove) and making sure to call event.preventDefault will also prevent normal browser behavior, like zooming and scrolling, that would disrupt your game.
For interface items such as menu items, tracking touchstart and touchend instead of click means you elements will be more responsive. For example, to track a set of DOM based interface elements set up as links inside of a <div> with an id of “interface”, you could do the following to get the normal interface usage pattern:
The point of this code is to fake hover type events on interface elements without actually triggering an element until the users release their touch. To achieve this, the touchmove and touchend events track the actual element that’s under the touch, and if that element is different than the currentElement being “hovered” over discard the touch. The little used document.elementFromPoint(x,y) will return the DOM element that’s actually at the mouse location of the passed in clientX and clientY coordinates. The changedTouches array used above is described in more detail below. You can give the code a shot at touch-test.html.
This code will allow the user to press a game interface element (like the start button on a game menu screen), release it as quickly as possible, and still generate an event. Similarly if the user moves their finger off the interface element without lifting it up, the event won’t trigger.
You can also add a standard hover psudeo-class CSS along with a hover class to your element:
This way you’ll be able to get the best of both worlds by handling the hover state correctly on both desktop and mobile browsers. The advantage here is that you can still use a standard click handler for the desktop and you won’t need to remove it on mobile for the devices that don’t support touch.
Moving on from menu-screen type interface elements to in-game controls, there are four basic controls schemes for touch games:
- Direct interaction - you touch the element you want to interact with (such as a small, perturbed bird) and move it directly.
- Control buttons - the equivalent of keyboard keys placed on the screen.
- Analog controls - an analog-style joystick added to the screen.
- A combination of 2 & 3.
In each of these cases you’ll want to use special features of the touch events to have the onscreen controls work effectively. HTML5 Rocks has a good in-depth introduction to touch events, but the main advantage of touch events over their corresponding mouse events is that you get three additional arrays of data:
- event.touches - all touches on the screen
- event.targetTouches - touches on the element that triggered the event
- event.changedTouches - touches that were affected because of this event
Inside of each of these lists are a number of useful pieces of information, the most valuable of which for our perspective are:
- touch.identifier - a unique ID for the current finger touch
- touch.target - the DOM element touched
- touch.pageX and touch.pageY - the location on the page where the element was touched
Using these events with jQuery requires the extra step of pulling them out of the originalEvent property of the passed-in normalized event that jQuery passes into its event handlers. If you try to grab any of the aforementioned arrays of touches from the normalized event you’ll be met with frustration.
In each case you’ll want to capture the location on the Canvas tag. This can be complicated by scaling and resizing and the position of the Canvas tag on the page (these calculations can be simplified if you aren’t resizing) but it comes down to:
Broken down into real code, given a touch (such as an element of changedTouches) and an element, you can calculate the pixel location on the canvas like so:
Note that this code assumes the Canvas tag itself doesn’t have a transformation matrix of any sort applied via a translate, scale, rotation or other transform. If it does then your direct manipulation code will need to adjust based on whatever transformation you’ve added to get a valid canvas x and y location.
Let’s try out the first type, direct interaction, with a demo of dragging dragging circles via multitouch based on the following array of items (which will be displayed as circles) and a simple collision method as below:
The placeholder collision code above will check for a collision between a point and a circle. Your engine or game would substitute in whatever code or actual collision check against game entities you want to include.
When a touch is started, check if any changedTouches (we know these are only new touches because it’s a touchstart event) are touching an element. If so, save the touch identifier of the drag along with the original location of the element and the location of the drag.
The touchmove event has the job of tracking each of the changedTouches again and matching those changedTouches against any elements that are already being dragged based on their touch identifier. To do this, we need to loop through the curDrags array, find if any of the existing matching elements are being dragged by a touch with the appropriate identifier and move that element appropriately:
Finally, the touchEnd event needs to find the touch that is ending (again by looking at changedTouches) and remove that element from the elements currently being dragged. You can pull up the full example in drag.html on your phone. iOS devices and Chrome for Android ICS support multi-touch, which means you can drag multiple elements at a time, while the default Android browser only supports a single touch.
The second type of input, onscreen keys, is the easiest, but it’s also got a couple of tricks to it. The goal is to add onscreen buttons, often a set of left and right arrows on the left side and a couple of buttons on the right. Once again, multitouch is preferred as the user may be mashing the left right arrow keys at the same time they are pressing the A or B buttons. The easiest way to achieve this is to keep a list of buttons and every time there is a touch event set all the keys to false, then loop through all the active touches on the target element in event.targetTouches and turn those buttons back on. This allows players to move their fingers back and forth on the device without having to lift them up to still be able to press the buttons.
Since we are looking just at targetTouches, which are available in all touch events and contains the touches currently on the bound element, we can use 1 handler for all touch events as below:
Check out keys.html on your phone for an example of this in action. You’ll notice the keys appear as small buttons on the bottom of the page but their hit area is actually a vertical column that goes up and down the page. This allows players with less nimble fingers to still easily hit the individual buttons.
The same rules apply as with dragging: on iOS and Chrome for Android, multi-touch is enabled so you can press multiple buttons at once while on older versions of Android you’ll be limited to a single button press at a time, but users will still be able to play your game.
Finally, the last input method is to add an analog stick to your game. There are a couple of ways to do this. The first is to add a single visible control onto the page with a fixed center. The second way is to center a joystick style control wherever a user first touches the device. Both mechanisms work, while the second is a little more flexible.
The main idea in the code is to lay down a joystick center on touchstart when the user touches the screen:
Once the joypadTouch variable is bound to the identifier, any further touch events can move the joystick center, up to a maximum. We can use the Pythagorean theorem to calculate the distance form the center and the inverse tangent Math.atan2 method to grab the angle of the joypad:
To prevent the joypad from moving too far away from the center, the code puts a limit on the total distance and moves the joystick to the edge of the center circle if necessary.
Finally, on touchend we just set joypadTouch to null if the changed touch matches the identifier of the touch that we’ve been using as the touch pad.
Try out joystick.html on your touch device for a full working example.
Keys and an analog joystick be mixed and matched, adding, for example, a set of buttons on the right and a joypad on the left. See joykeys.html for the two working together.
Sound is, unfortunately, the current Achilles heel of web-based HTML5 gaming. The restrictions on sound on iOS devices inside Safari are severe: only 1 channel of sound at a time, no preload, and all sounds must be initiated by user action. On Android, the restrictions are the same except for necessarily being limited to a single channel but this depends on the device and build of Android you are on. Android devices that support Flash can use that as a fallback.
What it all comes down to is that HTML5 audio on mobile is an unfriendly landscape you don’t want to try to tackle alone. You best bet for web-based games is to use Zynga’s open-source Jukebox software that is designed to provide a cross-platform solution to all of these issues.
If you are planning on dropping you game in an App store, you can use something like AppMobi’s multiSound API, which is part of their directCanvas suite. Both are open-source and up on GitHub (iOS, Android).
Extra iOS Home Screen Bling
Some additional things you can do to make playing your game a more seamless experience on mobile is to add a few pieces of meta-information to the head of your document to make it run more like a normal app on iOS Devices when added to the home screen. Apple gives you four meta-tags that do this.
The first tag indicates that your game is happy running as an “App” from the home screen. This means that if a user selects “Add to home screen” and then runs you app from their home screen it will show up maximized without an address bar or footer navigation icons. Apple refers to this as being “mobile web app capable” and all you need to do is add the following meta tag to your document <head>:
This would not have been recommend for games pre-iOS 5, but iOS 5 finally brought Safari’s Nitro JIT to homescreen-launched apps, so there’s no downside to your user running your game from their home screen and they’ll get more screen real estate to play.
The second tag you can use is one that adjusts the status bar at the top of the browser. While you can’t get rid of it, you do have two options besides the default gray: you can either make it black or make it black and translucent. Using the latter will open up the full size of the device to your game, even if the top few pixels are covered by a semi-translucent black bar.
Since black is the normal color for native apps that leave the status bar visible, setting the bar to black instead of the default safari gray will give your game a more native feel.
There are two final touches that will make your game look and behave more like a native app. The first is the ability to add a custom square launch icon with:
This works where launch-icon.png is a square 57x57 icon without any iOS gloss (iOS will add the gloss for you). If you already have an icon with rounded corners you can also use the precomposed version:
You can also specify three different sizes to handle newer iPhones, iPads and older iPhones (as well as Android 2.1+ thrown in for free)
The final piece you can add in is a startup image that is displayed while the browser instance loads. The default image should be 320x460 pixels in a portrait orientation and is added with:
If you want to add in custom sized images for every iOS screen size known to humankind you can do that using media queries as of iOS 5, but you’ll need to have the exact right images of the right sizes. See the head of startup-images.html for an example of setting up these meta tags. Note: the order in this case does matter! You can click the “Add to Home Screen” button on your device to see the launch icon and startup images.
Wrapping up and further enhancements
About the author
Pascal Rettig is the co-owner and lead developer for Cykod. He writes about issues relating to Ruby on Rails Development and Web Consulting in general. He is the author of Wiley's Professional HTML5 Mobile Game Development