HTML5 2D game development: Wrapping up the game

Put the final essential touches on Snail Bait and add some polish

In this series, HTML5 maven David Geary shows you how to implement an HTML5 2D video game one step at a time. This installment concludes the series by rounding out Snail Bail with important features and some aesthetic polish. Learn how to replace the game's background, fine-tune gameplay, keep score, dim controls, monitor frame rate, track lives, display credits, tweet scores, and deploy the game to a server.

David Geary, Author and speaker, Clarity Training, Inc.

David GearyThe author of Core HTML5 Canvas, David Geary is also the co-founder of the HTML5 Denver User's Group and the author of eight Java books, including the best-selling books on Swing and JavaServer Faces. David is a frequent speaker at conferences, including JavaOne, Devoxx, Strange Loop, NDC, and OSCON, and is a three-time JavaOne Rock Star. He is the author of the HTML5 2D game development, JSF 2 fu, and GWT fu article series for developerWorks.



23 July 2013

Also available in Chinese Russian Japanese

At the end of the preceding installment in the HTML5 2D game development series, Snail Bait is in a playable but raw state. In this concluding installment, I show you how to take Snail Bait from that state to its final version, the polished game that appears in Figure 1:

Figure 1. Snail Bait's final version
Screen capture of the final version of Snail Bait

In this article, you learn how to:

  • Replace the background image with CSS gradients.
  • Fine-tune gameplay and graphics.
  • Keep score.
  • Monitor frame rate and display a warning when the game is running slowly.
  • Implement the special effect of shaking the background.
  • Track lives.
  • Transition between lives.
  • Display credits.
  • Tweet the score.
  • Deploy games to a server.

See Download to get the full, final Snail Bail code.

Replace the background image with CSS gradients

In its current state, Snail Bait loads three images when the game starts: the background for the website, the game's background, and the game's sprite sheet. In the final version, I reduce that to one image by getting rid of the website background and incorporating the game's background into the sprite sheet. To eliminate the website background, I use CSS3 linear gradients to create the background in Figure 2:

Figure 2. Snail Bait's background
Screen capture of the game's background

The CSS for the background is in Listing 1:

Listing 1. Snail Bait's background (excerpt from game.css)
body {
   /* Background from CSS3 Patterns Gallery by Anna Kassner */

   background-color: #6d6aff;

   background-image:
      repeating-linear-gradient(120deg, rgba(255,255,255,.1), rgba(255,255,255,.1) 1px, 
                                transparent 1px, transparent 60px),

      repeating-linear-gradient(60deg, rgba(255,255,255,.1), rgba(255,255,255,.1) 1px, 
                                transparent 1px, transparent 60px),

      linear-gradient(60deg, rgba(0,0,0,.1) 25%, transparent 25%, transparent 75%, 
                      rgba(0,0,0,.1) 75%, rgba(0,0,0,.1)),

      linear-gradient(120deg, rgba(0,0,0,.1) 25%, transparent 25%, transparent 75%, 
                      rgba(0,0,0,.1) 75%, rgba(0,0,0,.1));

   background-size: 70px 120px;
}
...

The CSS3 Patterns Gallery hosts CSS snippets that you can copy to create a variety of backgrounds (see Resources). Figure 3 shows a portion of the gallery's backgrounds:

Figure 3. The CSS3 Patterns Gallery
Screen capture of part of the CSS3 Patterns Gallery

I downloaded the CSS in Listing 1 from the CCC3 Patterns Gallery to use in Snail Bait. I selected the Argyle pattern and changed the background color to a shade of blue.


Fine-tune gameplay and graphics

Game developers constantly fine-tune gameplay and graphics as they implement a game. For the final round of fine-tuning Snail Bait, I:

  • Change the locations and sizes of platforms.
  • Add bounce behaviors to all rubies and coins.
  • Move the snail to the end of the game.
  • Implement a detonating button.
  • Replace coins.

Most of these changes are evident in Figure 4, which shows the snail shooting bombs near the end of the game:

Figure 4. Characters eluding snail bombs near the end of the game
Screen capture of characters eluding snail bombs near the end of Snail Bait

About halfway through the game, the runner encounters the impasse that's shown in Figure 5: The bee is positioned between the runner and the next platform. To get past the bee and through this segment of the game, the runner must detonate the blue button, which blows up the bee.

Figure 5. Blue button for blowing up the bee that guards the next platform
Screen capture of the last segment of Snail Bait

Figure 6 shows the button detonating and the bee exploding. While sprites are exploding, Snail Bait disqualifies them from collision detection. Disqualification of the exploding bee from collision detection enables the runner to jump through the bee to the next platform.

Figure 6. The detonating button in action
Screen capture from Snail Bait showing a detonating button blowing up a bee

Like all of Snail Bait's actions, button detonation is a behavior. Listing 2 shows the implementation of the button-detonate behavior:

Listing 2. The button-detonate behavior
var SnailBait = function () {
   ...
   this.buttonDetonateBehavior = {
      execute: function(sprite, now, fps, lastAnimationFrameTime) {
         var BUTTON_REBOUND_DELAY = 1000;

         if ( ! sprite.detonating) { // trigger
            return;
         }

         sprite.artist.cellIndex = 1; // flatten the button

         snailBait.explode(snailBait.bees[5]);

         setTimeout( function () {
            sprite.artist.cellIndex = 0; // rebound
            sprite.detonating = false; // reset trigger
         }, BUTTON_REBOUND_DELAY);
      }
   }
};

Listing 3 shows how Snail Bait adds the detonate behavior to the blue button:

Listing 3. Creating the blue button with the detonate behavior
var SnailBait = function () {

SnailBait.prototype = {
   ... 
   createButtonSprites: function () {
      var button,
         blueButtonArtist = new SpriteSheetArtist(this.spritesheet, this.blueButtonCells),
         goldButtonArtist = new SpriteSheetArtist(this.spritesheet, this.goldButtonCells);

      for (var i = 0; i < this.buttonData.length; ++i) {
         if (i === this.buttonData.length - 1) {
            button = new Sprite('button', 
                                goldButtonArtist,  
                                [ this.paceBehavior ]);
         }
         else {
            button = new Sprite('button',
                                 blueButtonArtist, 
                                 [ this.paceBehavior, 
                                   this.buttonDetonateBehavior ]);
         }

         button.width = this.BUTTON_CELLS_WIDTH;
         button.height = this.BUTTON_CELLS_HEIGHT;

         this.buttons.push(button);
      }
   }, 
};

Recall that for every animation frame, Snail Bait iterates over all visible sprites. And for each sprite, Snail Bait iterates over the sprite's array of behaviors, invoking each behavior's execute() method. Most of the time, the button-detonate behavior's execute() method does nothing because the button's detonating attribute is false. When Snail Bait sets the blue button's detonating attribute to true, as in Listing 4, the button-detonate behavior:

  1. Flattens the button.
  2. Explodes the bee.
  3. Delays one second.
  4. Resets the button's original image.
  5. Sets the button's detonating attribute to false.

From then on, the detonate behavior lies dormant until the next time the runner falls on the button.

Listing 4. Tripping the detonate behavior
var SnailBait = function () {
   ...

   // The collide behavior is attached to the runner

   this.collideBehavior = {
      ...

      detonateButton: function (otherSprite) {
         otherSprite.detonating = true; // trigger
      },

      processCollision: function (sprite, otherSprite) {
         if (otherSprite.value) { 
            this.adjustScore(otherSprite);
         }

         if ('button' === otherSprite.type && sprite.falling)) {
            this.detonateButton(otherSprite);
         }
         ...
      },
   };
   ...
};

Keep score

Snail Bait has had a nonworking scoreboard ever since the first article in this series. Now it's time to make the scoreboard functional. The first step is to assign values to certain types of sprites. For example, Listing 5 shows the game's createRubySprites() method, which assigns a value of 100 points to each ruby:

Listing 5. Assigning sprite values
SnailBait.prototype = {
   ...
   createRubySprites: function () {
      var ruby,
          rubyArtist = new SpriteSheetArtist(this.spritesheet, this.rubyCells);
   
      for (var i = 0; i < this.rubyData.length; ++i) {
         ruby = new Sprite('ruby', rubyArtist, 
                   [ new CycleBehavior(this.RUBY_SPARKLE_DURATION,
                                          this.RUBY_SPARKLE_INTERVAL),
                     new BounceBehavior(800, 600, 120) ]);

         ruby.width = this.RUBY_CELLS_WIDTH;
         ruby.height = this.RUBY_CELLS_HEIGHT;
         
         ruby.value = 100;

         this.rubies.push(ruby);
      }
   },
   ...
};

As Listing 4 shows, when the runner collides with another sprite that has a value, the collide behavior's adjustScore() method adjusts the game's score and updates the HTML score element. Listing 6 shows the adjustScore() method:

Listing 6. Adjusting the score after collisions
var SnailBait = function () {
   ...
   // The collide behavior is attached to the runner

   this.collideBehavior = {
      ...
      adjustScore: function (otherSprite) {
         if (otherSprite.value) {
            snailBait.score += otherSprite.value;
            snailBait.score = snailBait.score < 0 ? 0 : snailBait.score;
            snailBait.scoreElement.innerHTML = snailBait.score;
         }
      },
      ...
   };
};

Monitor frame rate and show a warning when necessary

Unlike console games, which run in tightly controlled environments, HTML5 games run amidst chaos. Video players, OS backup software, and even something as mundane as a terminal window with a partially transparent background can bring even the best-performing games to their knees. Because you can't control the environment in which your HTML5 game runs, you must monitor the frame rate and warn players when the game is running unacceptably slowly. Figure 7 shows Snail Bait's warning:

Figure 7. The running-slowly warning
Screen capture of Snail Bait's running-slowly warning

Snail Bait displays the current frame rate when it warns players that the game is running slowly. Snail Bait does this by inserting the frame rate into otherwise static text at run time. Listing 7 shows the HTML for the static text:

Listing 7. HTML for the running-slowly warning
<html>
   <body>
      <div id='arena'>
         ...

         <!-- Running slowly........................................-->

         <div id='running-slowly'>

            <h1>Snail Bait is running slowly</h1>

            <p id='slowly-warning'></p>
            
            <p>High-performance applications, such as video players or 
              software that backs up your computer, can slow this game down. 
              For best results, hide all other windows on your desktop and 
              close CPU- or GPU- intensive apps when you play HTML5 games.
            </p>

            <p>You should also upgrade your browser to the latest version and make 
            sure that it has hardware accelerated HTML5 Canvas. Any version of Chrome 
            starting with version 18, for example, has hardware accelerated
               Canvas. Here is a link where you can download the 
               <a href='http://www.google.com/chrome/'>latest version of Chrome</a>.
            </p>

            <a id='slowly-okay' href='#'>
               Okay
            </a>

            <a  id='slowly-dont-show' href='#'>
               Do not show this warning again
            </a>
         </div>
         ...
      </div>
   </div>
</div>

Listing 8 shows how Snail Bait reveals and hides the running-slowly warning:

Listing 8. Revealing and hiding the running-slowly warning
SnailBait.prototype = {
   ...
   revealRunningSlowlyWarning: function (now, averageSpeed) {
      this.slowlyWarningElement.innerHTML =
      "Snail Bait is running at " +
      "<font color='red'> averageSpeed.toFixed(0)" + "</font>" +
      " frames/second (fps), but it needs more than " +
      this.runningSlowlyThreshold + " fps for the game to work correctly."

      this.runningSlowlyElement.style.display = 'block';

      setTimeout( function () {
         snailBait.runningSlowlyElement.style.opacity = 1.0;
      }, this.SHORT_DELAY);

      this.lastSlowWarningTime = now;
   },  

   hideRunningSlowlyWarning: function () {
      snailBait.runningSlowlyElement.style.display = 'none'; 
      snailBait.runningSlowlyElement.style.opacity = 0;
   },
   ...
};

When Snail Bait reveals the running-slowly warning, it creates the warning's text, which it assigns to the slowly-warning paragraph element. Then it sets that element's display attribute to block so that the browser displays it. However, the element is initially transparent, so after a short delay, the revealRunningSlowlyWarning() method sets the element's opacity to 1.0 to make it opaque. That setting triggers a CSS transition that fades the element from transparent to opaque for 1 second. Listing 9 shows the CSS for the running-slowly warning element:

Listing 9. CSS for the running-slowly warning element
#running-slowly {
   position: absolute;
   width: 600px;
   margin-top: 85px;
   margin-left: 90px;
   text-align: center;
   background: rgba(255,255,255,0.85);
   text-align: left;
   padding: 0px 20px 20px 20px;
   color: navy;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.5);

   -webkit-transition: opacity 1s;
   -moz-transition: opacity 1s;
   -o-transition: opacity 1s;
   transition: opacity 1s;
   
   -webkit-box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;
   -moz-box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;
   -o-box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;
   box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;

   opacity: 0;
   display: none;

   z-index: 2;
}

As Snail Bait runs, it monitors frame rate, as shown in Listing 10:

Listing 10. Monitoring frame rate
SnailBait.prototype = {
   ...
   animate: function (now) { 
      if (snailBait.paused) {
         setTimeout( function () {
            requestNextAnimationFrame(snailBait.animate);
         }, snailBait.PAUSED_CHECK_INTERVAL);
      }
      else {
         snailBait.fps = snailBait.calculateFps(now); 

         if (snailBait.windowHasFocus && !snailBait.paused &&
             snailBait.showSlowWarning &&
             now - snailBait.lastSlowWarningTime > 
             snailBait.FPS_SLOW_CHECK_INTERVAL) {
            snailBait.checkFps(now); 
         }

         snailBait.draw(now);
         requestNextAnimationFrame(snailBait.animate);
      }
   },

   checkFps: function (now) {
      var averageSpeed;

      this.updateSpeedSamples(snailBait.fps);

      averageSpeed = this.calculateAverageSpeed();

      if (averageSpeed < this.runningSlowlyThreshold) {
         this.revealRunningSlowlyWarning(now, averageSpeed);
      }
   },
   ...
};

Every FPS_SLOW_CHECK_INTERVAL seconds (which is set at 4 seconds), Snail Bait checks the frame rate by updating an array of speed samples and calculating the average speed. If the average speed is less than the game's running-slowly threshold, Snail Bait reveals the running-slowly warning. Listing 11 shows Snail Bait's methods for updating speed samples and calculating average speed:

Listing 11. Updating speed samples and calculating average speed
SnailBait.prototype = {
   ...
   updateSpeedSamples: function (fps) {
      this.speedSamples[this.speedSamplesIndex] = fps;

      if (this.speedSamplesIndex !== this.NUM_SPEED_SAMPLES-1) {
         this.speedSamplesIndex++;
      }
      else {
         this.speedSamplesIndex = 0;
      }
   },

   calculateAverageSpeed: function () {
      var i,
      total = 0;

      for (i=0; i < this.NUM_SPEED_SAMPLES; i++) {
         total += this.speedSamples[i];
      }

      return total/this.NUM_SPEED_SAMPLES;
   },
   ...
};

When a player clicks the running-slowly warning's OK button, Snail Bait hides the warning and resets the speed samples. When the player clicks the Do not show this warning again button, Snail Bait hides the warning and sets a flag so that it no longer monitors frame rate. The code for those two event handlers is in Listing 12:

Listing 12. Running slowly event handlers
snailBait.slowlyDontShowElement.onclick = function (e) {
   snailBait.hideRunningSlowlyWarning();
   snailBait.showSlowWarning = false;
};


snailBait.slowlyOkayElement.onclick = function (e) {
   snailBait.hideRunningSlowlyWarning();
   snailBait.speedSamples = [60,60,60,60,60,60,60,60,60,60]; // reset
};

Add special effects

The final version of Snail Bait shakes the game's background when the runner collides with another sprite and explodes. Listing 13 shows an excerpt from the runner's collide behavior that invokes Snail Bait's shake() method when such a collision occurs:

Listing 13. Shaking the background
var SnailBait = function () {
   ...
   // The collide behavior is attached to the runner

   this.collideBehavior = {
      ...
      processCollision: function (sprite, otherSprite) {
         ...
         if ('bat' === otherSprite.type || 'bee' === otherSprite.type   ||
         'snail' === otherSprite.type || 'snail bomb' === otherSprite.type) {
            snailBait.explode(sprite);

            snailBait.shake();

            setTimeout( function () {
               snailBait.loseLife();
               snailBait.reset();
               snailBait.fadeAndRestoreCanvas();
            }, snailBait.EXPLOSION_DURATION);
         }
      },
   };
   ...
};

Snail Bait's shake() method consists of a series of nested calls to setTimeout(), shown in Listing 14:

Listing 14. Implementing the shake() method
SnailBait.prototype = function () {
   ... 
   shake: function () {
      var SHAKE_INTERVAL = 90, // milliseconds
          v = snailBait.BACKGROUND_VELOCITY,
          ov = snailBait.bgVelocity; // ov means original velocity
   
      this.bgVelocity = -this.BACKGROUND_VELOCITY;

      setTimeout( function (e) {
       snailBait.bgVelocity = v;
       setTimeout( function (e) {
          snailBait.bgVelocity = -v;
          setTimeout( function (e) {
             snailBait.bgVelocity = v;
             setTimeout( function (e) {
                snailBait.bgVelocity = -v;
                setTimeout( function (e) {
                   snailBait.bgVelocity = v;
                   setTimeout( function (e) {
                      snailBait.bgVelocity = -v;
                      setTimeout( function (e) {
                         snailBait.bgVelocity = v;
                         setTimeout( function (e) {
                            snailBait.bgVelocity = -v;
                            setTimeout( function (e) {
                               snailBait.bgVelocity = v;
                               setTimeout( function (e) {
                                  snailBait.bgVelocity = -v;
                                  setTimeout( function (e) {
                                     snailBait.bgVelocity = v;
                                     setTimeout( function (e) {
                                        snailBait.bgVelocity = ov;
                                     }, SHAKE_INTERVAL);
                                  }, SHAKE_INTERVAL);
                               }, SHAKE_INTERVAL);
                            }, SHAKE_INTERVAL);
                         }, SHAKE_INTERVAL);
                      }, SHAKE_INTERVAL);
                   }, SHAKE_INTERVAL);
                }, SHAKE_INTERVAL);
             }, SHAKE_INTERVAL);
          }, SHAKE_INTERVAL);
       }, SHAKE_INTERVAL);
     }, SHAKE_INTERVAL);
   },
   ...
};

Every 90 milliseconds, Snail Bait's shake() method reverses the direction of the game's background velocity, which creates the illusion that the game is shaking.


Track lives

The final version of Snail Bait gives players three lives at the beginning of the game. When the runner collides with a malicious sprite and explodes, the player loses a life and Snail Bait returns the runner to the beginning of the game.

Snail Bait displays the number of remaining lives above the game's canvas by representing each life with a miniature icon of the runner. Those images are specified in the game's HTML, as shown in Listing 15:

Listing 15. The lives elements
<html>
   ...
   <body>
      <div id='arena'>
         <div id='header'>
            <div id='score'>0</div>

            <div id='lives'>
               <img id='life-icon-left'   src='images/runner-small.png'/>
               <img id='life-icon-middle' src='images/runner-small.png'/>
               <img id='life-icon-right'  src='images/runner-small.png'/>
            </div>
         </div>
         ...
      </div>
      ...
   <body>
</html>

At run time, Snail Bait's loseLife() method — which is invoked when the runner collides with a malicious sprite (see Listing 13) — updates the lives element. Listing 16 shows the updateLivesElement function and loseLife() methods:

Listing 16. Updating the lives element
SnailBait.prototype = {
   ...

   updateLivesElement: function () {
      if (this.lives === 3) {
         this.lifeIconLeft.style.opacity   = 1.0;
         this.lifeIconMiddle.style.opacity = 1.0;
         this.lifeIconRight.style.opacity  = 1.0;
      }
      else if (this.lives === 2) {

         this.lifeIconLeft.style.opacity   = 1.0;
         this.lifeIconMiddle.style.opacity = 1.0;
         this.lifeIconRight.style.opacity  = 0;
      }
      else if (this.lives === 1) {
         this.lifeIconLeft.style.opacity   = 1.0;
         this.lifeIconMiddle.style.opacity = 0;
         this.lifeIconRight.style.opacity  = 0;
      }
      else if (this.lives === 0) {
         this.lifeIconLeft.style.opacity   = 0;
         this.lifeIconMiddle.style.opacity = 0;
         this.lifeIconRight.style.opacity  = 0;
      }
   },

   loseLife: function () {
      this.lives--;
      this.updateLivesElement();

      if (this.lives === 1) {
         snailBait.revealToast('Last chance!');
      }

      if (this.lives === 0) {
         this.gameOver();
      }
   },
   ...
};

Transition between lives

In addition to reducing the number of lives icons when the player loses a life, Snail Bait fades the canvas out until it's nearly transparent, as shown in Figure 8. Then it fades the canvas back in until it's fully opaque. Snail Bait also drops the runner from the third (highest) track to reinforce that a new life is beginning.

Figure 8. Fading the canvas when the game transitions between lives
Screen capture from Snail Bait showing a transition between lives

Listing 17 shows Snail Bait's reset() method, which is called from the runner's collide behavior in Listing 13:

Listing 17. Fading and restoring the canvas
SnailBait.prototype = {
   ...
   fadeAndRestoreCanvas: function () {
      snailBait.canvas.style.opacity = 0.2;

      setTimeout( function () {
         snailBait.canvas.style.opacity = 1.0;
      }, 2500);
   },

   resetRunner: function () {
      snailBait.runner.exploding = false; 
      snailBait.runner.visible = false;
      snailBait.runner.opacity = snailBait.OPAQUE;
      snailBait.runner.artist.cells = snailBait.runnerCellsRight;

      if (snailBait.runner.jumping) { snailBait.runner.stopJumping(); }
      if (snailBait.runner.falling) { snailBait.runner.stopFalling(); }
   },

   reset: function () {
      var CANVAS_TRANSITION_DURATION = 2000,
          CONTINUE_RUNNING_DURATION = 1000;

      this.resetRunner();

      setTimeout( function () {
         snailBait.backgroundOffset = 
            snailBait.INITIAL_BACKGROUND_OFFSET;

         snailBait.spriteOffset = snailBait.INITIAL_BACKGROUND_OFFSET;
         snailBait.bgVelocity = snailBait.INITIAL_BACKGROUND_VELOCITY;

         snailBait.runner.track = 3;
         snailBait.runner.top = snailBait.calculatePlatformTop(snailBait.runner.track) - 
                                snailBait.runner.height;

         for (var i=0; i < snailBait.sprites.length; ++i) { 
            snailBait.sprites[i].visible = true;
         }

         setTimeout( function () {
            snailBait.runner.runAnimationRate = 0; // stop running
         }, CONTINUE_RUNNING_DURATION);
      }, CANVAS_TRANSITION_DURATION);
   },
   ...
};

The reset() method resets the game and gets it ready for a new life by:

  1. Resetting the sprite and background offsets to their initial values.
  2. Putting the runner on third track.
  3. Making all the game's sprites visible.

After reset() is called, the runner starts to fall beginning with the next animation frame, because she's on the third track, with no platform underneath her. So her fall behavior initiates falling, and she falls until she lands on the first platform.


Display credits

At the end of the game, Snail Bait displays credits, as shown in Figure 9:

Figure 9. Displaying credits at the end of the game
Screen capture of Snail Bait displaying game credits

The Snail Bait methods pertinent to revealing and hiding credits are in Listing 18:

Listing 18. Revealing and hiding credits
SnailBait.prototype = {
   ...
   
   gameOver: function () {
      snailBait.revealCredits();
   },

   restartGame: function () {
      this.hideCredits();

      this.lives = this.MAX_NUMBER_OF_LIVES;
      this.updateLivesElement();

      this.score = 0;
      this.updateScoreElement();
   },

   revealCredits: function () {
      this.creditsElement.style.display = 'block';
      this.revealLivesIcons();

      this.tweetElement.href = TWEET_PREAMBLE + this.score + TWEET_PROLOGUE;

      setTimeout( function () {
         snailBait.creditsElement.style.opacity = 1.0;
      }, snailBait.SHORT_DELAY);
   },

   hideCredits: function () {

      var CREDITS_REVEAL_DELAY = 2000;

      this.creditsElement.style.opacity = this.TRANSPARENT;

      setTimeout( function (e) {
         snailBait.creditsElement.style.display = 'none';
      }, this.CREDITS_REVEAL_DELAY);
   }, 
};

Snail Bait's gameOver() method, which is invoked by the loseLife() method (see Listing 16), reveals credits, whereas the restartGame() method hides them. The restartGame() method is invoked by the event handler associated with the credits' Play again link. That event handler is in Listing 19:

Listing 19. Credit-screen event handler
 snailBait.newGameLink.onclick = function (e) {
   snailBait.restartGame();
};

Tweet the score

Many HTML5 games have a social aspect, such as tweeting scores or interacting with other players during the game. Snail Bait players can tweet their score when a game ends by clicking the Tweet my score link in the credits display (see Figure 9). The player's browser opens Twitter in a new tab, and Snail Bait automatically enters a ready-to-send tweet in the compose-new-tweet dialog. Figure 10 shows an example:

Figure 10. Tweeting a score
Screen capture from Twitter of tweeting a Snail Bait score

Snail Bait tweets scores by constructing a URL string and assigning that string to the href attribute of the Tweet my score link, as shown in Listing 20:

Listing 20. Tweeting scores
SnailBait = function () {
   ...
   this.TWEET_PREAMBLE = 'https://twitter.com/intent/tweet?text=I scored ';
   this.TWEET_PROLOGUE = ' playing this HTML5 Canvas platformer: ' +
                         'http://bit.ly/NDV761 &hashtags=html5';
   ...
};

SnailBait.prototype = {
   ...
   
   revealCredits: function () {
      this.creditsElement.style.display = 'block';
      this.revealLivesIcons();

      this.tweetElement.href = TWEET_PREAMBLE + this.score + TWEET_PROLOGUE;

      setTimeout( function () {
         snailBait.creditsElement.style.opacity = 1.0;
      }, snailBait.SHORT_DELAY);
   },
   ...
};

Deploy to a server

Deploying an HTML5 game to a server brings up two main concerns: the amount of data you must transmit from the client to the server when the game starts, and the number of round trips you must make between the client and server. Ideally, you transmit as little data as possible and make as few HTTP requests as you can. To facilitate those goals, Snail Bait performs three steps:

  1. Compress JavaScript files.
  2. Copy all JavaScript files into one file (all.js).
  3. Zip JavaScript, CSS, and HTML.

First, a simple shell script finds all of Snail Bait's JavaScript files and compresses each of them using the YUI compressor (see Resources for more information about the compressor), as shown in Listing 21:

Listing 21. Shell script to compress Snail Bait's JavaScript files
#!/bin/bash

mkdir tmp

for f in `find . -name "*.js"`
   do
      echo 'Compressing ' $f
         java -jar ~/Utilities/yuicompressor-2.4.7.jar $f >> tmp/all.js
   done

The compressed files are concatenated into a single JavaScript file named all.js, which is included in Snail Bait's HTML file.

Another shell script, shown in Listing 22, deploys the game by running the compress script in Listing 21 and then zipping the game's JavaScript, HTML, and CSS:

Listing 22. A shell script to deploy Snail Bait
#!/bin/bash

rm -rf tmp
rm all.js
rm *.gz

echo 'Compressing...'
echo
./compress.sh

echo 'Deploying...'
echo

gzip < all.js > all.js.gz
gzip < index.html > index.html.gz
gzip < snailbait.css > snailbait.css.gz

rm -rf tmp

You must configure your server to accept ZIP files for it to use the zipped files instead of the originals. The configuration details vary among servers.


Conclusion

This article has covered a lot of ground, and so has the entire series. You've seen Snail Bait progress from its humble beginning in the first installment— when it simply displayed the background, runner, and platforms — to a polished game. You've learned how to use the 2D Canvas API, along with other HTML5 APIs such as requestAnimationFrame, to implement scrolling backgrounds and parallax. You've implemented sprites and sprite behaviors. You've also seen how to bend time to your will to implement nonlinear motion, and how to detect collisions and animate sprites. But 2D game development also can involve many other aspects that I didn't discuss in this series, such as:

  • Implementing a time system to modify the flow of time throughout the entire game.
  • Saving high scores and real-time, in-game metrics on a server.
  • Creating a developer's backdoor that gives developers access to special powers.
  • Implementing particle systems to simulate smoke and fire.
  • Fine-tuning games for mobile devices.

I encourage you to explore all of these topics and to start developing your own HTML5 2D games.


Download

DescriptionNameSize
Sample codewa-html5-game10-code.zip12.9MB

Resources

Learn

Get products and technologies

  • CSS3 patterns gallery: Visit Lea Verou's gallery to download or submit patterns.
  • Replica Island: You can download the source for this popular open source platform video game for Android. Most of Snail Bait's sprites are from Replica Island (used with permission).

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=938007
ArticleTitle=HTML5 2D game development: Wrapping up the game
publish-date=07232013