Chapter 20. Epilogue: Bodega’s Revenge

This book devotes entire chapters to implementing various aspects of 2D games such as graphics and animation, sprites and sprite behaviors, and collision detection, by examining the implementation of a nontrivial HTML5 video game.

This chapter provides a more holisitic perspective by showing you how to combine those aspects of 2D game development to implement a simpler video game, shown in Figure 20.1.

Figure 20.1. Bodega’s Revenge

Image

In Bodega’s Revenge, the player shoots advancing birds with a turret gun. Players rotate the turret by sliding their fingers up and down on mobile devices. On the desktop, players use either the left and right arrow keys or the d and k keys on the desktop to rotate the turret. The player shoots bullets from the turret by pressing the spacebar on the desktop or tapping the screen on mobile devices.

When the turret fires a bullet, the circular part of the turret darkens and fire erupts from the end of the turret, as shown in Figure 20.1. When a bullet collides with a bird, the bird explodes and the bullet disappears.

The player starts the game with 20 bullets. When a bullet goes out of view, the game immediately reloads it in the turret and if the player holds down the spacebar on the desktop, the turret shoots bullets in rapid succession as shown in Figure 20.1.

Birds fly from right to left a little faster than the background scrolls, creating a mild parallax effect (see Chapter 3 for more about parallax). When a bird flies behind the turret and off the left edge of the game’s canvas, the player loses a bullet. The game displays the number of bullets that remain above the canvas. The game ends when the turret runs out of bullets.

In this chapter we implement Bodega’s Revenge with the following steps.

• Design the user interface (Section 20.1 on p. 513)

• Create the spritesheet (Section 20.2 on p. 517)

• Instantiate the game (Section 20.3 on p. 518)

• Implement sprites (Section 20.4 on p. 519)

• Implement sprite behaviors (Section 20.5 on p. 528)

• Draw the bullet canvas (Section 20.6 on p. 545)

• Implement touch-based controls for mobile devices (Section 20.7 on p. 547)

Let’s begin our discussion of Bodega’s Revenge by looking at its user interface.


Image Note: Starting a game of your own

Bodega’s Revenge was implemented from a stripped-down version of Snail Bait, without sprites and many of Snail Bait’s extraneous features such as smoking holes and the developer backdoor. You can download that stripped-down version of Snail Bait, which you can use as the basis for your own game, at corehtml5games.com/snailbait-stripped-down.



Image Note: The rest of this book is a prerequisite for this chapter

This chapter focuses on implementing the gameplay of Bodega’s Revenge and spends no time discussing the fundamentals of the game’s underlying infrastructure such as implementing sprites and sprite behaviors in general. Discussions about implementing 2D game fundamentals take place elsewhere in this book and are not repeated in this chapter.



Image Note: Play Bodega’s Revenge

You can play Bodega’s Revenge online at www.corehtml5games.com/bodegas-revenge.


20.1. Design the User Interface

Bodega’s Revenge uses CSS to draw the carbon fiber background for the game’s webpage, as shown in Figure 20.2. The CSS for that background comes from the CSS3 Patterns Gallery, discussed in Chapter 2.

Figure 20.2. Bodega’s CSS background

Image

The game’s favicon shown in Figure 20.3 was generated at genfavicon.com. That website and favicons in general are also discussed in Chapter 2.

Figure 20.3. Bodega’s favicon

Image

As Bodega’s Revenge loads resources, it displays the Loading screen shown in Figure 20.4, which is an animated GIF of the firing turret. That animated GIF was created at picasion.com.

Figure 20.4. Bodega’s Loading screen

Image

The game’s Start screen, shown in Figure 20.5, fades in after the game loads. When the player clicks the Start button, the game begins.

Figure 20.5. Bodega’s Start screen

Image

Figure 20.6 shows the running slowly warning for Bodega’s Revenge. When the game’s average frame rate drops below 40 frames per second, the game displays the warning.

Figure 20.6. Bodega’s running slowly warning

Image

When the game completes, Bodega’s Revenge displays the Credits screen shown in Figure 20.7.

Figure 20.7. Bodega’s Credits

Image

The implementation details of the game’s loading and start screens, running slowly warning, and credits are discussed elsewhere in this book. The differences between those screens in Snail Bait vs. Bodega’s Revenge are entirely cosmetic, relegating them to HTML and CSS that is not discussed in this chapter.

20.2. Create the Spritesheet

The sprite sheet for Bodega’s Revenge is shown in Figure 20.8.

Figure 20.8. Bodega’s spritesheet

Image

As is the case for Snail Bait, the graphics for Bodega’s Revenge comes from Replica Island. The sprite sheet for Bodega’s Revenge was created by dragging individual images from Replica Island into a diagramming tool (Omni Graffle for the Mac) and then exporting the project as a PNG file.


Image Note: Transparent backgrounds for sprite sheets

When you export a sprite sheet from a diagramming tool or drawing software, it’s important to specify a transparent background; otherwise, you end up with sprites embedded in blocks of the default background color, usually white. In Figure 20.8, the sprite sheet’s transparent background is gray so that you can see the white part of the explosion and the fire at the end of the turret.


20.3. Instantiate the Game

Bodega’s Revenge is a JavaScript object with a constructor function and prototype, as shown in Example 20.1.

Example 20.1. Creating the game object


var BodegasRevenge = function () { // constructor
   this.CLOCKWISE = 0;
   this.COUNTER_CLOCKWISE = 1;
   ...

   this.canvas  = document.getElementById('game-canvas');
   this.context = this.canvas.getContext('2d');
   ...
};

BodegasRevenge.prototype = { // methods
   createSprites: function () {
      ...
   },
   ...
};


The game creates an instance of BodegasRevenge with the JavaScript new operator, as shown in Example 20.2.

Example 20.2. Instantiating the game


var game = new BodegasRevenge();


With user interface preliminaries out of the way and a game object in hand, let’s see how to implement Bodega’s Revenge, starting with the implementation of its sprites.


Image Note: From here on out, it’s all sprites and behaviors

Using the techniques discussed in this book, implementing gameplay consists almost entirely of implementing sprites and their behaviors. The rest of this chapter concentrates on the implementation of the sprites of Bodega’s Revenge.


20.4. Implement Sprites

Bodega’s Revenge has three types of sprites: a turret, bullets, and birds. Those sprites are created by the game’s createSprites() method, listed in Example 20.3.

Example 20.3. Creating sprites


BodegasRevenge.prototype = {
   ...

   createSprites: function () {
      this.createTurret();
      this.createBullets();
      this.createBirds();
   },
   ...
};


We discuss the three methods that createSprites() invokes in the sections that follow.

20.4.1. The Turret

The turret rotates around the center of the circular part of the turret and shoots bullets. The game creates the turret with the createTurret() method, listed in Example 20.4.

The createTurret() method creates the turret sprite with its artist and array of behaviors and sets the sprite’s properties. The artist is discussed in the next section and the turret’s behaviors are discussed in Section 20.5.1, “Turret Behaviors,” on p. 529.

Example 20.4. Creating the turret sprite


var BodegasRevenge = function () {
   ...

   this.turretData = { left: 50, top: 180 };

   this.TURRET_WIDTH  = 68;
   this.TURRET_HEIGHT = 40;

   this.TURRET_CENTER_X = 20;
   this.TURRET_CENTER_Y = 20;

   this.TURRET_CYCLE_DURATION = 10;
   ...
};

BodegasRevenge.prototype = {
   ...

   createTurret: function () {
      this.turret = new Sprite('turret',          // type
                       this.createTurretArtist(), // artist
                       [                          // behaviors
                         this.createTurretRotateBehavior(),
                         this.createTurretBarrelFireBehavior(),
                         this.createTurretShootBehavior(),
                         new CycleBehavior(this.TURRET_CYCLE_DURATION)
                       ]
                    );

      this.turret.left   = this.turretData.left;
      this.turret.top    = this.turretData.top;
      this.turret.width  = this.TURRET_WIDTH;
      this.turret.height = this.TURRET_HEIGHT;
      this.turret.radius = this.TURRET_WIDTH - this.TURRET_CENTER_X;
   },
   ...
};


20.4.1.1. Create the Turret Sprite’s Artist

The createTurretArtist() method, which creates the turret’s artist, is listed in Example 20.5.

Example 20.5. Creating the turret sprite’s artist


var BodegasRevenge = function () {
   ...

   this.turretRotation = 0;

   this.turretCell = [
      {left:  12, top: 120, width: 68, height: 40}
   ];
};

BodegasRevenge.prototype  =  {
   ...

   createTurretArtist: function () {
      var turretArtist = new SpriteSheetArtist(this.spritesheet,
                                this.turretCell);

      // Override turret artist's draw method to rotate the turret
      // before drawing it

      turretArtist.draw = function (sprite, context) {
         context.translate(sprite.left + game.TURRET_CENTER_X,
                           sprite.top  + game.TURRET_CENTER_Y);

         context.rotate(game.turretRotation);

         context.drawImage(this.spritesheet, // image
                           this.cells[this.cellIndex].left, // source x
                           this.cells[this.cellIndex].top,  // source y
                           game.TURRET_WIDTH,     // source width
                           game.TURRET_HEIGHT,    // source height
                           -game.TURRET_CENTER_X, // destination x
                           -game.TURRET_CENTER_Y, // destination y
                           game.TURRET_WIDTH,     // destination width
                           game.TURRET_HEIGHT);   // destination height
      };

      return turretArtist;
   },
   ...
};


The turret’s artist is a sprite sheet artist. Recall from Chapter 7 that sprite sheet artists draw their sprites by copying a rectangular region from a sprite sheet into the game’s canvas.

20.4.1.2. Draw the Turret

Because the turret can rotate, drawing the turret is not as simple as merely copying a rectangular region from the game’s sprite sheet; instead, the turret artist’s draw() method translates the coordinate system of the game’s canvas to the center of rotation and subsequently rotates it through the game’s turret rotation angle, depicted in Figure 20.9, before copying the rectangle from the sprite sheet. See Chapter 3 for more about translating and rotating the canvas context.

Figure 20.9. Tracking the turret’s rotation

Image

The turret rotation angle, stored in the game’s turretRotation property, is set by the turret’s behaviors, which we discuss in Section 20.5.1, “Turret Behaviors,” on p. 529.


Image Note: Apparent horizontal motion vs. apparent rotation

Recall that Snail Bait’s apparent horizontal motion is the result of translating the coordinate system of the game’s canvas. In a similar fashion, Bodega’s Revenge makes it appear as though the game’s turret is rotating by translating and rotating the coordinate system of the game’s canvas.


20.4.2. Bullets

Bodega’s Revenge creates bullet sprites with its createBullets() method, listed in Example 20.6.

Example 20.6. Creating bullet sprites


var BodegasRevenge = function () {
   ...
   this.NUM_BULLETS      = 20;

   this.BULLET_WIDTH     = 11;
   this.BULLET_HEIGHT    = 11;

   this.BULLET_CELL_LEFT = 18;
   this.BULLET_CELL_TOP  = 4;

   this.bulletCell = {
      left:   this.BULLET_CELL_LEFT,
      top:    this.BULLET_CELL_TOP,
      width:  this.BULLET_WIDTH,
      height: this.BULLET_HEIGHT
   };
   ...
};

BodegasRevenge.prototype = {
   ...

   createBullet: function (artist, moveBehavior) {
      bullet = new Sprite('bullet',
                          artist,
                          [ moveBehavior ]);

      bullet.width   = this.BULLET_WIDTH;
      bullet.height  = this.BULLET_HEIGHT;
      bullet.visible = false;

      return bullet;
   },

   createBullets: function ()  {
      var artist = this.createBulletArtist(this.bulletCell,
                                           this.BULLET_WIDTH,
                                           this.BULLET_HEIGHT),

          moveBehavior = this.createBulletMoveBehavior(),
          bullet,
          i;

      this.bullets = [];

      for (i=0; i < this.NUM_BULLETS; ++i) {
         this.bullets[i] = this.createBullet(artist, moveBehavior);
      }

      this.lostBulletIndex = this.bullets.length;
   },
   ...
};


The preceding createBullets() method creates the game’s bullets, all of which are drawn by a single bullet artist. Bullets have a single behavior—also shared by all bullets—that moves them along their initial trajectory when they left the turret.

Bodega’s Revenge uses the lostBulletIndex property to track which bullets have been lost as a result of birds flying behind the turret and off the left edge of the canvas. We discuss the lostBulletIndex property in Section 20.5.3.1, “The Bird Move Behavior,” on p. 540.

The bullet artist is created by the createBulletArtist() method, listed in Example 20.7.

Example 20.7. Creating the bullet artist


BodegasRevenge.prototype = {
   ...
   createBulletArtist: function (bulletCell) {
      return {
         draw: function (sprite, context) {
            context.translate(game.turret.left + game.TURRET_CENTER_X,
                              game.turret.top  + game.TURRET_CENTER_Y);

            context.rotate(sprite.trajectory);

            context.drawImage(game.spritesheet,
               bulletCell.left, bulletCell.top,   // sourcex, sourcey
               sprite.width, sprite.height,       // sourcew, sourceh
               sprite.distanceAlongTrajectory, 0, // destx, desty
               sprite.width, sprite.height);      // destw, desth
          }
       };
   },
   ...
};


Bullets maintain two interesting properties that their artist uses to draw them: trajectory and distanceAlongTrajectory, as illustrated in Figure 20.10.

Figure 20.10. A bullet’s trajectory and distance

Image

The bullet’s trajectory property represents the game’s turret rotation at the time the turret fired the bullet. The distanceAlongTrajectory property represents how far the bullet has moved along the trajectory.

To draw a bullet, the bullet artist translates the coordinate system of the game’s canvas to the center of the circular part of the turret and rotates the coordinate system through the bullet’s trajectory. That translation and subsequent rotation let the bullet artist use the bullet’s distanceAlongTrajectory property as the X coordinate in the call to the canvas context’s drawImage() method.


Image Note: Flyweights

Bullets share a single artist and move behavior, which means those objects are flyweights. See Chapter 7 for more about flyweights and how to implement them.



Image Note: Temporary translations and rotations

The HTML5 canvas context’s translate() and rotate() methods translate and rotate the canvas’s coordinate system thus affecting graphics operations such as drawing images with drawImage(). The underlying sprite implementation saves the graphics context before drawing sprites and restores it afterwards, so calls such as translate() and rotate() are temporary instead of permanent. See Chapter 3 for more information about saving and restoring the graphics context.


20.4.3. Birds

The createBirds() method, which creates the game’s birds, is listed in Example 20.8.

Example 20.8. Creating birds


var BodegasRevenge = function () {
   ...

   this.BIRD_CYCLE_RATE_BASE = 500;
   this.BIRD_CYCLE_RATE_MAX_ADDITION = 500;

   this.EXPLOSION_CYCLE_DURATION = 50;

   this.birdCells = [
      {
         left:   9,
         top:    20,
         width:  this.BIRD_WIDTH,
         height: this.BIRD_HEIGHT
      },

      {
         left:   49,
         top:    30,
         width:  this.BIRD_WIDTH,
         height: this.BIRD_HEIGHT
      },

      {
         left:   89,
         top:    15,
         width:  this.BIRD_WIDTH,
         height: this.BIRD_HEIGHT
      }
   ];
   ...
};

BodegasRevenge.prototype = {
   ...

   createBird:  function  () {

      var cycleRate = (game.BIRD_CYCLE_RATE_BASE +
                       game.BIRD_CYCLE_RATE_MAX_ADDITION
                       * Math.random()).toFixed(0),

           bird = new  Sprite('bird',
                         new SpriteSheetArtist(game.spritesheet,
                                                game.birdCells),
                           [
                              this.createExplosionBehavior(),
                              this.createBirdMoveBehavior(),
                              this.createBirdCollideBehavior(),
                              new  CycleBehavior(cycleRate)
                            ]);
      return bird;
   },

   createBirds:  function  ()  {
      var i;

      this.birds =  [];

      for (i=0; i < this.NUM_BIRDS; ++i) {
         this.birds[i] = this.createBird();
         this.initializeBirdProperties(this.birds[i]);
      }
   },
   ...
};


Birds fly, collide with bullets, and explode, so they have three behaviors that implement that functionality. Additionally, birds have a cycle behavior, discussed in Chapter 7, that continuously cycles birds through their animation cells to make it appear as though the birds are flapping their wings and bobbing up and down as they fly.

To make it appear as though the birds flap their wings at different rates of speed, Bodega’s Revenge uses a technique, discussed in Chapter 9, that adds a random amount to a fixed base to randomly vary the rate at which birds cycle through their animation cells, and as a result, flap their wings.

Bodega’s Revenge uses the same technique to randomly vary each bird’s position and velocity, as you can see in Example 20.9, which lists the game’s initializeBirdProperties() method.

Example 20.9. Initializing bird properties


BodegasRevenge.prototype = {
   ...

   initializeBirdProperties:  function (bird) {
      bird.width   = this.BIRD_WIDTH;
      bird.height  = this.BIRD_HEIGHT;
      bird.visible = true;

      bird.left = this.BIRD_LEFT_BASE +
                  this.BIRD_LEFT_MAX_ADDITION * Math.random();

      bird.top  = this.BIRD_TOP_BASE  +
                  this.BIRD_TOP_MAX_ADDITION  * Math.random();

      bird.velocity = this.BIRD_VELOCITY_BASE +
                      this.BIRD_VELOCITY_MAX_ADDITION * Math.random();

      bird.value = this.BIRD_VALUE;

      bird.flying = false;
      bird.exploding = false;
   },
   ...
};


Up to this point, you’ve seen how Bodega’s Revenge creates and draws its sprites. Now lets see how the game implements sprite behaviors.

20.5. Implement Sprite Behaviors

Sprite artists draw sprites with a draw() method. Each sprite has a single artist. Every animation frame, the game iterates over its sprites and for every visible sprite, invokes the sprite artist’s draw() method.

Sprite behaviors encapsulate sprite functionality, such as flying or shooting, with an execute() method. Each sprite has an array of behaviors. Every animation frame, the game invokes the execute() method of each behavior for every visible sprite.

Most sprite behaviors do nothing until they are tripped by a trigger. Triggers are conditions that cause behaviors to take action. They are typically boolean variables attached to sprites; for example, the turret’s shoot behavior shoots bullets only when the turret’s shooting property is true. The game temporarily sets that property to true when the player presses the spacebar to shoot a bullet.

Because behavior-based games encapsulate actions associated with sprites, much of a game’s gameplay is implemented in sprite behavior objects. See [Missing XREF!] for a more in-depth discussion of sprite behaviors.

Bodega’s Revenge has a total of eight behaviors:

• Turret

• Rotate

• Barrel fire

• Shoot

• Cycle

• Bullets

• Move

• Birds

• Move

• Collide

• Explode

The rest of this chapter discusses the implementation of the preceding behaviors.

20.5.1. Turret Behaviors

Turret behaviors are summarized in Table 20.1.

Table 20.1. Turret behaviors

Image

Let’s look at the implementation of each behavior.

20.5.1.1. The Turret’s Rotate Behavior

The game attaches a keydown event handler to the window object. That event handler sets the turret’s rotating and direction properties, as shown in Example 20.10. A corresponding onkeyup event handler sets the rotating property back to false.

Example 20.10. Tripping the rotate behavior


window.addEventListener(
   'keydown',

   function (e) {
      ...

      if (key === 68 || key === 37) { // 'd' or left arrow
         game.turret.rotating  = true;
         game.turret.direction = game.CLOCKWISE;
      }

      else if (key === 75 || key === 39) { // 'k' or right arrow
         game.turret.rotating  = true;
         game.turret.direction = game.COUNTER_CLOCKWISE;
      }
      ...
   }
);

window.addEventListener(
   'keyup',

   function (e) {
      game.turret.rotating = false;
   }
);


The turret’s rotate behavior takes action depending on the values of the turret’s rotating and direction properties, as shown in Example 20.11.

Example 20.11. Creating the turret rotate behavior


BodegasRevenge.prototype = {
   ...

   createTurretRotateBehavior: function () {
      return {
         execute: function (sprite, now, fps, context,
                            lastAnimationFrameTime) {
            var ROTATE_INCREMENT = Math.PI/100;

            if(sprite.rotating) {
               if (sprite.direction === game.CLOCKWISE) {
                  game.turretRotation -= ROTATE_INCREMENT;
               }
               else if (sprite.direction === game.COUNTER_CLOCKWISE) {
                  game.turretRotation += ROTATE_INCREMENT;
               }
            }
         }
      };
   },
   ...
};


Recall from Section 20.4.1.1, “Create the Turret Sprite’s Artist,” on p. 520 that the turret’s artist rotates the coordinate system for the game’s canvas through the game.turretRotation angle. When the turret’s rotating property is true, the rotate behavior increments or decrements that angle depending on the value of the turret’s direction property.

20.5.1.2. The Turret’s Barrel Fire Behavior

When the turret shoots bullets, its barrel fire behavior darkens the center of the turret and shoots fire from the tip of the turret’s barrel, as shown in Figure 20.11.

Figure 20.11. The turret’s barrel fire

Image

The game’s sprite sheet contains the five images of the turret shown in Figure 20.12. When the turret is not firing bullets, the game displays the image on the far left in Figure 20.12. When it fires a bullet, the turret’s barrel fire behavior displays the remaining four images in succession from left to right.

Figure 20.12. Turret cells

Image

Both the barrel fire behavior and the turret’s shoot behavior, which we discuss in the next section, are triggered by the game’s shooting property. When the player presses the spacebar, the window object’s keydown event handler sets the property’s value to true, as you can see in Example 20.12.

Example 20.12. Tripping the shoot and barrel fire behaviors


window.addEventListener(
   'keydown',

   function (e) {
      var key = e.keyCode;
      ...

      else if (key === 32) {  // spacebar
         game.turret.shooting = true;
      }
      ...
   }
);


The barrel fire behavior is created by the game’s createBarrelFireBehavior() method, listed in Example 20.13.

Example 20.13. Creating the turret barrel fire behavior


var BodegasRevenge = function ()  {
   ...
   this.turretFiringCells = [
      {left: 91,  top: 120, width:  68, height: 40},
      {left: 170, top: 121, width:  68, height: 40},
      {left: 250, top: 122, width:  68, height: 40},
      {left: 329, top: 123, width:  68, height: 40}
   ];

   this.TURRET_CYCLE_DURATION  = 10; // Cycle every 10 ms

   this.TURRET_FIRING_DURATION = this.TURRET_CYCLE_DURATION *
                                 this.turretFiringCells.length;
   ...
};

BodegasRevenge.prototype = {
   ...

   createTurretBarrelFireBehavior: function () {
      return  new  CellSwitchBehavior(
         game.turretFiringCells,      // Temporary animation cells
         game.TURRET_FIRING_DURATION, // Duration in ms to display cells

         function (sprite, now, fps, lastAnimationFrameTime) { // Trigger
            return sprite.shooting;
         },

         function (sprite) {  //  Callback
            sprite.shooting =  false;
         }
      );
   },
   ...
};


The barrel fire behavior is an instance of CellSwitchBehavior, which temporarily switches the cells that a sprite’s artist uses to draw the sprite. Cell switch behaviors have a trigger function. If that function returns true, the cell switch behavior takes action, either setting the animation cells for the associated sprite’s artist or cycling through those cells. When a cell switch behavior is done cycling through the temporary animation cells, it restores the original cells and invokes its callback function. Cell switch behaviors are discussed in more detail in Chapter 13.


Image Note: The turret’s cycle behavior

The turret has a cycle behavior that continuously cycles through the turret’s animation cells. Initially, however, as you can see from Example 20.5 on p. 521, the turret has only one animation cell, so the cycle behavior has no noticeable effect. The cycle behavior does have an effect however, when the turret’s barrel fire behavior switches the turret’s animation cells to the four cells shown on the right in Figure 20.12. Without the cycle behavior, the barrel fire behavior would still switch the turret’s animation cells, but it would not cycle through those cells and therefore show only the second cell from the left in Figure 20.12.


20.5..2.1. The turret’s shoot behavior

The turret’s shoot behavior, listed in Example 20.14, shoots bullets by obtaining a reference to the first available bullet, making it visible, and placing it at the tip of the turret’s barrel.

As long as the turret’s shooting property is true, the shoot behavior, which the game invokes every animation frame, shoots bullets, provided that enough time has elapsed since the behavior fired the last bullet. That pause between shots, which is 1.5 times longer than it takes for the turret to fire, puts a space between bullets when the player holds down the spacebar and shoots bullets in rapid succession.

Example 20.14. Creating the turret shoot behavior


var BodegasRevenge = function () {
   ...

   this.TURRET_SHOT_INTERVAL =  this.TURRET_FIRING_DURATION * 1.5;
   ...
};

BodegasRevenge.prototype = {
   ...

   createTurretShootBehavior: function () {
      return  {
         lastTimeShotFired: 0,

         ...

         execute: function (sprite, now, fps, context,
                            lastAnimationFrameTime) {
            var elapsed = now - this.lastTimeShotFired;

            if(sprite.shooting && elapsed > game.TURRET_SHOT_INTERVAL)  {
               bullet = this.getBullet();

               if (bullet) {
                  bullet.visible = true;
                  bullet.trajectory = game.turretRotation;
                  bullet.distanceAlongTrajectory = game.turret.radius;

                  this.lastTimeShotFired = now;

                  game.playSound(game.turretFiringSound);
               }
            }
          }
       };
   },
   ...
};


The turret’s shoot behavior obtains a reference to the first available bullet with the getBullet() method, listed in Example 20.15.

Example 20.15. Getting the first available bullet


BodegasRevenge.prototype = {
   ...

   createTurretShootBehavior: function () {
      return {
         ...

         getBullet: function () {
            var bullet,
                i;

            for (i=0; i < game.lostBulletIndex; ++i) {
               bullet = game.bullets[i];

               if (!bullet.visible) {
                  return bullet;
               }
            }
            return  null;
         }
      };
   },
   ...
};


The getBullet() method iterates over the game’s bullets array up to the lostBulletIndex and returns the first invisible bullet. If the lostBulletIndex is zero or all available bullets are visible, the getBullet() method returns null and the turret fires a blank. We discuss the lostBulletIndex property in Section 20.5.3.1, “The Bird Move Behavior,” on p. 540.


Image Note: Incorporate sounds into the game

Bodega’s Revenge plays a sound when the turret fires a bullet and another sound when a bird explodes. Like the game’s graphics, those sounds come from Replica Island.


20.5.2. Bullet Behaviors

Bullets have only one behavior. That behavior, which moves bullets along their trajectory, is created by the createBulletMoveBehavior() method, listed in Example 20.16.

Example 20.16. Creating the bullet move behavior


BodegasRevenge.prototype = {
   ...

   createBulletMoveBehavior: function () {
      return {
         ...

         execute: function (sprite, now, fps, context,
                            lastAnimationFrameTime) {
            var BULLET_VELOCITY = 450, // pixels / second
                location;

            sprite.distanceAlongTrajectory += BULLET_VELOCITY / fps;

            location = this.getBulletLocation(sprite);

            if (this.isBulletOutOfPlay(location)) {
               sprite.visible = false;
            }
            else {
               sprite.left = location.left;
               sprite.top  = location.top;
            }
         }
      };
   },
   ...
};


When bullets are visible, the bullet’s move behavior moves them along their trajectory at a constant rate of 450 pixels/second regardless of the frame rate of the game’s underlying animation. The behavior decouples the bullet’s speed from the animation’s frame rate with time-based motion—discussed in Chapter 3—to calculate how far to move the bullet along its trajectory. The behavior moves the bullet to that location when the bullet is in play or makes it invisible when it goes out of play. Making the bullet invisible makes it immediately available for firing, assuming the bullet has not previously been lost as a result of a bird flying off the left edge of the canvas.

The bullet’s move behavior invokes two helper methods—getBulletLocation() and isBulletOutOfPlay(); the former is listed in Example 20.17.

Example 20.17. Getting a bullet’s location


BodegasRevenge.prototype = {
   ...

   createBulletMoveBehavior: function () {
      return {
         ...

         getBulletLocation: function (bullet) {
            return game.polarToCartesian(
                game.turret.left + game.TURRET_CENTER_X, // x
                game.turret.top  + game.TURRET_CENTER_Y, // y
                bullet.distanceAlongTrajectory,          // radius
                bullet.trajectory);                      // angle
         },
         ...
      };
   },
   ...
};


Recall from Figure 20.10 on p. 525 that bullets store their position as the angle of their trajectory and the distance along that trajectory from the center of the turret. Mathematically speaking, that angle and trajectory, which are depicted in Figure 20.13, are known as polar coordinates.

Figure 20.13. Polar coordinates

Image

Because the rest of the game, along with the HTML5 canvas context, deal strictly in Cartesian coordinates, we convert the polar coordinates stored in each bullet to Cartesian coordinates with the more general polarToCartesian() method, listed in Example 20.18.

Example 20.18. Converting polar coordinates


BodegasRevenge.prototype = {
   ...

   polarToCartesian: function (px, py, r, angle) {
      var x = px + r * Math.cos(angle),
          y = py + r * Math.sin(angle);

      return { left: x, top: y };
   },
   ...
};


The polarToCartesian() method implements the math for converting polar coordinates to Cartesian coordinates. Given the location of a bullet in Cartesian coordinates, it’s a simple matter to determine if a bullet is out of play, as shown in Example 20.19.

Example 20.19. Determining if a bullet is out of play


BodegasRevenge.prototype = {
   ...

   createBulletMoveBehavior: function () {
      return {
         ...

         isBulletOutOfPlay: function (location) {
            return location.left < 0                 ||
                   location.left > game.canvas.width ||
                   location.top  < 0                 ||
                   location.top  > game.canvas.height;
         },
         ...
      };
   },
   ...
};


Now that you’ve seen how bullets behave, let’s turn our attention to the birds of Bodega’s Revenge.

20.5.3. Bird Behaviors

Birds have four behaviors, listed in Table 20.2.

Table 20.2. Bird behaviors

Image

With the exception of the cycle behavior, whose implementation is discussed in [Missing XREF!], let’s look at the implementations of each of the preceding behaviors.

20.5.3.1. The Bird Move Behavior

Like bullets, birds have a move behavior. That behavior, which moves birds from right to left horizontally, is listed in Example 20.20.

The bird move behavior, like the bullet move behavior, moves birds by using time-based motion to ensure that birds move at a constant rate of speed regardless of the frame rate of the game’s underlying animation.

Example 20.20. Creating the bird move behavior


BodegasRevenge.prototype = {
   ...

   createBirdMoveBehavior: function () {
      return {
         execute: function (sprite, now, fps, context,
                            lastAnimationFrameTime) {
            if (!sprite.exploding) {
               sprite.left -= sprite.velocity / fps;

               if (sprite.left + sprite.width < 0) {
                  sprite.left = game.canvas.width - sprite.width;
                  game.loseOneBullet();
               }
            }
         }
      }
   },
   ...
};


The loseOneBullet() method, which the move behavior calls when the bird moves off the left edge of the canvas, is listed in Example 20.21.

Example 20.21. Losing a bullet


BodegasRevenge.prototype = {
   ...

   loseOneBullet: function () {
      if (this.lostBulletIndex === 0) {
         this.gameOver();
      }
      else {
         this.lostBulletIndex--;
      }
      ...
   },
   ...
};


Players lose a bullet—meaning the bullet is no longer in play—when a bird flies behind the turret and off the left edge of the canvas. The game keeps track of lost bullets with the lostBulletIndex property, as depicted in Figure 20.14.

Figure 20.14. The lost bullet index

Image

The lostBulletIndex is originally 20, which places it beyond the end of the bullets array, as shown in the top picture in Figure 20.14. That value for lostBulletIndex means all bullets are initially in play.

Every time a bird flies behind the turret and off the left edge of the game’s canvas, the loseOneBullet() method decrements the lostBulletIndex property. The middle illustration in Figure 20.14 shows the lostBulletIndex after the player has lost 13 bullets. The game ends when, as shown in the bottom illustration in Figure 20.14, the lostBulletIndex is zero.

The lostBulletIndex restricts the number of bullets because methods such as getBullet(), listed in Example 20.15 on p. 536 and the bird’s collide behavior, discussed in Section 20.5.3.2, “The Bird Collide Behavior,” on p. 542, iterate over the game’s bullets array like this: for (i=0; i < game.lostBulletIndex; ++i) { ... }; instead of iterating over the entire bullets array.

20.5.3.2. The Bird Collide Behavior

The bird collide behavior, listed in Example 20.22, iterates over the game’s bullets (up to the lostBulletIndex as discussed in the previous section), checking for collisions between the bullets and the bird under consideration.

Example 20.22. Creating the bird collide behavior


var BodegasRevenge = function () {
   ...

   this.BIRD_EXPLOSION_TOP_DELTA = 20;
   this.BIRD_EXPLOSION_LEFT_DELTA = 20;
   ...
};
BodegasRevenge.prototype = {
   ...

   createBirdCollideBehavior: function () {
      return {
         ...

         execute: function (sprite, now, fps, context,
                            lastAnimationFrameTime) {
            var bullet,
                i;

            for (i=0; i < game.lostBulletIndex; ++i) {
               bullet = game.bullets[i];

               if (bullet.visible && !sprite.exploding) {
                  if (this.isBulletInsideBird(context,
                                              bullet,
                                              sprite)) {
                     sprite.left -= game.BIRD_EXPLOSION_LEFT_DELTA;
                     sprite.top -= game.BIRD_EXPLOSION_TOP_DELTA;

                     game.explode(sprite);

                     bullet.visible = false;

                     game.score += sprite.value;
                     game.updateScoreElement();

                     break;
                  }
               }
            }
         }
      };
   },
   ...
};


Recall that the sprite in the behavior’s execute()A collision between bullet and bird takes place when the bullet is visible, the bird is not exploding, and the bullet is inside the bird. In that case, the bird collide behavior adjusts the bird’s position to account for the size difference between bird animation cells and explosion animation cells (without adjusting the bird’s positions, explosions appear below and to the right of the bird that’s exploding).

After adjusting the bird’s position, the bird collide behavior explodes the bird, makes the bullet invisible, and updates the game’s score.

The isBulletInsideBird() method, listed in Example 20.23, determines whether a bullet lies within a bird by using the HTML5 canvas context’s isPointInPath() method, which we discussed in Chapter 11.

Example 20.23. Hit detection with the HTML5 canvas context


BodegasRevenge.prototype = {
   ...

   createBirdCollideBehavior: function () {
      return {
         ...

         isBulletInsideBird: function (context, bullet, bird) {
            context.beginPath();

            context.rect(bird.left, bird.top,
                         bird.width, bird.height);

            return context.isPointInPath(bullet.left + bullet.width/2,
                                         bullet.top + bullet.height/2);
         },
         ...
      };
   },
   ...
};


20.5.3.3. The Bird Explosion Behavior

Birds explode with an explosion behavior, listed in Example 20.24. That behavior is an instance of CellSwitchBehavior, which temporarily changes the bird’s animation cells to the game’s explosion cells for the specified duration. The behavior takes action when the bird’s exploding property is true; and when the bird has exploded, the behavior invokes its callback, which initializes the bird’s properties to their initial state.

Example 20.24. Creating the explosion behavior


BodegasRevenge.prototype = {
   ...

   createExplosionBehavior: function () {
      return new CellSwitchBehavior(
         game.explosionCells,
         game.EXPLOSION_DURATION,

         function (sprite, now, fps,
                   lastAnimationFrameTime) { // trigger
            return sprite.exploding;
         },

         function (sprite) { // callback
            game.initializeBirdProperties(sprite);
         }
      );
   },
   ...
};


20.6. Draw the Bullet Canvas

Bodega’s Revenge displays the number of remaining bullets above the game’s canvas, as shown in Figure 20.15, by drawing actual bullets. In this section we take a look at the implementation of that bullet canvas.

Figure 20.15. The bullet canvas

Image

The bullet canvas is an HTML5 canvas element, declared in the game’s HTML file, as in Example 20.25.

Example 20.25. The bullet canvas element


<canvas id='bullet-canvas' width='450' height='30'>
   Your browser does not support HTML5 Canvas.
</canvas>


Bodega’s Revenge redraws the bullet canvas every time the player loses a bullet, as you can see in Example 20.26, which shows a revised listing of the game’s loseOneBullet() method.

Example 20.26. Losing a bullet, revised


BodegasRevenge.prototype = {
   ...

   loseOneBullet: function () {
      if (this.lostBulletIndex === 0) {
         this.gameOver();
      }
      else {
         this.lostBulletIndex--;
      }

      this.eraseBulletCanvas();
      this.drawBulletCanvas();
   },
   ...
};


The eraseBulletCanvas() method is listed in Example 20.27.

Example 20.27. Erasing the bullet canvas


BodegasRevenge.prototype = {
   ...

   eraseBulletCanvas: function () {
      this.bulletContext.clearRect(
         0,0,
         this.bulletCanvas.width,
         this.bulletCanvas.height);
   },
   ...
};


The eraseBulletCanvas() method erases the canvas by invoking the canvas context’s clearRect() method to clear the entire canvas.

The drawBulletCanvas() method is listed in Example 20.28.

Example 20.28. Drawing the bullet canvas


BodegasRevenge.prototype = {
   ...

   drawBulletCanvas: function () {
      var context = this.bulletContext,
          firstBullet = game.bullets[0],
          TOP_EDGE = 0,
          LEFT_EDGE = 6,
          HORIZONTAL_SPACING = 16,
          VERTICAL_SPACING = 16,
          row = 0,
          col = 0,
          i;

      for (i=0; i < game.lostBulletIndex; ++i) {
            context.drawImage(game.spritesheet,
               game.bulletCell.left,                // sourcex
               game.bulletCell.top,                 // sourcey
               firstBullet.width, firstBullet.height,   // sourcew, sourceh
               LEFT_EDGE + HORIZONTAL_SPACING*col,      // destx
               TOP_EDGE  + VERTICAL_SPACING*row,        // desty
               firstBullet.width, firstBullet.height);  // destw, desth

         if (i === game.bullets.length / 2 - 1) { // middle
            col = 0;
            row++;
         }
         else {
            col++;
         }
      }
   },
   ...
};


The drawBulletCanvas() method iterates over the game’s bullets up to the lostBulletIndex, and draws each bullet in the canvas.

20.7. Implementing Touch-Based Controls for Mobile Devices

Bodega’s Revenge runs on mobile devices, as you can see in Figure 20.16.

Figure 20.16. Bodega’s Revenge on mobile devices

Image

Bodega’s Revenge uses the techniques discussed in Chapter 15 to run on mobile devices. The implementation details are nearly identical to Snail Bait’s support for mobile devices, so those discussions are not repeated in this chapter.

The biggest difference between Bodega’s Revenge and Snail Bait on mobile devices is the manner in which players control the game. In Bodega’s Revenge, players rotate the turret by sliding a finger or thumb up or down to rotate the turret counterclockwise or clockwise, respectively. To shoot bullets from the turret, players tap the screen. In this section, we discuss the implementation of those controls.

The game’s touch-start event handler is listed in Example 20.29.

Example 20.29. The touch start event handler


game.canvas.addEventListener(
   'touchstart',

   function (e) {
      game.turret.touchStartTime = game.timeSystem.calculateGameTime();

      game.turret.lastFingerLocation = {
         x: e.changedTouches[0].pageX,
         y: e.changedTouches[0].pageY
      };

      game.turret.armed = true;

      e.preventDefault();
   }
);


When the player touches the game’s canvas, the preceding event handler records the time of the touch and the touch’s location. Additionally, the event handler arms the turret and prevents the browser from reacting to the touch event in its default manner.

When the player subsequently moves a finger or thumb, the browser invokes the event handler listed in Example 20.30.

Example 20.30. The touch move event handler


game.canvas.addEventListener(
   'touchmove',

   function (e) {
      var MOVEMENT_THRESHOLD = 3;

      if (game.turret.armed && !game.turret.rotating) {
         game.turret.armed = false;
         game.turret.rotating = true;
      }

      if (game.turret.lastFingerLocation.y >
          e.changedTouches[0].pageY) {

         if (game.turret.lastFingerLocation.y -
            e.changedTouches[0].pageY > MOVEMENT_THRESHOLD) {
            game.turret.direction = game.CLOCKWISE;
         }
      }
      else if (e.changedTouches[0].pageY -
         game.turret.lastFingerLocation.y > MOVEMENT_THRESHOLD) {
         game.turret.direction = game.COUNTER_CLOCKWISE;
      }

      game.turret.lastFingerLocation = {
         x: e.changedTouches[0].pageX,
         y: e.changedTouches[0].pageY
      };
   }
);


When a touch-move event occurs, the turret starts rotating if it was armed. The direction the turret rotates depends on the finger’s movement. If the finger is moving down and it has moved more than three pixels since the last touch-move event, the preceding event handler sets the turret’s rotation to clockwise. Conversley, if the finger is moving up and it has moved more than three pixels since the last touch-move event, the preceding event handler sets the turret’s rotation to counterclockwise.

Finally, when the touch ends, the browser invokes the event handler listed in Example 20.31.

Example 20.31. The touch end event handler


game.canvas.addEventListener(
   'touchend',

   function (e) {
      var TAP_THRESHOLD = 200, // milliseconds
          now = game.timeSystem.calculateGameTime();

      game.turret.rotating = false;

      if (now - game.turret.touchStartTime < TAP_THRESHOLD) {
         game.turret.shooting = true;
      }
      else {
         if (game.turret.armed) {
            game.turret.armed = false;
            game.turret.rotating = true;
         }
      }

      e.preventDefault();
   }
);


The preceding event handler stops the turret’s rotation. It also checks to see if less than 200 ms has elapsed since the time of the touch start event. If it has, the event handler sets the turret’s shooting property to true, which triggers its shooting event.

20.8. Conclusion

Most of this book is concerned with the implementation of Snail Bait, which is a platformer video game of moderate complexity. Throughout this book we’ve examined various aspects of that game from its graphics and animation to its developer backdoor.

This chapter illustrated how to use what you’ve learned in the rest of the book to implement the simpler, but still nontrivial Bodega’s Revenge. At this point, you understand the fine details of the fundamentals of implementing 2D video games in addition to the bigger picture of putting those details together to implement your own game. Good luck!

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

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