5. Creating Games with the Canvas Tag

The Canvas (that is, the canvas tag) is what many think about when HTML5 is mentioned. Instead of creating a bunch of div elements with contained images with CSS to simulate movement and game play, you can use canvas to provide a surface to draw objects on the screen. Neither method is easier or harder, but each has different considerations. The Canvas has some built-in translation, rotation, scaling, and clipping, whereas with divs you have to handle transformations yourself. However, one advantage that games using div and CSS have over the Canvas is that older browsers, such as Internet Explorer 6, are supported. Hopefully, as more browsers become HTML5 compliant, this will become less of an issue.

Getting Started with the Canvas

Unlike with some other technologies, you don’t need to include any extra libraries to use the Canvas. As long as you have an HTML5-compliant browser and a text editor, you have all you need to get up and running. As mentioned before, the Canvas is a drawing area on a web page. The first thing that we need to do is to create that area. We can do that by creating a canvas tag and setting the height, width, and (optionally) the ID:

<canvas id="c" height="400" width="400"></canvas>

As you probably have guessed, this line of code draws a 400-pixel-by-400-pixel square Canvas with an ID of “c.”  Your next guess might be that we could start using the Canvas by running something like

var canvas = document.getElementById("c");

and then drawing with the canvas object. Close, but not quite. What is not apparent at first glance is that the Canvas acts as a container for drawing APIs, which we will actually use directly. WebGL, which we discuss in Chapter 7, “Creating Games with WebGL and Three.js,” uses the same HTML tag but a different drawing API. Continuing with our code example, to retrieve the 2D context (or the interface for drawing), we need to call getContext on the Canvas:

var context = canvas.getContext("2d");

Now we can begin drawing graphics.

Not all of the world’s graphics are driven by rectangles. No matter how little artistic ability you have, you will eventually need a way to create complex shapes. Paths are how we can create shapes using lines and arcs. The concept of paths pops up several times in the different technologies in this book. Let’s take the time to learn about them with a simple example, and the next occasions with be total child’s play.

Although they can be as simple as a straight line, paths form complex shapes by accumulating instructions one after another until the final shape is drawn. Any path consists of three primitive types:

• Lines

• Arcs

• Curves

If you think about objects in real life, they are composed of these three types. As I’m writing this chapter, I’m looking out the window at a street lamp and some tree planters. If you take a cross-section of these items, you will see those primitive path components, which can be spun around a center point or repeatedly drawn along a path to create a three-dimensional shape. Engineers using Computer Aided Design (CAD) employ these primitives to prototype the parts for the car you drive or the bike you ride.

Drawing Your First Paths

After creating a canvas object and retrieving the context, the first thing we need to do to draw a path is to call the aptly named beginPath() function, which clears the stack of any other paths we might have been drawing. This is important to do because path instructions are cumulative, and all paths for a given canvas tag draw using the same context. The next thing we need to do is to move to the point where we would like to begin drawing. Calling moveTo() with an x position and y position does just that. You can think of this as picking up the pen from the paper in order to draw another shape. Once the “pen” is in place, calling lineTo() with x and y positions places the pen down on the paper and draws a line to that point. The last thing we need to do is to stroke the lines we have drawn. You can think of the moveTo and lineTo instructions as tracings on the paper, and the stroke instruction is for going back and filling in the lines now that we know where we want them. We can use this knowledge to draw our game board for the tic-tac-toe game, as shown in Listing 5-1. It consists of two parallel vertical lines and two parallel horizontal lines to form a grid of nine spaces.

Listing 5-1. Drawing the Game Board for Tic-Tac-Toe


self.drawGameBoard = function() {
    var ctx = self.context;

    ctx.beginPath();

    ctx.moveTo(200,0);
    ctx.lineTo(200,600);

    ctx.moveTo(400,0);
    ctx.lineTo(400,600);

    ctx.moveTo(0,200);
    ctx.lineTo(600,200);

    ctx.moveTo(0,400);
    ctx.lineTo(600,400);

    ctx.stroke();
}


Drawing Game Sprites for Tic-Tac-Toe

Now that the game board is squared away, we need to draw the sprites to play the game. Let’s start with the X’s.

We can again use paths for X’s. They are drawn by starting at a point, drawing a line indicated by an offset to the right and down, picking up the “pen,” moving up by that same number of units, and then drawing a line that is down and to the left this time.

To make things simple for our game sprites, the game board was set at 600 by 600 pixels. That gives us a 200-pixel square for each space. We don’t want to the sprites to touch the dividing lines, so all the lines are drawn just short of the edges. Listing 5-2 shows the code to draw an X on the canvas.

Listing 5-2. Drawing an X for Tic-Tac-Toe


ctx.lineWidth = 2;
ctx.beginPath();

ctx.moveTo(10,10);
ctx.lineTo(190,190);
ctx.moveTo(190,10);
ctx.lineTo(10,190);
ctx.stroke();


Drawing our O’s requires using arcs. The most simple arc is a shape that we see every day—a circle. A circle is defined by a center point and a given distance (or radius) from that center point; the line that is drawn about that center point keeps the same radial distance. The arc function can draw a segment of a circle as well as a full circle, so we have to indicate the starting and ending angles along with an optional clockwise or counterclockwise flag.

You might have learned in Geometry class a little of the math used to calculate the surface area of shapes, the area and circumference of circles, and things like that. If you’ve forgotten it all, rest assured. We will only be using the fundamentals for now. One important concept from Geometry class is the calculation of angles. Instead of using degrees, we use radians. So for a full revolution about a point, we say that we are rotating 2Π (or 2 times pi) as opposed to 360 degrees. Pi is a mathematical constant representing the ratio of any circle’s circumference, or the distance of the line around a circle’s edge to its diameter, which is twice the distance from any point on the edge to the center point. Pi, which equals roughly 3.14159... can’t be represented cleanly as a decimal number, so it is much simpler to denote the approximation by using the symbol. Table 5-1 shows some common angles in both their degree and radian representations.

Table 5-1. Common Angles in Degrees and Radians

Image

If you need an angle that isn’t listed in the chart, you can convert from degrees to radians by using the following formula. You don’t have to worry about remembering pi because JavaScript stores the value in the Math.PI.

angle in radians = (angle in degrees) * (pi / 180)

The following snippet shows the code needed to draw our O’s. The circle is drawn by selecting a center point and radius and drawing from 0 to 2*Π radians. In keeping with the size of our X’s and game board, the radius is 90 pixels, giving a diameter of 180 pixels. It looks nicer than 190 pixels, and that’s what programming is about. In other words, sometimes the mathematically correct solution isn’t always the most pleasing.

ctx.lineWidth = 2;
ctx.beginPath();

ctx.arc(100,100, 90, 0, 2*Math.PI);
ctx.stroke();

Drawing Objects on the Canvas with Transformations

On a traditional Tic-Tac-Toe game board, there are nine possible spots where an X or an O could go. We could just create functions that explicitly draw the objects in each spot, but that wouldn’t be efficient. Instead, we can use translation to move the entire drawing plane from the origin to our desired point, draw our object, and then move the drawing plane back. This allows us to reuse the same code if we decided to make a 4×4 grid instead of a 3×3 grid.

A matrix is a collection of rows and columns of numbers that define the location, scale, and orientation of an object in space. For anything but translation, we would use a 2×2 matrix to represent transformations. But before we get to the more complex examples, let’s start out with translation.

To translate an object currently located at (x,y) by (xr, yr) relative units, we would add two 2×1 matrices together, as shown in Figure 5-1. In this figure, we have a point located at (0,5) that we want to move 5 units to the left and 5 units down.

Figure 5-1. Translation of an object

Image

For scaling, shearing, and rotation, we have to multiply matrices. This is also where our 2×2 matrices come into play. Figure 5-2 shows the role of each position in the matrix.

Figure 5-2. Definitions of the positions in a 2×2 matrix

Image

The most basic matrix we could use is the Identity matrix, as shown in Figure 5-3. The interesting property about the Identity matrix is that for any other matrix, multiplying it by the identity will return the original matrix. We can see from the figure that it represents a scale of 1 on the x and y axes with no skewing.

Figure 5-3. Identity matrix

Image

Let’s put the Identity matrix to the test by demonstrating multiplication. To multiply a 2×2 matrix by a 2×1 matrix, we would follow the order indicated in Figure 5-4. You can also see in this figure that the Identity matrix’s property holds true.

Figure 5-4. Matrix multiplication

Image

Looking again at Figure 5-2, we can easily discern how to do scaling or skewing by placing our scale or skew constants in the proper slots. Rotation, however, is a bit more complicated.

Figures 5-5 and 5-6 show the matrixes needed to multiply by the vector representing some point to rotate it theta degrees about its center.

Figure 5-5. Counterclockwise rotation

Image

Figure 5-6. Clockwise rotation

Image

Ordering Your Transformations

Similar to the order of operations mnemonic device “Please Excuse My Dear Aunt Sally” (which represents parentheses, exponents, multiplication, division, addition, and subtraction), transformations have to be applied in a specific order; otherwise, unexpected results will occur. The issue at hand is that each subsequent matrix that is multiplied builds upon and distorts the result of the final position. The general order is scaling, rotation, and then translation.

For example, let’s say you want to draw a box at location (10,10) and you want to rotate it 2 radians around its center. The current matrix is located at (0,0), as is the box’s center. You would rotate the box using the desired amount and then translate it to (10,10). To move it a further 3 radians, you use the inverse order, translating the object back to the origin and applying the rotation before translating it to its desired location.

When a person does a back flip in real life, he is rotating about his current position. To account for this in a game, we must move the object to the origin, do the rotation, and then translate the object back to its original position.

The Canvas allows us to set the transformation matrix directly by calling setTransform and transform. setTransform sets the matrix to the identity before setting the transformation, whereas transform creates a product of the current matrix and the developer-provided matrix. Luckily for us, canvas has first-class support for translation, rotation, and scaling using translate, rotate, and scale. Both translate and scale take x and y parameters, whereas rotate takes an angle to rotate the matrix in radians.

Saving and Restoring the Canvas Drawing State

The last thing we need to properly transform objects is a way save and restore the Canvas state. We need to save and restore the transformation matrix so that we properly isolate transformations from one object affecting those that are drawn after it. save and restore do just that. Their combined functionality is similar to hitting a save point in a game, going down a fork in the road to retrieve some sort of power-up, being able to restore the state to return to the fork, and keep the power-up.

These functions save and restore not only the current transformation matrix but also the clipping region and several properties, including strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, font, textAlign, globalCompositeOperation, and textBaseline. Listing 5-3 shows our updated function for drawing O sprites and properly handling translation.

Listing 5-3. Drawing Several O’s


self.drawOSprite = function(x, y) {
var ctx = self.context;

    // Save the canvas state and translate
    ctx.save();
    ctx.translate(x,y);

    ctx.lineWidth = 2;
    ctx.beginPath();

    ctx.arc(100,100, 90, 0, 2*Math.PI);
    ctx.stroke();

    // Restore canvas state
    ctx.restore();
}


Using Images with the Canvas

For every image your game uses, another hit is incurred against the server to retrieve it. We already know from basic HTML how to use the img tag. In this section, we look at a couple more ways to use images in our applications.

Serving Images with Data URLs

Data URIs provide a way to include the data for a file inline in HTML code. The contents of the file then get retrieved when the HTML is downloaded, thus reducing the number of hits to the server and theoretically the wait time. The general format is as follows:

data:[<mime type>][;charset=<charset>][;base64],<encoded data>

Here is a simple data URI inside an image tag:

<img src= "data:image/gif;base64,R0lGODlhDwAOAIAAAP///
    wAAACwAAAAAD wAOAAACGISPCaG9rhhEcppq8dSQO9+AUUCWpoVOBQA" />

Let’s examine this URI. The original file is a GIF, so mime type is set to image/gif. The MIME type would be image/png, for example, to reflect that the source file is PNG. charset refers to the character set of the file if the source is a text file. The source file isn’t text, in this case, so we omit charset. If both the MIME type and character set are omitted, the default values will be text/plain for the MIME type and US-ASCII for the character set. The next component, ;base64, indicates whether the data is Base64 encoded. Base64 is used when you need to transmit binary data over a medium that is more tailored to transmitting text. It is also used sometimes to do basic password encoding for web services. Base64 encoding formats data using A–Z, a–z, and 0–9, plus two additional printable characters, generally + and /. Many tools on the Web will convert image files to data URIs. The website www.sveinbjorn.org/dataurlmaker offers a web-based application along with links to desktop applications.

Serving Images with Spritesheets

Trying to load a bunch of image files,  even if they are small in size, can be very taxing on a server and cause your users to wait a really long time. Spritesheets solve this by packaging the images for many files into one. They are generally used for a large number of small images of a similar size. Images are padded to have uniform dimensions and can be retrieved using the images’ calculated coordinates. Image-editing applications such as GIMP, ImageMagick, and Photoshop can create them for you, and certain web services can create them as well. Spritesheets can be combined with data URIs.

Drawing Images on the Canvas

Now that we have our images loaded, we need a way to draw them. The Canvas has a function appropriately named drawImage with several variants. All our examples assume we have retrieved an image from an image tag using document.getElementById, pulled it from the document.images collection, or created the image directly in JavaScript using Image() and adding a src URI to it.

The simplest variant is

drawImage(image, x, y)

which draws the image in its entirety with its current size with its left-upper corner at (x,y).

The second variant is

drawImage(image, x, y, width, height)

which, like the first variant, draws the entire image. The last two parameters scale the drawn image in the Canvas.

The last variant is the most powerful and has the most parameters:

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

This one allows you to use only parts of the source image and do scaling on the Canvas. sx and sy refer to the left-upper corner of the image slice. All data in the bounds of (sx,sy) and (sx+sWidth, sy+sHeight) is drawn. On the Canvas, the image is drawn in the area of (dx,dy) and (dx+dWidth, dy+dHeight).

Animating Objects with Trident.js

Trident.js (https://github.com/kirillcool/trident-js) is an animation library for JavaScript created by Kirill Grouchnikov that was ported from his Java library of the same name. The main focus of the library comes from the timeline, not that much unlike one in a video-editing program, allowing us to transition between different states with keyframes. Easing functions allow objects to move in a more life-like manner. Many timelines with different functions can operate simultaneously or in a specific order, one at a time. This is the main reason Trident.js was selected over the myriad of possibilities when it comes to JavaScript animation libraries.

Trident, at present, uses setTimeout instead of requestAnimationFrame. requestAnimationFrame determines whether to draw an object based on if the browser window is obscured of if the page is currently rendering, and it caps the refresh rate to 60Hz. setTimeout does none of this. It tries to render as much as possible even if the app is still rendering or not in view. Trident is a multipurpose animation library. setTimeout can be applicable to both DOM- and Canvas-based animation. requestAnimationFrame cannot. It might seem that the use of setTimeout disqualifies Trident before it even gets out of the gate. The feature that redeems it is the ability to pause a timeline. This, along with the other features, temper the disadvantages of using setTimeout.

Creating Timelines

The most basic timeline in Trident.js has a duration over which it will run and an object and property to interpolate. The interpolator tells Trident.js how to make intermediary values and optional starting and ending values. Timelines work by periodically waking up, or pulsing, to check how much time has passed and adjusting values appropriately. Although there is no guarantee on the frequency or time period of timeline pulses, for most applications, they perform reasonably well and regularly. These attributes are affected by the load on the client machine and what is being run on each pulse. Listing 5-4 shows some code to create a timeline.

Listing 5-4. A Timeline to Interpolate Text Size


var rolloverTimeline = new Timeline(myspan.style);
  rolloverTimeline.addPropertiesToInterpolate([
    { property: "font-size", from:16, to: 36,
      interpolator: new IntPropertyInterpolator()}
  ]);
  rolloverTimeline.duration = 2000;


First, we instantiate a timeline, assigning the element and property that will be modified. In this case, it is the style field of an element with the ID “myspan.” Next, we added the properties that will be modified. In the code, we state that the font size will range from 16 to 36 units and to interpolate the values as integers. Lastly, we set the duration of the animation to be 2 seconds (in milliseconds).

As opposed to constantly monitoring the state of the interpolated properties, which could cause the page to become unresponsive anyways, Trident.js fires a timeline pulse or an instant in time in which wakes up, checks the state, and modifies it as needed. Unlike the Java version of Trident, which has a pulse rate of once every 40 ms, the pulse rate of Trident.js depends on the system load and the runtime environment. That being said, JavaScript engines in browsers are getting faster and faster with every iteration. Given the proper load, I wouldn’t expect it to get too bogged down. We can also use a timeline to control our game loop. You can see this in action in this chapter’s Copy Me game source code.

The last thing we need to do is to add a way to start our animation. I decided to put the start and reverse functions on onmouseover and onmouseout in a span element, as shown in Listing 5-5. Thanks to timeline pulses, the reverse action starts relatively quickly after onmouseout is fired. In addition to play and playReverse, there is also a replay function we could use as well to interact with the timeline.

Listing 5-5. Starting and Reversing a Timeline


<span id="myspan"
  onmouseover="rolloverTimeline.play();"
  onmouseout="rolloverTimeline.playReverse();">Some text</span>


In addition to integers, we could also interpolate over floating-point numbers or even RGB color values. We just need to set the appropriate “from” and “to” values with a different interpolator: IntPropertyInterpolator, FloatPropertyInterpolator, or RGBPropertyInterpolator, respectively. We aren’t limited to HTML elements. Trident can also interpolate properties on JavaScript objects.

Animating with Keyframes

Normal timelines transitions between the beginning and ending points without any control over any intermediary value. Keyframes provide a way to indicate what a value should be at a given point on the timeline. A new field called goingThrough is where you would specify the values on the range from 0 (beginning of timeline) to 1 (end of timeline). You can see keyframes in action in Listing 5-6.

Listing 5-6. Animating a Timeline with Keyframes


var keyframeTimeline = new Timeline(keyframespan.style);
  keyframeTimeline.addPropertiesToInterpolate([
    {   property: "font-size", from:16, to:36,
        interpolator: new IntPropertyInterpolator()
    },
    {   property:"color",
        goingThrough: {
            0: "rgb(0,0,0)",
            0.4:"rgb(0,255,0)",
            1:"rgb(200,140,140)"
        },
        interpolator: new RGBPropertyInterpolator()
    }
  ]);
keyframeTimeline.duration = 3000;


Creating Nonlinear Timelines with Easing

By default in Trident.js, object and transitions happen at an equal rate of speed from start to finish. This is not what we usually see in real life. Friction, inertia, and other forces are in play that will cause an object to accelerate, decelerate, or bounce. Although doing precise physics calculations is outside the scope of this chapter, we can use easing functions to bring our animations somewhat close to what is expected. Given a ball that moves up and down on the y-axis, Figure 5-7 shows regular linear motion where every unit of time passed corresponds to one unit of movement on the y-axis. Figure 5-8 shows a bounce effect. We can see a couple of rebounds along the y-axis. Also notice the sharp decline on some of the segments, which corresponds to a faster speed.

Figure 5-7. Graph of linear motion

Image

Figure 5-8. Graph of a bounce easing function

Image

Trident.js, at the time of this writing, has 31 easing functions. Rather than list them here, I encourage you to download the source and try out the testBallLoop page for yourself. We will cover easing functions again when we discuss SVG in Chapter 6, “Creating Games with SVG and RaphaëlJS.”

Animating Game Objects with Spritesheets

After learning how to slice and dice our images to draw on the Canvas, we can use some Trident.js timelines to create animations. The sprites for this demo were sourced from OpenGameArt (http://opengameart.org). All of the game assets listed on the site have very permissive licenses and are free to use in games. The spritesheet I selected for the demo is of a walking zombie that is subsequently shot in the head. All of the individual frames are 128 pixels by 128 pixels. Let’s begin by setting up our timeline to control the animation. Given a JavaScript function named Sprites, Listing 5-7 shows the code from the initialization function that sets up the timeline.

Listing 5-7. Setting Up the Timeline for an Animation


self.init = function() {
// Truncated for brevity
    var self = this;
self.current = 0;
self.spriteTimeline = new Timeline(this);
    self.spriteTimeline.addPropertiesToInterpolate([
        { property: "current", from:0, to: 36,
            interpolator: new IntPropertyInterpolator()}
    ]);

    self.spriteTimeline.duration = 5000;
    self.spriteTimeline.addEventListener("onpulse,
      function (timeline, durationFraction, timelinePosition) {
        self.drawSprite();
    });
    self.spriteTimeline.play();
}


One notable difference between this and the other examples so far is that the parameter for a new timeline is this, referring to the current object. The other difference is how we handle drawing on the Canvas. Unlike with CSS properties, which are automatically propagated to HTML elements, we have to make the changes in two steps. First, we set up a listener to fire every time the timeline position is updated. When the timeline is played, it modifies the current property, which is used by drawSprite function to calculate the proper slice to draw. Listing 5-8 shows the code to draw an individual sprite.

Listing 5-8. Drawing an Individual Sprite


self.drawSprite = function() {
    var ctx = self.context;
    var row = 3;
    ctx.clearRect(0, 0, 128, 128);
    ctx.drawImage(self.sheet, self.current *128,
      row*128, 128, 128, 0, 0, 128, 128);
}


Simulating 3D in 2D Space

Starting back in the arcade and early console days, the ability to simulate three dimensions in 2D space made for a more pleasing game experience. At that time, real 3D wasn’t possible, and simulated 3D (called 2.5D) was the only option. In a way, the more things change, the more they stay the same. At this time of writing, the 2D Canvas API was more widely supported, with WebGL being regulated to experimental status. As a result, a 2.5D game in Canvas will have more potential users that one in WebGL. Another advantage of 2.5D is that it can potentially take less computing power than a true 3D game. In this section, we discuss some of the options to create a 2.5D experience.

Perspective Projection

Perspective projection seeks to mimic how the human eye sees objects. What our eyes see is a 2D projection of a scene in 3D. Several things are happening in concert that give us a perception of depth. For example, distant objects appear smaller and with less detail than closer objects. The simplest form and most appropriate for 2D games is one-point perspective. This point is known as the vanishing point, and it is the point where parallel lines appear to converge. Figure 5-9 shows an example of this in action with a graphic created in the 3D modeling application Blender. Orthographic projection is a way to represent a 3D object in 2D space. Images are created for the top, bottom, left, right, front, and back of the object. Often, just the information from the front, top, and right views are enough to recreate the object. The views on the right side of the figure are the top and side views, respectively. In the perspective view on the left, we can see the edges of the rectangles appear to be slowly converging toward a point on the horizon. We could heighten the effect by texturing closer objects in more detail than distant object.

Figure 5-9. Perspective projection rendered in Blender

Image

Parallaxing

One technique that is often used in side-scrolling video games such as Super Mario Brothers and Sonic the Hedgehog to simulate three dimensions and create a more immersive experience is parallaxing. We can create the effect by making different components of the background exist as separate, independently movable layers that move at different speeds. It is similar to what you would experience if you were a passenger in a car riding across a bridge. The cars and barriers around you would move relatively fast compared to a city skyline you might see far in the distance. Let’s dig a little deeper by creating a parallax effect ourselves.

Creating a Parallax Effect with JavaScript

For this demo, I have created some rudimentary graphics, including a sky dome layer representing the sky and clouds, a mountain range layer, and a layer showing the ground and other near objects. These are PNGs, but any image format that allows transparency will do. Each is like a sheet of paper with cutouts that let you see through to the sheet below it. This concept is known as z-ordering.

To keep things simple, our animation will continuously move. However, in a real side-scrolling game, motion would respond to player input such as a key press or move of the mouse. Besides the backmost layer, which is usually stationary, each layer moves at a rate 100% faster than the one behind it. We can start by setting up our timeline as shown in Listing 5-9. Because we’ll be using multiple layers of images, we will use the timelinePosition value directly and draw our layers every time there is a pulse.

Listing 5-9. Setting Up the Parallax Timeline


self.setupTimeline = function() {
    self.parallaxTimeline = new Timeline(this);

      self.parallaxTimeline.duration = 5000;
    self.parallaxTimeline.addEventListener("onpulse",
      function (timeline, durationFraction, timelinePosition) {
        var ctx = self.context;
        ctx.clearRect(0, 0, 320, 200);
        // background layer is stationary
        ctx.drawImage(document.images[0], 0, 0);

        self.drawLayer(timelinePosition, document.images[1]);
        self.drawLayer(timelinePosition, document.images[2]);
        self.drawLayer(timelinePosition, document.images[3]);
    });
    self.parallaxTimeline.playInfiniteLoop(RepeatBehavior.LOOP);
}


We begin by clearing the Canvas and drawing our background. We are drawing it whole with no transformations, so we can use the simplest drawImage function. Its size is 320 pixels by 200 pixels. Each of our other layers will have a width that is some multiple of 320 pixels. Doing so frees us from having to perform much calculation for the speed of the layers. Layer 1 has a width of 320 pixels, so a full cycle of the timeline will only move it that far. However, layers 2 and 3, which are 640 pixels wide and 960 pixels wide, respectively, have to cover a greater distance in the same amount of time.

You won’t be able to escape a little bit of math. The scrolling effect doesn’t come for free, but luckily it requires only a minimal amount of arithmetic. Listing 5-10 shows our function for drawing a layer given a position in the timeline.

Listing 5-10. Drawing a Layer


self.drawLayer = function(position, image) {
    var ctx = self.context;

    var startX = position*image.width;
    var pixelsLeft = image.width - startX;
    var pixelsToDraw;

    ctx.drawImage(image, startX, 0, pixelsLeft, 200, 0, 0, pixelsLeft, 200);
    if(pixelsLeft < 320) {
        pixelsToDraw = image.width - pixelsLeft;
        ctx.drawImage(image, 0, 0, pixelsToDraw, 200,
            pixelsLeft-1, 0, pixelsToDraw, 200);
    }
}


Based on the timeline position, we can calculate where in the source image to start retrieving pixels to draw. If there aren’t enough pixels to fill the viewport, we calculate how many pixels we need to draw (pixelsToDraw) and we draw a portion from the beginning of the source image to fill that space. To avoid having a 1-pixel gap between the seams of the image, we decrement the destination x position in the Canvas by 1 to cleanly stitch everything together.

Creating Copy Me

To put some more of what you’ve learned so far into practice, we will create a clone of the old electronic memory game Simon called Copy Me. The game’s name is a play on children’s game Simon Says, where the players must do everything that Simon says to do. In the game Simon, the computer creates a sequence of tones for the player to mimic. As each tone is played, a corresponding light—either red, green, blue, or yellow—illuminates. With each passing round, the sequence is increased by one. Game play continues until the player makes a mistake.

Drawing Our Game Objects

Instead of making our game look just like the traditional Simon game, we will instead use rectangles and drop shadows, thus taking advantage of some other features of the Canvas Path API. Listing 5-11 shows the code to create one of the rectangles.

Listing 5-11. Drawing a Rectangle with a Shadow


ctx.save();
ctx.fillStyle = "rgb(150,150,0)";
ctx.shadowOffsetX = yellowOffset.x;
ctx.shadowOffsetY = yellowOffset.y;
ctx.beginPath();
ctx.rect(400,200, 200,200);
ctx.fill();
ctx.restore();


The rect function takes the x and y coordinates of the left corner—in this case (400,200)—followed by the width and height. Creating drop shadows is easy as well. It is important to note that the shadowColor needs to be set on the context before a shadow can be drawn. Figure 5-10 shows the completed game board.

Figure 5-10. The Copy Me game board

Image

Making the Game Tones

When I think of a game from the 1970s, I immediately think of the 8-bit sound board that was melodic yet electronic. To duplicate that effect, we could try to find tones or we could create them ourselves using MIDI, the Musical Instrument Digital Interface. The advantage that MIDI gives us is that software packages are available that allow use to create our tones using a text file. MIDI also gives us the flexibility of using the files as is, depending on the instruments available on the client machine, or converting to WAV or MP3 to bring obscure and unique instruments to our games. We only need four tones, so this is very manageable for our needs. Convention states that tones be organized as follows:

• Red button: An A note

• Green button: An A note one octave up from the red button

• Blue button: A D note, a fourth above the green button

• Yellow button: A G note, a fourth above the blue button

Several software projects are available that can generate MIDI files (GarageBand, for instance) and several can even use text files to do it. Because I didn’t have a musical keyboard handy, I went the text route. Two of the more popular text-to-MIDI applications are Lilypond and ABC notation. However, both are more suited to creating long-form compositions and are overkill for our four-note “score.” What’s more, their notation styles are a bit complicated. I instead went with JFugue, a Java API for programming sequences without the complexity of MIDI. With about ten lines of code, I was able to hear all the tones together to determine whether they sounded right. It’s not JavaScript, but you can’t really argue with concise code. After creating the MIDI file with Java, we can use a MIDI editor/sequencer such as GarageBand to chop up the “score” into one-note pieces and convert them to WAV or MP3 files.

Listing 5-12 shows our tones, starting at the fourth octave, with a half-note duration and played as a harpsichord. Include just a single note after the instrument designation, and we have a single-note MIDI file. Easy peasy.

Listing 5-12. Generating MIDI Tones with JFugue


import org.jfugue.Player;
public class TestMidi {
    public static void main(String [] args) {
        Player player = new Player();
        String musicString ="I[Harpsichord] A4h A5h D5h G5h";
        player.play(musicString);
        player.saveMidi(musicString,
            new File("/Users/jwill/Desktop/test.mid"));
    }
}


Playing MIDI Files in the Browser

Now that we   have our MIDI files, we have a slight problem. We don’t have a means to play them. Some browsers do have plug-ins to play .mid files, but they don’t give us the programmatic control we need for our game. Lucky for us, the jasmid (https://github.com/gasman/jasmid) project fills that need. It can parse and play MIDI files using the HTML5 Audio API with a fallback to Flash. At runtime, the .wav files are generated and piped to the Audio API. A fair bit of work is involved in retrieving the file and assigning an instrument, so it will be an exercise for the intrepid to venture down that path. I fully expect browsers to eventually have native MIDI support as they currently support other sound file formats. In the meantime, some of the effort can be cut by preprocessing MIDI into another sound format such as WAV, MP3, or OGG. The open-source project Audacity does a fairly good job of this task, along with fulfilling other audio-editing needs.

Playing Multiple Sounds at Once

You saw in Chapter 1, “Introducing HTML5,” how we could use the Audio API to write HTML or JavaScript code to play sound. One thing that we didn’t cover in that chapter was playing multiple sounds at once. It’s a bit more complicated than just piping multiple sounds at once to an Audio object and executing play().

Each Audio object can only do a single thing at once. When you call play on something that is already playing, it silently ignores the request. If you wait until slightly after it has finished playing to try and play it again, it will probably work. If your sounds sometimes don’t play, it could ruin the game experience and make people think your game is wonky. The way we can solve this is instead of creating a Audio object for each sound, we create a pool of Audio objects that we can iterate through, looking for one that isn’t currently playing. We can check the status by comparing the duration and the currentTime. If they are equal, the sound has finished playing and we can use the channel. If not, the sound is either playing or cueing up to play. You can see this code in action in Listing 5-13.

Listing 5-13. Playing Multiple Sounds


var numChannels = 10;
channels = new Array();
for (var i = 0; i<numChannels; i++) {
    channels[i] = new Audio();
}
var play_multi_channel = function(id) {
for (var i = 0; i<channels.length; i++) {
        if (isNaN(channels[i].duration) ||
          channels[i].duration == channels[i].currentTime) {
            channels[i].src = document.getElementById(id).src;
            channels[i].load();
            channels[i].play();
            console.log("Playing on Channel: "+i);
            break;
        }
    }
}


This code assumes that the sounds will be enclosed in audio tags, but we could just as easily pass in the URLs to the files. 

Playing Sounds Sequentially

Although the method described in   the previous section would be great for a side-scroller, it doesn’t serve us that well for the Copy Me game. One sound shouldn’t play until the other one has finished playing. We can do this by registering listeners for the ended event on Audio objects. We need an array of objects, self.audios, to which we have added objects containing an Audio object and a timeline like in the following snippet:

self.audios.push(
{audio: new Audio(self.sounds.red), timeline: self.timelines.red});

When the audio plays, the timeline will also be executed to simulate a color pulse. On every Audio element except the last on in the sequence, we need to execute

self.audios[i].audio.addEventListener('ended', self.playNext, false);

self.playNext very simply increments the counter, noting the current position, plays the sound, and starts the timeline to blink the proper rectangle, as shown in Listing 5-14.   

Listing 5-14. Playing the Next Sound


self.playNext = function() {
    var j = self.currentPosition++;
    self.audios[j].audio.play();
    self.audios[j].timeline.play();
}


Drawing Our Game Text

The Canvas 2D contex gives us some basic functions to draw text. We can draw filled or stroked (outlined) text, set font attributes, and set text alignment. Listing 5-15 shows the code to draw a couple strings in our game. The fillText and strokeText functions take as parameters the text string to draw and x and y coordinates from which to start drawing text. An optional parameter, maxWidth, scales the font size to fit in that width. Our font string is composed of the attributes we specify, such as the weight (bold or italic), the font size, and the desired font with fallback options.

Listing 5-15. Drawing the Copy Me Game Text


var ctx = self.context;
ctx.font="bold 24px Arial, sans-serif";
ctx.fillText("Copy Me", 250,250);
ctx.fillText("Round " + self.currentRound, 250,300);


Styling Text with CSS Fonts

Although we can relatively guarantee that some fonts with be available on most operating systems, such as Arial, Georgia, and Times New Roman, sometimes you might want a font that isn’t included by default on the client’s machine. With CSS fonts, we can bundle them with our games. Whereas for a regular website, it might be appropriate to use a font loader that pulls from the Web, this doesn’t work well with the Canvas unless we can ensure that the fonts are loaded before the Canvas is drawn. Otherwise, we have a big empty space where our text should go or text in the default font.

Listing 5-16 shows the code in a fonts.css file. In it, we have listed the font family of the included TrueType font file and the format of the font file, generally either TrueType or OpenType. This code loads the ReenieBeanie font asynchronously. We can ensure it’s loaded in a rudimentary way by calling setTimeout with an interval of 3 to 5 seconds, depending on the number of fonts we are loading. A more elegant means is to employ a loading screen that waits for user input before starting the game or directly embedding the fonts using data URIs.

Listing 5-16. CSS File Demonstrating the Use of font-face


@font-face {
    font-family:'ReenieBeanie';
    src: url('../ttf/ReenieBeanie.ttf') format("TrueType");
}


We now just need to include that CSS file in the HTML page hosting our Canvas:

<link href='css/fonts.css' rel='stylesheet' type='text/css'>

Now we’re all set. ReenieBeanie is downloaded from the Google Font Directory (http://code.google.com/webfonts), which has very permissive licenses for the all the listed fonts. Another great resource is the Open Font Library (http://openfontlibrary.org/). Be sure to verify the license terms of any font you include with your games.

Summary

In this chapter, through the development of several games and game-related examples, we dove deep into the Canvas 2D context. You learned how to animate images on a canvas, draw primitives, and create text. In the later examples, we wove together MIDI sounds and bundled fonts into our game.

Exercises

1. Convert 290 degrees to radians.

2. How would you play a theme song for your game level and then play sounds for actions such as jumping and kicking without interrupting the theme song?

3. Despite the small file size, why might you not use MIDI directly for game sounds?

You can download chapter code and answers to the chapter exercises at www.informit.com/title/9780321767363.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.102.178