• 15.1 Run Snail Bait on Mobile Devices
• 15.3 Scale Games to Fit Mobile Devices
• 15.4 Change Instructions Underneath the Game’s Canvas
• 15.5 Change the Welcome Screen
• 15.6 Incorporate Touch Events
• 15.7 Work Around Sound Idiosyncracies on Mobile Devices
• 15.8 Add an Icon to the Home Screen and Run Without Browser Chrome
As this book went to press in early 2014, Gartner—a highly respected technology research and advisory company—predicted a staggering 2 billion mobile phones and tablets would be sold in that year alone. By comparison, Gartner expected that around 280,000 desktop-based PCs and notebooks would be sold. That’s about 7,100 mobile devices for every PC and notebook, which is a compelling argument for making sure your game runs on mobile devices.
First, the good news. As of early 2014, lots of mobile devices can run HTML5 games such as Snail Bait at a rock-solid 60 frames per second. And players can add HTML5 applications to their home screens on iOS and Android, which means those applications open without browser chrome such as the address bar, making your games virtually indistinguishable from native games.
The bad news is that HTML5 games are not playable on less powerful devices, such as vintage 2010 iPods running iOS 4.X, or a first-generation Samsung Galaxy S3. And then there’s HTML5 audio, which has been one of the biggest challenges for browser developers to implement reliably. It’s not until recently, with the advent of iOS 6, that audio works reliably on iOS devices, but as you’ll see in Section 15.7, “Work Around Sound Idiosyncracies on Mobile Devices,” on p. 408, both iOS 7 and Android 4.X sound support still require workarounds.
There is not a specific version of Snail Bait for mobile devices; instead, the game detects when it’s running on a mobile device and configures itself appropriately. In this chapter we discuss how Snail Bait does that. Specifically, this chapter covers the following topics:
• Detect mobile devices (Section 15.2 on p. 376)
• Scale games to fit mobile device screens (Section 15.3 on p. 377)
• Change instructions when games run on mobile devices (Section 15.4 on p. 389)
• Implement alternative user interface controls (Section 15.5 on p. 391)
• Incorporate touch events (Section 15.6 on p. 405)
• Prevent zooming and inadvertent dragging during gameplay, but allow it otherwise (Section 15.6 on p. 405)
• Work around sound idiosyncracies on mobile devices (Section 15.7 on p. 408)
• Run your app like a native app on iOS and Android (Section 15.8 on p. 409)
You can find the online example for this chapter at corehtml5games.com/book/code/mobile.
You can remotely debug your game on Mac OS X. As shown in Figure 15.1, with your device connected via USB to your Mac, Safari adds your device to the Develop menu. After selecting the application to remotely debug, Safari opens a debugger window, as Figure 15.1 also illustrates. See http://bit.ly/1i954dV for explicit instructions on how to set up remote debugging on Safari.
Note: To enable the Develop menu in Safari, go to the Advanced panel in Safari preferences and check the checkbox labeled Show Develop menu in menu bar.
You can also remotely debug Android applications. See https://developers.google.com/chrome-developer-tools/docs/remote-debugging?hl=de for more information.
Snail Bait runs on mobile devices, as you can see in Figure 15.2, which shows Snail Bait running on an iPad and a Google Nexus 5.
Table 15.1 shows the results of Snail Bait running on various mobile devices and operating systems.
As you would expect, the newer the mobile device and its operating system, the better Snail Bait performs.
Next, let’s see how the game detects mobile devices.
Because of alternative input controls and lack of a keyboard on most mobile devices, HTML5 games that run on both the desktop and mobile devices typically implement some code that runs on mobile devices only. For example, instead of controlling games with keyboards and mice, players on mobile devices use their fingers, which requires mobile-only code to detect and react to touch events.
The first order of business when tailoring your game to mobile devices is to determine whether the game is running on the desktop or on a mobile device. Example 15.1 shows the code that Snail Bait uses to make that determination.
SnailBait.prototype = {
...
detectMobile: function () {
snailBait.mobile = 'ontouchstart' in window;
},
...
};
The preceding method sets a snailBait.mobile
Boolean variable to true
if the window
object contains an ontouchstart()
method; otherwise, snailBait.mobile
is false
. Snail Bait invokes the detectMobile()
method when the game starts and subsequently uses the snailBait.mobile
variable to implement mobile-specific code, as shown in Example 15.2.
snailBait = new SnailBait();
...
snailBait.detectMobile();
if (snailBait.mobile) {
// Mobile-specific code goes here, such as touch
// event handlers.
}
Now that you’ve seen how to detect features corresponding to mobile devices, let’s see how to scale HTML5 games to snugly fit their display.
You can read more about determining whether a JavaScript object has a particular property or method at www.nczonline.net/blog/2010/07/27/determining-if-an-object-property-exists.
In simpler times, when mobile devices were still novelties, developers detected mobile devices with media queries to determine screen size. If a media query revealed that a game was running on a device with 320 x 480 pixels, for example, the device was an iPhone.
Today, there are many types of mobile devices; in fact, the line between computer and mobile device itself is significantly blurred. Feature detection, which probes APIs instead of hardware, is a better choice than media queries for detecting capabilities that browsers provide.
Other than Node.js and socket.io, which Snail Bait uses to communicate between the client and server, the game eschews frameworks and libraries altogether, so you can learn how to develop your own HTML5 games entirely from scratch.
When you implement your own game, however, you should take full advantage of frameworks and libraries as you see fit. One useful library is Modernizr, which performs all sorts of feature detection in a manner similar to the feature detection discussed in this section.
When Snail Bait runs on mobile devices, the browser scales the game so that it fits snugly, as shown in Figure 15.3, which shows the game running on Android in the Chrome browser.
Players can push the address bar up and out of the way to run full-screen, as shown in Figure 15.4. To start the game in full-screen mode, players can add Snail Bait to their home screen, as discussed in Section 15.8, “Add an Icon to the Home Screen and Run Without Browser Chrome,” on p. 409.
As you can see from Figure 15.3 and Figure 15.4, Snail Bait programatically resizes the game when the amount of available screen real estate changes.
Players can also pinch and zoom as they desire; for example, players can zoom in and concentrate entirely on the action if they wish, leaving the score and instructions behind as shown in Figure 15.5.
Once gameplay starts, Snail Bait prevents taps in the game’s canvas from zooming the display. See Section 15.6, “Incorporate Touch Events,” on p. 405 to see how Snail Bait disallows pinching and zooming.
Now that you’ve seen how Snail Bait scales to fit mobile device screens, let’s see how it implements that functionality with the viewport
meta tag and programatic resizing.
viewport
Meta TagThere’s a lot more available screen real estate on desktops than on small mobile devices, as Figure 15.6 illustrates.
Because webpages meant for the desktop are too big for small mobile devices, mobile device browsers scale webpages to fit the screen, as shown in Figure 15.7.
When browsers display a webpage, they do not draw directly into the mobile device’s screen, as you might expect. Instead, the browser draws into an off-screen viewport and subsequently copies the contents of the off-screen viewport to the screen. The off-screen viewport is known as the layout viewport because that’s where the browser lays out the webpage, and the screen is known as the visible viewport. When the browser copies the contents of the layout viewport to the visible viewport, it scales the contents of the layout viewport to fit the visible viewport.
Layout viewports have different widths on different mobile operating systems, but for the most part they are close to the average width for a window on the desktop, meaning somewhere between 800 and 1,000 pixels. The layout viewport on iOS, for example, is 980 pixels wide, whereas it’s 800 pixels on Android. That correlation to desktop window size means that on mobile devices, CSS machinations result in websites that look proportionally similar to what you see on the desktop.
You can set the width of the layout viewport with the viewport
meta tag, originally introduced by Apple. Figure 15.8 shows the effects of setting the width of Snail Bait’s layout viewport to 800, 900, and 2000 pixels, respectively, on an iPad.
The width of Snail Bait’s canvas is 800 pixels. When the layout viewport’s width is also 800 pixels, the game fits snugly in the horizontal direction, as in the top screenshot in Figure 15.8.
When the layout viewport’s width is 900 pixels, there are 50 pixels on either side of the canvas, as you can see in the middle screenshot in Figure 15.8. When the layout viewport’s width is 2000 pixels, there are 600 pixels on either side of the canvas, as you can see in the bottom screenshot in Figure 15.8.
Snail Bait chooses a layout viewport width of 900 pixels, as shown in Example 15.3.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=900"/>
...
</head>
</html>
...
</html>
The viewport
meta tag goes in the <head>
section of your HTML page, as the preceding listing shows. Snail Bait uses only the width
directive, but the viewport
tag has several other directives, all of which are listed in Table 15.2.
You can combine the preceding directives; for example, you could prevent browsers from scaling when they copy the contents of the layout viewport to the visible viewport, like this: <meta name="viewport" width="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">.
Now that you’ve seen how Snail Bait uses the viewport
meta tag to fit mobile device screens horizontally, let’s see how it fits the game vertically.
A device’s screen has a fixed number of pixels. For example, Google’s Nexus 5 has a resolution of 1080 x 1920 pixels. Those pixels are known as device pixels. In the previous discussion, when we talk about setting the viewport
meta tag’s width to a certain number of pixels, we’re not talking about device pixels. Instead, the previous discussion refers to CSS pixels, meaning the number of pixels you specify in CSS for the sizes of HTML elements. High-resolution devices like the Nexus 5 use multiple device pixels to represent a single CSS pixel.
Given the width you specify for the layout viewport with the viewport
meta tag, the browser infers the layout viewport’s height by maintaining the aspect ratio (width/height) of the webpage. The browser also infers the values of directives you don’t specify directly, so if you’re not getting the results you expect, specify more directives directly.
You can find many recommendations on the Internet for the following use of the viewport
tag: <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">.
That use of the tag sets the width of the layout viewport to the width of the physical device. In addition, the preceding use of the tag disallows scaling and zooming.
Snail Bait opts for a simpler use of the viewport
meta tag because the game conditionally allows dragging and zooming and because it adds breathing room on either side of the game’s canvas.
When this book went to press, browser implementations of the viewport
meta tag were a mess, especially for Android WebKit. For example, even though you should be able to set the height of the viewport instead of the width, no browsers implement it correctly, even on iOS. Also, iOS pays attention to the initial-scale
directive only when a page loads initially; as a result, if the user rotates the device from portrait to landscape mode, the page puffs up to 1.5 times its original size. Android WebKit pays attention to initial-scale
only if the value is 1
and there is no width
directive. And that’s just the beginning of the problems with the viewport
meta tag.
You can read more about the viewport
meta tag at www.quirksmode.org/mobile/metaviewport and http://tech.bluesmoon.info/2011/01/device-width-and-how-not-to-hate-your.html.
Instead of a meta tag, the W3C has specified a @viewport
rule for CSS that provides functionality similar to the viewport
meta tag; however, as this book went to press, the @viewport
rule was not well supported among mobile browsers. You can read more about CSS Device Adaption at www.w3.org/TR/css-device-adapt.
Because aspect ratios differ among mobile device displays, it’s not enough to merely use the viewport
meta tag as discussed above to fit HTML5 games snugly in the horizontal direction. If the aspect ratio of a mobile device differs enough from your game’s aspect ratio, the game will be cropped vertically, as illustrated in the top screenshot Figure 15.9.
To avoid aspect ratio cropping so the game is displayed as shown in the bottom screenshot in Figure 15.9, Snail Bait resizes the game to fit the screen at startup. Subsequently, Snail Bait also resizes the game whenever the window’s size changes (such as when the player pushes the address bar out of the way) or when the player changes the device’s orientation. Example 15.4 shows how Snail Bait does that.
snailBait.detectMobile();
if (snailBait.mobile) {
...
snailBait.fitScreen();
window.addEventListener("resize", snailBait.fitScreen);
window.addEventListener("orientationchange", snailBait.fitScreen);
}
Snail Bait’s fitScreen()
method fits the game to mobile device screens when the game starts. Subsequently, Snail Bait specifies that method as a callback for the window’s resize
and orientationchange
events. The fitScreen()
method is listed in Example 15.5.
SnailBait.prototype = {
...
fitScreen: function () {
var arenaSize = snailBait.calculateArenaSize(
snailBait.getViewportSize());
snailBait.resizeElementsToFitScreen(arenaSize.width,
arenaSize.height);
},
...
};
The fitScreen()
method gets the size of the visible viewport, which it uses to calculate the size of the game’s arena. Finally, fitScreen()
resizes some of the game’s HTML elements to fit the arena.
The getViewportSize()
method is listed in Example 15.6.
Using document.documentElement
is the preferred method of getting the width and height of the visible viewport (clientWidth
and clientHeight
), but if that’s not available, getViewportSize()
falls back to the inner width and height of the window.
SnailBait.prototype = {
...
getViewportSize: function () {
return {
width: Math.max(document.documentElement.clientWidth ||
window.innerWidth || 0),
height: Math.max(document.documentElement.clientHeight ||
window.innerHeight || 0)
};
},
...
};
The most interesting aspect of fitting Snail Bait to mobile device screens is the calculateArenaSize()
method, listed in Example 15.7.
SnailBait.prototype = {
...
calculateArenaSize: function (viewportSize) {
var DESKTOP_ARENA_WIDTH = 800, // Pixels
DESKTOP_ARENA_HEIGHT = 520, // Pixels
arenaHeight,
arenaWidth;
arenaHeight = viewportSize.width *
(DESKTOP_ARENA_HEIGHT / DESKTOP_ARENA_WIDTH);
if (arenaHeight < viewportSize.height) { // Height fits
arenaWidth = viewportSize.width; // Set width
}
else { // Height doesn't fit
arenaHeight = viewportSize.height; // Set height
arenaWidth = arenaHeight * // Calculate width
(DESKTOP_ARENA_WIDTH / DESKTOP_ARENA_HEIGHT);
}
if (arenaWidth > DESKTOP_ARENA_WIDTH) { // Too wide
arenaWidth = DESKTOP_ARENA_WIDTH; // Limit width
}
if (arenaHeight > DESKTOP_ARENA_HEIGHT) { // Too tall
arenaHeight = DESKTOP_ARENA_HEIGHT; // Limit height
}
return {
width: arenaWidth,
height: arenaHeight
};
},
...
};
On the desktop, Snail Bait’s arena, which contains all the game’s HTML elements, is 800 pixels wide and 520 pixels high. The calculateArenaSize()
method recalculates those values depending on the aspect ratio of the mobile device. The method returns an object with width
and height
properties that represent the width and height of the arena.
The fitScreen()
method, listed in Example 15.5, passes the arena’s width and height to resizeElementsToFitScreen(),
which is listed in Example 15.8.
SnailBait.prototype = {
...
resizeElementsToFitScreen: function (arenaWidth, arenaHeight) {
snailBait.resizeElement(
document.getElementById('snailbait-arena'),
arenaWidth,
arenaHeight
);
snailBait.resizeElement(snailBait.mobileWelcomeToast,
arenaWidth, arenaHeight);
snailBait.resizeElement(snailBait.mobileStartToast,
arenaWidth, arenaHeight);
},
...
};
The resizeElementsToFitScreen()
method resizes the arena, along with mobile toasts. Those toasts are discussed in Section 15.5, “Change the Welcome Screen,” on p. 391.
Finally, resizeElementsToFitScreen()
invokes Snail Bait’s resizeElement()
method, listed in Example 15.9.
SnailBait.prototype = {
...
resizeElement: function (element, w, h) {
element.style.width = w + 'px';
element.style.height = h + 'px';
},
...
};
The resizeElement()
method changes the width
and height
properties of an HTML element’s style
object. It’s important to remember to add the px string to the values for width and height; without it, browsers ignore the value.
Now that you’ve seen how Snail Bait combines the viewport
meta tag with resizing the game’s arena in JavaScript, let’s see how the game changes instructions when it runs on mobile devices.
Snail Bait uses the viewport
meta tag to fit the game initially in the horizontal direction. Subsequently, it resizes the game to fit the screen in both the vertical and horizontal directions.
On the desktop, Snail Bait shows the instructions in the top screenshot in Figure 15.10. For mobile devices, Snail Bait changes those instructions to the ones shown in the bottom screenshot in Figure 15.10. Those instructions, unlike the instructions at the beginning of the game, are displayed all the time beneath the game’s canvas.
The HTML for the mobile instructions is shown in Example 15.10.
<div id='snailbait-mobile-instructions'>
<div class='snailbait-keys'>
Left
<div class='snailbait-explanation'>Run left or jump</div>
</div>
<div class='snailbait-keys'>
Right
<div class='snailbait-explanation'>Run right or jump</div>
</div>
</div>
At startup on mobile devices, Snail Bait sets its instructionsElement
property to the HTML element listed above, as in Example 15.11. That assignment cancels a previous assignment of the instructionsElement
to the desktop instructions.
if (snailBait.mobile) {
// The following line of code overrides the instructionsElement
snailBait.instructionsElement =
document.getElementById('snailbait-mobile-instructions');
...
}
Now that you’ve seen how to change the instructions under the game, let’s see how Snail Bait modifies the game’s initial toasts for mobile devices.
Implementing support for touch events is the most obvious aspect of customizing games for mobile devices. Often, that support results in different instructions for the game. In Section 15.6, “Incorporate Touch Events,” on p. 405 we discuss the implementation of Snail Bait’s support for touch events; in this section, we discuss the implementation of Snail Bait’s instructions for mobile devices.
Recall from Chapter 1 that when Snail Bait runs on the desktop it initially displays a brief toast, as shown in Figure 15.11. The game’s instructions showing which keys control the runner are always displayed beneath the game’s canvas.
When the game runs on a mobile device, Snail Bait modifies the game’s initial toast as in the top screenshot in Figure 15.12. If the player taps on the Show how to use the controls link, Snail Bait displays a second toast, shown in the bottom screenshot in Figure 15.12, that describes the game’s controls.
If players have played the game before, they probably don’t want to see the instructions again, so the welcome toast gives the player the option to immediately start the game, as shown in the top screenshot in Figure 15.12.
Implementing the toasts shown in Figure 15.12 involves the following steps:
• Implement a welcome toast (shown in the top screenshot in Figure 15.12)
• Implement an instructions toast (shown in the bottom screenshot in Figure 15.12) with a Start link that starts the game
We implement the welcome toast shown in the top screenshot in Figure 15.12 with the following steps:
• Modify the game’s start sequence
• Add HTML for the welcome toast
• Define CSS for the welcome toast
• Implement event handlers for the welcome toast’s links
When Snail Bait runs on the desktop, the game’s startGame()
method starts the game’s animation and displays the desktop welcome toast. We modify that method as shown in Example 15.12 to fade in the mobile welcome toast when the game is running on a mobile device instead of the desktop welcome toast.
var SnailBait = function () {
...
this.mobileWelcomeToast =
document.getElementById('snailbait-mobile-welcome-toast'),
...
};
SnailBait.prototype = function () {
...
startGame: function () {
...
if (snailBait.mobile) {
this.fadeInElements(snailBait.mobileWelcomeToast);
}
else {
this.revealInitialToast();
this.playing = true;
}
...
requestNextAnimationFrame(this.animate);
},
...
};
Before it returns, the startGame()
method invokes requestNextAnimationFrame(),
the polyfill for requestAnimationFrame()
discussed in Chapter 3, to kick off the game’s animation. The screenshots in Figure 15.12 don’t show the effect, but underneath the partially transparent toast a smoking hole smokes, and the coins and jewels sparkle and bob up and down.
The mobile welcome toast is a DIV element whose identifier is snailbait-mobile-welcome-toast
, as you can see from Example 15.13. That DIV contains the brief message and two links shown in the top screenshot in Figure 15.12.
<!DOCTYPE html>
<html>
...
<body>
...
<!-- Arena.....................................................-->
<div id='arena'>
...
<!-- mobile devices only -->
<div id='snailbait-mobile-welcome-toast'
class='snailbait-mobile-toast'>
<div id='snailbait-welcome'>
Capture coins and jewels. Avoid bees and bats.
<div id='snailbait-welcome-instructions'>
<p><a href='#' class='snailbait-welcome-link'
id='snailbait-start-link'>
Start the game
</a>
</p>
<p><a href='#' class='snailbait-welcome-link'
id='snailbait-show-how-link'>
Show how to use the controls
</a>
</p>
</div>
</div>
</div>
...
</div>
...
</body>
</html>
The preceding HTML is unremarkable, other than the snailbait-mobile-welcome-toast
DIV’s CSS class. Let’s look at that CSS next.
Snail Bait has one other toast for mobile devices in addition to the welcome toast listed in Example 15.13. That other toast, which contains a lone Start link, is shown in the bottom screenshot in Figure 15.12. The welcome and start toasts have several CSS properties in common, so they reside in a CSS class, listed in Example 15.14.
.snailbait-mobile-toast {
position: absolute;
border-radius: 10px;
font: 30px fantasy;
text-align: center;
text-shadow: 1px 1px 1px rgb(0,0,0);
color: yellow;
-webkit-transition: opacity 1s;
-moz-transition: opacity 1s;
-o-transition: opacity 1s;
transition: opacity 1s;
display: none;
z-index: 1;
opacity: 0;
}
The preceding CSS exhibits the familiar pattern of HTML elements that Snail Bait fades in and out. The elements that have the snailbait-mobile-toast
class are initially invisible and have a transition associated with their opacity
property. See Chapter 5 for more information on how Snail Bait fades elements in and out.
Example 15.15 lists the CSS for the snailbait-mobile-welcome-toast
element.
#snailbait-mobile-start-toast {
width: 800px;
height: 400px;
}
The preceding CSS positions sizes the snailbait-mobile-start-toast
element to exactly fit the game’s canvas. Next, we look at the event handlers for the links in the mobile welcome toast.
When the player taps the mobile welcome toast’s Start the game link, the browser invokes the event handler listed in Example 15.16.
snailBait.startLink.addEventListener(
'click',
function (e) {
var FADE_DURATION = 1000;
snailBait.playSound(snailBait.coinSound);
snailBait.fadeOutElements(snailBait.mobileWelcomeToast,
FADE_DURATION);
snailBait.playing = true;
}
);
The browser detects click events when players either click links with the mouse or tap them with a finger. The preceding click event handler plays a sound, fades out the initial start toast, and sets the game’s playing
property to true
.
If the player taps the Show how to use the controls link, the browser invokes the event handler shown in Example 15.17.
snailBait.showHowLink.addEventListener(
'click',
function (e) {
var FADE_DURATION = 1000;
snailBait.fadeOutElements(snailBait.mobileWelcomeToast,
FADE_DURATION);
snailBait.drawMobileInstructions();
snailBait.revealMobileStartToast();
snailBait.mobileInstructionsVisible = true;
}
);
The preceding event handler fades out the welcome toast and subsequently draws the mobile instructions and reveals the mobile Start toast. The event handler also sets a flag named mobileInstructionsVisible
that Snail Bait uses to determine when to draw the game’s mobile instructions.
Snail Bait draws instructions for mobile devices, shown in Figure 15.13, with a drawMobileInstructions()
method. That method is listed in Example 15.18.
SnailBait.prototype = {
...
drawMobileInstructions: function () {
var TOP_LINE_OFFSET = 115,
LINE_HEIGHT = 40;
this.context.save();
this.initializeContextForMobileInstructions();
this.drawMobileDivider(cw, ch);
this.drawMobileInstructionsLeft(this.canvas.width,
this.canvas.height,
TOP_LINE_OFFSET,
LINE_HEIGHT);
this.drawMobileInstructionsRight(this.canvas.width,
this.canvas.height,
TOP_LINE_OFFSET,
LINE_HEIGHT);
this.context.restore();
},
...
};
The drawMobileInstructions()
method saves and restores the context for the game’s canvas, so context settings made between those two calls are temporary. See Chapter 3 for more information about the context’s save()
and restore()
methods.
The drawMobileInstructions()
method initializes the context for the game’s canvas with an initializeContextForMobileInstructions()
method, which is listed in Example 15.19.
SnailBait.prototype = {
...
initializeContextForMobileInstructions: function () {
this.context.textAlign = 'center';
this.context.textBaseline = 'middle';
this.context.font = '26px fantasy';
this.context.shadowBlur = 2;
this.context.shadowOffsetX = 2;
this.context.shadowOffsetY = 2;
this.context.shadowColor = 'rgb(0,0,0)';
this.context.fillStyle = 'yellow';
this.context.strokeStyle = 'yellow';
},
...
};
As discussed in Chapter 3, the HTML5 canvas context has properties that control certain drawing aspects, such as where to place text and whether or not to add shadows to graphics and text.
The initializeContextForMobileInstructions()
method listed above sets the context’s textAlign
and textBaseline
properties so the context’s fillText()
method draws text centered horizontally. The initializeContextForMobileInstructions()
method also sets a fantasy font that’s 26 pixels high and black shadow for text and graphics. Finally, the method sets the context’s fill and stroke styles to yellow.
After initializing context properties, Snail Bait draws the vertical divider in the center of the game’s canvas with the drawMobileDivider()
method, listed in Example 15.20.
SnailBait.prototype = {
...
drawMobileDivider: function (cw, ch) {
this.context.beginPath();
this.context.moveTo(cw/2, 0);
this.context.lineTo(cw/2, ch);
this.context.stroke();
},
...
};
Next, drawMobileInstructions()
draws the instructions on the left side of the game’s canvas with the drawMobileInstructionsLeft()
method listed in Example 15.21.
SnailBait.prototype = {
...
drawMobileInstructionsLeft: function (cw, ch,
topLineOffset,
lineHeight) {
this.context.font = '32px fantasy';
this.context.fillText('Tap on this side to:',
cw/4, ch/2 - topLineOffset);
this.context.fillStyle = 'white';
this.context.font = 'italic 26px fantasy';
this.context.fillText('Turn around when running right',
cw/4, ch/2 - topLineOffset + 2*lineHeight);
this.context.fillText('Jump when running left',
cw/4, ch/2 - topLineOffset + 3*lineHeight);
},
...
};
The preceding method primarily uses the context’s fillText()
method to draw text. The same is true for drawMobileInstructionsRight(),
which draws the instructions on the right side of the canvas, listed in Example 15.22.
SnailBait.prototype = {
...
drawMobileInstructionsRight: function (cw, ch,
topLineOffset, lineHeight) {
this.context.font = '32px fantasy';
this.context.fillStyle = 'yellow';
this.context.fillText('Tap on this side to:',
3*cw/4, ch/2 - topLineOffset);
this.context.fillStyle = 'white';
this.context.font = 'italic 26px fantasy';
this.context.fillText('Turn around when running left',
3*cw/4, ch/2 - topLineOffset + 2*lineHeight);
this.context.fillText('Jump when running right',
3*cw/4, ch/2 - topLineOffset + 3*lineHeight);
this.context.fillText('Start running',
3*cw/4, ch/2 - topLineOffset + 5*lineHeight);
},
...
};
As you saw in Example 15.17, Snail Bait invokes drawMobileInstructions()
when the player taps on the Show how to use the controls link in the mobile welcome screen. As you’ve seen, however, Snail Bait draws the mobile instructions directly into the game’s canvas, which is the same canvas that Snail Bait redraws every animation frame. Because Snail Bait continuously redraws the game’s canvas, it must continuously redraw the mobile instructions while they are visible. To do that, we modify the game’s draw()
method, as shown in Example 15.23.
SnailBait.prototype = {
...
draw: function (now) {
...
if (this.mobileInstructionsVisible) {
snailBait.drawMobileInstructions();
}
},
...
};
If the mobile instructions are visible, Snail Bait’s draw()
method draws the mobile instructions. Recall that the mobileInstructionsVisible
flag is set when the player taps on the Show how to use the controls link, as shown in Example 15.17.
To start the game, players tap the Start button that the game displays along with the mobile instructions. That Start button resides in a Start toast, whose implementation we discuss next.
The HTML for the mobile Start toast is shown in Example 15.24.
The Start toast contains only the Start link. The toast’s CSS class is snailbait-mobile-toast
, which is the class shared by the initial start toast, as discussed in Section 15.5.1.3, “Define CSS for the Mobile Toasts,” on p. 395.
<!DOCTYPE html>
<html>
...
<body>
...
<div id='arena'>
...
<!-- mobile devices only -->
<div id='snailbait-mobile-start-toast'
class='snailbait-mobile-toast'>
<a href='#'
class='snailbait-welcome-link'
id='snailbait-mobile-start-link'>
Start
</a>
</div>
...
</div>
...
</body>
</html>
The CSS for the Start toast is listed in Example 15.25.
#snailbait-mobile-start-toast {
width: 800px;
height: 400px;
}
The Start toast, like the mobile welcome toast, is the same size as the game’s canvas.
Next, we implement the event handler for the Start link.
The event handler for the Start link is shown in Example 15.26.
var SnailBait = function () {
...
this.mobileStartToast =
document.getElementById('snailbait-mobile-start-toast');
...
};
...
snailBait.mobileStartLink.addEventListener(
'click',
function (e) {
var FADE_DURATION = 1000;
snailBait.fadeOutElements(snailBait.mobileStartToast,
FADE_DURATION);
snailBait.mobileInstructionsVisible = false;
snailBait.playSound(snailBait.coinSound);
snailBait.playing = true;
}
);
The preceding event handler fades out the mobile Start toast and sets the mobileInstructionsVisible
property’s value to false
, which prevents the game’s draw()
method from drawing the instructions any further.
As was the case for the event handler for the Start the game link in the initial start toast, the preceding event handler plays the coin sound when the player taps the Start link. Finally, the event handler sets Snail Bait’s playing
property to true
, which activates the game’s event handlers.
Recall that the event handler for the Show how to use the controls link, listed in Example 15.17, invokes the revealMobileStartToast()
method. That method is listed in Example 15.27.
SnailBait.prototype = {
...
revealStartToast: function () {
snailBait.fadeInElements(snailBait.mobileStartToast);
this.mobileInstructionsVisible = true;
},
...
};
The revealStartToast()
method fades in the start toast containing the Start link and sets the mobileInstructionsVisible
Boolean variable’s value to true
. The revealStartToast()
method is invoked by the Show how to use the controls link’s event handler, which we discussed in Example 15.17.
You’ve seen how to modify Snail Bait’s user interface for mobile devices, so now let’s see how to incorporate touch events.
When Snail Bait runs on mobile devices, it adds touch event handlers to the game, as you can see in Example 15.28.
if (snailBait.mobile) {
...
snailBait.addTouchEventHandlers();
...
}
Snail Bait’s addTouchEventHandlers()
method, listed in Example 15.29, adds two touch event handlers to the game, one for touchstart
events and another for touchend
events.
SnailBait.prototype = {
...
addTouchEventHandlers: function () {
snailBait.canvas.addEventListener(
'touchstart',
snailBait.touchStart
);
snailBait.canvas.addEventListener(
'touchend',
snailBait.touchEnd
);
},
...
};
The touchstart
event handler, which is listed in Example 15.30, is uneventful.
SnailBait.prototype = {
...
touchStart: function (e) {
if (snailBait.playing) {
// Prevent players from inadvertently
// dragging the game canvas
e.preventDefault();
}
},
...
};
If gameplay is underway, the preceding event handler prevents the browser from reacting in its default manner for touch start events. In more practical terms, the call to preventDefault()
prevents players from accidentally dragging the canvas.
The touchend
event handler is listed in Example 15.31.
SnailBait.prototype = {
...
touchEnd: function (e) {
var x = e.changedTouches[0].pageX;
if (snailBait.playing) {
if (x < snailBait.canvas.width/2) {
snailBait.processLeftTap();
}
else if (x > snailBait.canvas.width/2) {
snailBait.processRightTap();
}
// Prevent players from double
// tapping to zoom into the canvas
e.preventDefault();
}
},
...
};
The touchend
event handler calls either processLeftTap()
or processRightTap(),
depending on the location of the tap. The call to preventDefault()
prevents players from zooming the display. The processRightTap()
method is listed in Example 15.32.
SnailBait.prototype = {
...
processRightTap: function () {
if (snailBait.runner.direction === snailBait.LEFT ||
snailBait.bgVelocity === 0) {
snailBait.turnRight();
}
else {
snailBait.runner.jump();
}
},
...
};
The preceding event handler turns the runner to the right when the runner is moving left; otherwise, the event handler makes the runner jump. The processLeftTap()
method is listed in Example 15.33.
SnailBait.prototype = {
...
processLeftTap: function () {
if (snailBait.runner.direction === snailBait.RIGHT) {
snailBait.turnLeft();
}
else {
snailBait.runner.jump();
}
},
...
};
The processLeftTap()
method is the mirror image of processRightTap().
The former turns the runner to the left when the runner is moving right; otherwise, the event handler makes the runner jump.
Both the touchstart
and touchend
event handlers only respond to events once gameplay is underway.
Mouse and touch events are similar, but not exactly the same. Not understanding the subtle distinctions between the two can be the source of bugs. You can read more about those subtleties at www.html5rocks.com/en/mobile/touchandmouse.
Both the touch event handlers discussed in the previous section invoke the preventDefault()
method on the event object the browser passes to those event handlers. For the ontouchstart
method, that call to preventDefault()
prevents players from double-tapping to zoom in or out. The call to preventDefault()
in the ontouchmove
event handler prevents players from inadvertently dragging the canvas.
It’s important to keep in mind that preventDefault()
has side effects you may not anticipate. For example, if you prevent the default reaction for every mouse event, the browser will not allow the default behavior of setting focus in a text field.
At the time this book was written, developers had to do the following two things, at a minimum, to get sound working reliably on mobile devices.
• Force the player to initiate a sound so that iOS will download sound files
• Adjust positions in the audio spritesheet for Android
The HTML5 audio
element on iOS downloads sound files only if a user activates a user interface control to make the first sound. From then on, the operating system will dutifully play sounds without user intervention.
That requirement on iOS is the reason the event handlers for the start links discussed in this chapter played the coin sound; see Example 15.16 and Example 15.26.
Without the player initiating the sound with a click, iOS will refuse to play any of Snail Bait’s sounds at all.
The second nuisance associated with getting sound to work reliably on mobile devices is Android’s interpretation of where individual sounds lie in an audio sprite. Those sounds are offset anywhere from 0.2 seconds to 2.5 seconds. Because of those offsets, Snail Bait resets all the sound positions when the game runs on Android devices, as shown in Example 15.34. See Chapter 14 for more about how Snail Bait implements sound and music.
if (snailBait.mobile) {
...
if (/android/i.test(navigator.userAgent)) {
snailBait.cannonSound.position = 5.4;
snailBait.coinSound.position = 4.8;
snailBait.electricityFlowingSound.position = 0.3;
snailBait.explosionSound.position = 2.8;
snailBait.pianoSound.position = 3.5;
snailBait.thudSound.position = 1.8;
}
}
The preceding code uses a JavaScript regular expression to determine if the game is running on an Android device; if so, it adjusts the game’s sound positions in the audio sprite sheet.
JavaScript regular expressions have many other uses besides detecting mobile devices. See http://www.w3schools.com/jsref/jsref_obj_regexp.asp for a reference to these handy objects.
Both iOS and Android make it easy for users to add an icon representing a website to the mobile device’s home screen, as you can see from Figure 15.14, which shows a drop-down menu on Android.
When the player taps on the resulting icon, shown in the second row and third column of the left screenshot in Figure 15.15, the game begins without browser chrome, as you can see in the right screenshot in Figure 15.15.
As this book want to press, the W3C had released a working draft of a full-screen API, that lets your web application run full-screen without user intervention, but that functionality was not widely supported.
There are lots of mobile devices in the world, with more being sold all the time at a rate of 2 billion per year and climbing. And lots of those mobile device users play games on their devices.
In this chapter you saw how to customize Snail Bait so it runs on mobile devices. This chapter is far from a full-fledged exploration of implementing games for mobile devices; you will find entire books devoted to that topic. Instead, this chapter shows you how to get a nontrivial video game running on iOS and Android, pointing out some of the pitfalls of HTML5 mobile development in general.
In the next chapter you will see how to implement particle systems to represent real-world phenomena such as smoke and fire.
1. Try Snail Bait on different mobile devices and see how the game performs.
2. Experiment with the viewport
meta tag by setting different sizes for the width.
3. Set the viewport
meta tag’s width to device-width
and restart the game on Android and iOS. Does the game initially scale the same on both operating systems?
18.221.249.198