Chapter 17. User Interface

Great games are not merely games, they are experiences. All aspects of a game, including sound, music, user interface and gameplay, combine to create that experience, so it’s important to apply as much polish as you can to each aspect to create the best experience.

In this chapter we explore the implementation of Snail Bait’s user interface, starting with the simple task of tracking and displaying the game’s score and ending with something a little more complicated: displaying a winning animation when the player wins the game. In between, we discuss how to let players tweet their score, how to warn players when the game runs slowly, and how to show credits at the end of the game.

In this chapter you will learn how to do the following:

• Keep score (Section 17.1 on p. 444)

• Add a lives indicator to the game (Section 17.2 on p. 448)

• Display credits (Section 17.3 on p. 454)

• Tweet player scores (Section 17.4 on p. 461)

• Warn players when the game runs slowly (Section 17.5 on p. 464)

• Implement a winning animation (Section 17.6 on p. 473)

17.1. Keep Score

When the runner collides with an asset, meaning a coin, ruby, or sapphire, Snail Bait increases the game’s score, which it displays above the game’s canvas, as shown in Figure 17.1.

Figure 17.1. The score element

Image

Snail Bait keeps track of the score as follows:

• Adds a score HTML element

• Specifies CSS for the score element

• Accesses the score element in JavaScript

• Creates a score variable and initialize it to zero

• Assigns values to sprites that are assets

• Updates the score variable and score element when the runner collides with an asset

First, we add a DIV HTML element to Snail Bait’s HTML and initialize its value to zero, as shown in Example 17.1.

Example 17.1. Add a score HTML element


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

      <div id='snailbait-arena'>
         ...

         <div id='snailbait-score'>
            0
         </div>
         ...

      </div>
   </body>
</html>


The CSS for the score element is listed in Example 17.2.

Example 17.2. Specify CSS for the score element


#snailbait-score {
   font: 46px fantasy;
   text-align: center;
   color: yellow;
   text-shadow: 2px 2px 4px navy;

   -webkit-transition: opacity 5s;
   -moz-transition: opacity 5s;
   -o-transition: opacity 5s;
   transition: opacity 5s;

   opacity: 0;
   display: none;
}


The preceding CSS configures the score element’s font, specifies a CSS transition, and makes the element initially invisible. That transition takes effect when Snail Bait sets the score element’s opacity property to 1.0 after gameplay begins, as discussed in [Missing XREF!].

With the HTML and CSS in place for the score element, Snail Bait accesses the element in the game’s constructor, as shown in Example 17.3. The constructor creates the score variable and initializes it to zero.

Example 17.3. Create the score variable and access the score HTML element in JavaScript


var SnailBait = function () {
   ...

   this.scoreElement = document.getElementById('snailbait-score');
   this.score = 0;
   ...
};


Snail Bait assigns values to assets in its setSpriteValues() method. The game invokes that method from initializeSprites(), as you can see from Example 17.4.

Example 17.4. Assign values to coins, rubies, and sapphires


SnailBait.prototype = {
   ...

   setSpriteValues: function() {
      var sprite,
          COIN_VALUE     = 100,
          SAPPHIRE_VALUE = 500,
          RUBY_VALUE     = 1000;

      for (var i = 0; i < this.sprites.length; ++i) {
         sprite = this.sprites[i];

         if (sprite.type === 'coin') {
            sprite.value = COIN_VALUE;
         }
         else if (sprite.type === 'ruby') {
            sprite.value = RUBY_VALUE;
         }
         else if (sprite.type === 'sapphire') {
            sprite.value = SAPPHIRE_VALUE;
         }
      }
   },

   initializeSprites: function() {
      ...

      this.setSpriteValues();
      ...
   },
   ...
};


When the runner collides with an asset, the runner’s collide behavior increments the game’s score and updates the score element, as shown in Example 17.5.

Example 17.5. Adjust the score


var SnailBait = function () {
   ...

   this.collideBehavior = {
      adjustScore: function (sprite) {
         if (sprite.value) {
            snailBait.score += sprite.value;
            snailBait.updateScoreElement();
         }
      },

      processAssetCollision: function (sprite) {
         sprite.visible = false; // sprite is the asset

         if (sprite.type === 'coin')
            snailBait.playSound(snailBait.coinSound);
         else
            snailBait.playSound(snailBait.chimesSound);

         this.adjustScore(sprite);
      },
      ...
   };
   ...
};

SnailBait.prototype = {
   ...

   updateScoreElement: function () {
      this.scoreElement.innerHTML = this.score;
   },
   ...
};


When the runner collides with an asset, the collide behavior’s processAssetCollision() method plays a sound and adjusts the game’s score.

Now that you’ve seen how to keep score, let’s see how to add a lives indicator.

17.2. Add a Lives Indicator

When Snail Bait begins, players have three lives. When a player collides with a bat or a bee or falls through the bottom of the game, the player loses a life. Snail Bait displays the number of remaining lives above and to the left of the game’s canvas, as shown in Figure 17.2.

Figure 17.2. The lives indicator

Image

Adding the lives indicator involves the following steps:

• Add HTML elements for each life indicator and their containing DIV

• Specify CSS for the elements

• Access the elements in JavaScript

• Reveal the elements when the game starts

• Update the elements when the player loses a life

As you can see from Example 17.6, each life indicator is an image. The images, three in all, reside in a DIV with the identifier lives.

Example 17.6. Add HTML elements for life indicators


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

      <div id='snailbait-arena'>
         ...

         <div id='snailbait-lives'>
            <img id='snailbait-life-icon-left'
                 src='images/runner-small.png'/>

            <img id='snailbait-life-icon-middle'
                 src='images/runner-small.png'/>

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


The three images and the DIV are shown in Figure 17.3 with white borders around each element.

Figure 17.3. Life indicator HTML elements

Image

The CSS for those elements (without the white borders) is shown in Example 17.7.

Example 17.7. Specify CSS for the elements


#snailbait-lives {
   position: absolute;
   margin-top: 20px;
   margin-left: 5px;

   -webkit-transition: opacity 5s;
   -moz-transition: opacity 5s;
   -o-transition: opacity 5s;
   transition: opacity 5s;

   display: none;
   opacity: 0;
}

#snailbait-life-icon-left {
   -webkit-transition: opacity 5s;
   -moz-transition: opacity 5s;
   -o-transition: opacity 5s;
   transition: opacity 5s;
}

#snailbait-life-icon-middle {
   -webkit-transition: opacity 5s;
   -moz-transition: opacity 5s;
   -o-transition: opacity 5s;
   transition: opacity 5s;
}

#snailbait-life-icon-right {
   -webkit-transition: opacity 5s;
   -moz-transition: opacity 5s;
   -o-transition: opacity 5s;
   transition: opacity 5s;
}


All four elements have a CSS transition on the opacity property. When Snail Bait changes that property’s value, the browser smoothly animates the associated element from the current opacity to the new opacity. Snail Bait sets the opacity for the lives element when the game starts, fading the elements in, and fades them out temporarily when a player loses a life.

Snail Bait’s constructor function obtains references to the four elements as shown in Example 17.8.

Example 17.8. Access the elements in Snail Bait’s constructor


var SnailBait = function () {
   ...

   this.livesElement =
      document.getElementById('snailbait-lives');

   this.lifeIconLeft =
      document.getElementById('snailbait-life-icon-left');

   this.lifeIconMiddle =
      document.getElementById('snailbait-life-icon-middle');

   this.lifeIconRight =
      document.getElementById('snailbait-life-icon-right');
   ...
};


As Snail Bait loads resources, the score element and the lives indicator elements—collectively referred to as the game’s top chrome—fade in, but only to 0.25 opacity to focus the player’s attention on the game’s bottom chrome, which contains the game’s instructions. A short time later, the game fades the top chrome to 1.0 opacity.

Example 17.9 shows the revised listings of Snail Bait’s revealTopChrome() and revealTopChromeDimmed() methods, originally listed in Chapter 5, which now manipulate the lives element instead of the defunct frames/second indicator that has occupied the upper left-corner of the game until now.

Example 17.9. Snail Bait’s revealTopChrome() and revealTopChromeDimmed(), final version


SnailBait.prototype = {
   ...

   revealTopChrome: function () {
      this.fadeInElements(this.livesElement,
                          this.scoreElement);
   },

   revealTopChromeDimmed: function () {
      var DIM = 0.25;

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

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

      setTimeout( function () {
         snailBait.scoreElement.style.opacity = DIM;
         snailBait.livesElement.style.opacity = DIM;
      }, this.SHORT_DELAY); // 50 ms
   },
   ...
};


The revealTopChrome() method invokes Snail Bait’s fadeInElements() method, which takes a variable-length list of elements. The fadeInElements() method fades elements in by setting their opacity to 1.0; for the fade-in animation, the method relies on CSS transitions attached to the elements.

The revealTopChromeDimmed() method sets the opacity for the score and lives elements to 0.25 instead of 1.0, so instead of invoking fadeInElements(), which makes elements fully opaque, revealTopChromeDimmed() fades elements in by hand. Like revealTopChrome(), revealTopChromeDimmed() relies on CSS transitions attached to the elements it manipulates.

When the player loses a life, Snail Bait’s loseLife() method decrements the lives property and updates the lives element, as shown in Example 17.10.

Example 17.10. Update the lives variable and the lives element when the player loses a life


SnailBait.prototype = {
   ...

   loseLife: function () {
      ...

      this.lives--;
      this.updateLivesElement();
      ...
   }
   ...
};


The updateLivesElement() method is listed in Example 17.11.

Example 17.11. Update the lives element


SnailBait.prototype = {
   ...

   updateLivesElement: function () {
      if (this.lives === 3) {
         this.lifeIconLeft.style.opacity   = snailBait.OPAQUE;
         this.lifeIconMiddle.style.opacity = snailBait.OPAQUE;
         this.lifeIconRight.style.opacity  = snailBait.OPAQUE;
      }
      else if (this.lives === 2) {
         this.lifeIconLeft.style.opacity   = snailBait.OPAQUE;
         this.lifeIconMiddle.style.opacity = snailBait.OPAQUE;
         this.lifeIconRight.style.opacity  = snailBait.TRANSPARENT;
      }
      else if (this.lives === 1) {
         this.lifeIconLeft.style.opacity   = snailBait.OPAQUE;
         this.lifeIconMiddle.style.opacity = snailBait.TRANSPARENT;
         this.lifeIconRight.style.opacity  = snailBait.TRANSPARENT;
      }
      else if (this.lives === 0) {
         this.lifeIconLeft.style.opacity   = snailBait.TRANSPARENT;
         this.lifeIconMiddle.style.opacity = snailBait.TRANSPARENT;
         this.lifeIconRight.style.opacity  = snailBait.TRANSPARENT;
      }
   },
   ...
};


The updateLivesElement() method sets the opacity for each life indicator according to the number of lives remaining, which triggers a smooth animation from the previous opacity by virtue of the elements’ CSS transition. Snail Bait’s OPAQUE and TRANSPARENT constants are 1.0 and 0, respectively.

Now that you’ve seen how Snail Bait implements the lives indicator, let’s see how it displays credits.


Image Note: Fading elements in and out

The user interface effects in this chapter use Snail Bait’s fadeInElements() and fadeOutElements() methods. Those methods, which in turn rely on CSS transitions attached to the elements they manipulate, are discussed in Chapter 5.


17.3. Display Credits

Snail Bait displays credits at the end of the game, as shown in Figure 1.4.

Figure 17.4. Credits

Image

The game implements credits with the following steps:

• Add HTML elements for the credits

• Specify CSS for the credit HTML elements

• Access credits HTML elements in JavaScript

• Implement hideCredits() and revealCredits() methods

• Modify gameOver() to reveal credits

• Modify restartGame() to hide credits

• Add a click event handler for the credit’s Play again link that calls restartGame()

As it does for keeping score and displaying life indicators, Snail Bait implements credits by specifying HTML elements and their corresponding CSS and subsequently accessing those elements in the game’s constructor. The HTML for the credits is listed in Example 17.12.

Example 17.12. Add HTML elements for the credits


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

      <div id='snailbait-arena'>
         <!-- Credits...............................................-->

         <div id='snailbait-credits' class='snailbait-credits'>
            <div class='snailbait-heading'>Credits</div>

            <hr></hr>

            <div class='snailbait-credit'>
               <div id='snailbait-art-title'
                    class='snailbait-title'>

                    ~ Art ~

               </div>

               <div class='snailbait-attribution'><b>
                  <a href='http://lea.verou.me/css3patterns'>
                     CSS3 Patterns Gallery
                  </a>:

                  </b> Background pattern in CSS by Anna Kassner
               </div>

               <div class='snailbait-attribution'><b>
                  <a href='http://bit.ly/kNzDVc'>
                     Replica Island
                  </a>:

                  </b> All graphics except the runner and coins
               </div>

               <div class='snailbait-attribution'>
                  <b>MJKRZAK People's Sprites:</b>
                  Runner spritesheet (Link no longer available)
               </div>

               <div class='snailbait-attribution'><b>
                  <a href='http://bit.ly/QvGRVR'>
                     LoversHorizon at deviantART
                  </a>:

                  </b> Coins
               </div>
            </div>

            <div class='snailbait-credit'>
               <div id='snailbait-sound-and-music-title'
                    class='snailbait-title'>

                  ~ Sound and Music ~

               </div>

               <div class='snailbait-attribution'>
                  <a href='http://bit.ly/LFtwr8'>
                     Pete Marquardt:
                  </a>

                  Soundtrack from soundclick.com
               </div>

               <div class='snailbait-attribution'>
                  <a href='http://bit.ly/kNzDVc'>
                     Replica Island:
                  </a>

                  Sound effects
               </div>
            </div>

            <p>
               <!-- Tweet my score link omitted -->

               <a id='snailbait-play-again-link'
                  href='#'>Play again</a>
            </p>

         </div> <!-- end of credit -->
         ...
      </div> <!-- end of arena -->
  </body>
</html>


The preceding HTML creates a hierarchy of DIVs where an enclosing credits DIV contains credit DIVs. The credit DIVs in turn contain attribution DIVs. At the end of the credits DIV, a paragraph element contains the Tweet my score and Play again links. The HTML for the Tweet my score link is omitted from Example 17.12; instead, it is discussed in Section 17.4, “Tweet Player Scores,” on p. 461.

The CSS for the credits elements is listed in Example 17.13.

Example 17.13. Specify CSS for the credit HTML elements


.snailbait-credits {
   position: absolute;
   margin-left: 50px;
   margin-top: 10px;
   padding-bottom: 30px;
   font: 20px fantasy;
   text-align: center;
   width: 650px;
   height: 23em;
   background: rgba(255,255,230,0.75);
   border: thin solid blue;
   padding-top: 10px;
   padding-left: 40px;
   padding-right: 40px;

   -webkit-transition: opacity 2s;
   -moz-transition: opacity 2s;
   -o-transition: opacity 2s;
   transition: opacity 2s;

   -webkit-box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
   -moz-box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
   -o-box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
   box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;

   border-radius: 10px;

   opacity: 0;
   display: none;
   z-index: 1;
}

#snailbait-credits a:hover {
   color: blue;
   text-shadow: 1px 1px 1px rgba(0,0,200,0.5);
}
#snailbait-credits p {
   margin-top: 20px;
   margin-bottom: 10px;
   font: 24px fantasy;
   text-shadow: 1px 1px 1px rgba(0,0,0,0.8);
   color: blue;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.6);
}

#snailbait-credits .attribution {
   font: 18px fantasy;
   color: blue;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.6);
}

#snailbait-credits .title {
   margin-bottom: 10px;
   font-size: 22px;
   color: blue;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.6);
}

#snailbait-new-game-link {
  margin-top: 10px;
  float: right;
  margin-right: 20px;
  font-size: 0.9em;
}

#snailbait-art-title {
   margin-top: 20px;
}

#snailbait-sound-and-music-title {
   margin-top: 20px;
}

#snailbait-credits .snailbait-heading {
   margin-bottom: 10px;
   font-size: 35px;
   font-family: fantasy;
   color: blue;
   text-shadow: 2px 2px 2px rgba(255,255,255,0.8);
}

.snailbait-tweet-link {
  margin-top: 10px;
  margin-left: 20px;
  float: left;
  font-size: 0.9em;
}

#snailbait-play-again-link {
   margin-top: 10px;
   float: right;
   margin-right: 20px;
   font-size: 0.9em;
}


As we’ve done previously with other DIVs that the game fades in and out, the credits DIV is initially invisible. A CSS transition smoothly animates the DIV’s opacity when the game sets that property. Additionally, the border-radius and box-shadow properties give the credits element rounded corners and a drop shadow.

Besides the two links, Snail Bait does not manipulate any of the elements inside the credits element because those elements merely display static information. In its constructor function, Snail Bait obtains references to the credits element and the Play again link, as shown in Example 17.14.

Example 17.14. Snail Bait’s constructor accesses credits HTML elements


var SnailBait = function () {
   ...

   // Credits...........................................................

   this.creditsElement =
      document.getElementById('snailbait-credits');

   this.newGameLink =
      document.getElementById('snailbait-play-again-link');
   ...
};


Snail Bait’s revealCredits() and hideCredits() methods, listed in Example 17.15 use the game’s fadeInElements() and fadeOutElements() methods, respectively.

Example 17.15. Implement revealCredits() and hideCredits() methods


SnailBait.prototype = {
   ...
   revealCredits: function () {
      this.fadeInElements(this.creditsElement);
      ...
   },

   hideCredits: function () {
      var FADE_DURATION = 1000;

      this.fadeOutElements(this.creditsElement, FADE_DURATION);
   },
   ...
};


The gameOver() method, listed in Example 17.16, reveals the game’s credits, drastically slows the rate at which the background scrolls, and sets the game’s playing property to false, which causes the game to disregard the player input.

Example 17.16. Modify gameOver() to reveal credits


SnailBait.prototype = {
   ...
   gameOver: function () {
      this.revealCredits();
      this.bgVelocity = this.BACKGROUND_VELOCITY / 20;
      this.playing = false;
      ...
   },
   ...
};


When the game restarts, it hides credits, as shown in Example 17.17.

Example 17.17. Modify restartGame() to hide credits


SnailBait.prototype = {
   ...
   restartGame: function () {
      this.hideCredits();
      ...
   },
   ...
};


The game restarts when the player clicks the Play again link, as shown in Example 17.18.

Example 17.18. Add a click event handler for the Play again link that calls restartGame()


snailBait.newGameLink.onclick = function (e) {
   snailBait.restartGame();
};


The preceding event handler restarts the game when the player clicks the Play again link. Next, let’s see how to implement the functionality behind the Tweet my score link.


Image Note: The z-index property for UI elements

The credits and running slowly warning both have a z-index of one. That places those elements above the game’s canvas, which by default, has a z-index of zero.


17.4. Tweet Player Scores

Twitter’s Web Intents are the simplest way to let players tweet about your game. Snail Bait lets players tweet their scores by clicking on the Tweet my score link in the game’s credits, as shown in Section 17.3, “Display Credits,” on p. 454.

Figure 17.5 shows Twitter’s documentation for Web Intents.

Figure 17.5. Twitter Web Intents (see https://dev.twitter.com/docs/intents)

Image

Using Web Intents is simple. Create a link that points to the Web Intents URL, with parameters for things such as the tweet’s text. As you can see from Figure 17.6, when players click the link, the Twitter intent opens a webpage that makes it easy for them to sign into their Twitter account and tweet (the sign in is not necessary if the player is already logged into Twitter).

Figure 17.6. Tweeting scores

Image

Snail Bait implements the Tweet my score link with the following steps.

• Add a link to the game’s HTML.

• Access the link in JavaScript.

• Set the link’s href property to a twitter intent URL.

Example 17.19 shows the Tweet my score link in the game’s HTML. The link’s target property is _blank, so the browser opens the linked page in a new window or tab, as you can see in Figure 17.6.

Example 17.19. The Tweet my score link


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

      <div id='snailbait-arena'>
         ...
         <div id='snailbait-credits'>
            ...

            <p>
               <a id='snailbait-tweet' href=""
                  hashtags='#html5' target='_blank'
                  class='snailbait-tweet-link'>
                  Tweet my score</a>
               ...
            </p>
         </div> <!-- end of credits -->
      </div>
      ...
  </body>
</html>


Snail Bait’s constructor accesses the link and declares two string constants, as shown in Example 17.20.

Example 17.20. Access the tweet link in JavaScript


var SnailBait = function () {
   ...

   this.tweetElement = document.getElementById('snailbait-tweet');

   this.TWEET_PREAMBLE = 'https://twitter.com/intent/tweet?text= +
                         'I scored ';

   this.TWEET_PROLOGUE = ' playing this HTML5 Canvas platformer: ' +
                         'http://bit.ly/NDV761 &amp;hashtags=html5';
   ...
};


Example 17.21 lists a revised version of the revealCredits() method that constructs the link’s hypertext reference, which Snail Bait accesses with the href property.

Example 17.21. Set the tweet link’s hypertext reference


SnailBait.prototype = {
   ...

   revealCredits: function () {
      this.fadeInElements(this.creditsElement);
      this.tweetElement.href = this.TWEET_PREAMBLE + this.score +
                               this.TWEET_PROLOGUE;
   },
   ...
};


From Snail Bait’s perspective, that’s all the code that’s necessary to let players tweet their scores. Snail Bait’s end of the bargain is merely to provide a properly configured link that points to the Twitter intent. The intent takes care of the rest.


Image Note: The power of Web Intents

Web applications are typically implemented as a collection of services, ranging from simple services such as saving a document to more sophisticated services such as tweeting a game’s score. Web intents let web applications hand off services to third parties to implement the intent.

In this section Snail Bait handed off the service of tweeting the game’s score to a Twitter web intent, which is orders of magnitude easier than implementing the same functionality on your own.


17.5. Warn Players When the Game Runs Slowly

HTML5 game developers are fortunate because their games run on virtually any computer platform with a browser, including, with a little extra work, mobile devices. HTML5 game developers are unfortunate, however, because their games, which run in an unpredictable environment, can easily be brought to their knees by all sorts of other applications, from backup software running in the background to YouTube videos running in another window.

No matter how fast your HTML5 game runs, there will be times when a player’s computer won’t be able to give your game the time it needs and the game will run too slowly to be playable. When that happens, you must warn the user that the game is running too slowly. Figure 17.7 shows Snail Bait’s running slowly warning.

Figure 17.7. The running slowly warning

Image

Snail Bait’s running slowly warning includes the game’s current frame rate (which the game constantly updates while the running slowly warning is displayed) and an option to dismiss the warning for good. The warning also explains why HTML5 games sometimes run slowly and what the user can do about it.

Snail Bait implements the running slowly warning with the following steps:

• Add HTML elements for the running slowly warning

• Specify CSS for the running slowly warning elements

• Access the HTML elements in JavaScript

• Implement a revealRunningSlowlyWarning() method

Example 17.22 shows the HTML for Snail Bait’s running slowly warning.

Example 17.22. Add HTML elements for the running slowly warning


<!DOCTYPE html>
<html>
   ...
   <body>
      ...

      <div id='snailbait-arena'>
         ...

         <div id='snailbait-running-slowly'>

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

            <hr>

            <p id='snailbait-slowly-warning'></p>

            <p>
               A wide range of applications, from video players to
               backup software, can slow this game down. For best
               results, hide all other windows on your desktop and
               close graphics intensive apps when you play HTML5 games.
            </p>

            <p>
               You should also upgrade your browser to the latest
               version to 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 to the
               <a href='http://www.google.com/chrome/'>
                  latest version of Chrome
               </a>.
            </p>

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

            <a  id='snailbait-slowly-dont-show' href='#'>
               Do not show this warning again
            </a>
         </div> <!-- End of running-slowly -->
         ...
      </div> <!-- End of snailbait-arena -->
      ...
   </body>
</html>


The interesting part of the preceding HTML is the empty DIV with the identifier snailbait-slowly-warning. Snail Bait fills in the contents of that DIV on the fly because the warning contains the game’s current frame rate.

The CSS for the running slowly warning is listed in Example 17.23.

Example 17.23. Specify CSS for the running slowly warning elements


#snailbait-running-slowly {
   position: absolute;
   margin-left: 82px;
   margin-top: 75px;
   width: 600px;
   background: rgba(255,255,255,0.85);
   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;

   border-radius: 10px 10px 10px;

   -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: 1;
}

#snailbait-running-slowly h1 {
   padding-top: 0;
   text-align: center;
   color: rgb(50,50,250);
}

#snailbait-running-slowly p {
   color: navy;
   font-size: 1.05em;
}

#snailbait-slowly-okay {
   margin-top: 20px;
   float: left;
   margin-left: 50px;
   font-size: 1.2em;
}

#snailbait-slowly-okay:hover {
   color: blue;
}

#snailbait-slowly-dont-show {
   margin-top: 20px;
   float: right;
   margin-right: 50px;
   font-size: 1.2em;
}

#snailbait-slowly-dont-show:hover {
   color: blue;
}


Once again, we combine initial invisibility with CSS transitions to fade the running slowly warning in and out as needed by modifying its opacity with Snail Bait’s fadeInElements() and fadeOutElements(). Example 17.24 shows how Snail Bait’s constructor accesses the running slowly warning HTML elements.

Example 17.24. Access HTML elements in JavaScript


var SnailBait = function () {
   ...

   // Running slowly warning............................................

   this.runningSlowlyElement =
      document.getElementById('snailbait-running-slowly');

   this.slowlyOkayElement =
      document.getElementById('snailbait-slowly-okay');

   this.slowlyDontShowElement =
      document.getElementById('snailbait-slowly-dont-show');

   this.slowlyWarningElement =
      document.getElementById('snailbait-slowly-warning');

   this.lastSlowWarningTime = 0;
   this.showSlowWarning = false;

   this.runningSlowlyThreshold = this.DEFAULT_RUNNING_SLOWLY_THRESHOLD;
   ...
};


Snail Bait obtains references to some of the elements in the running slowly warning and creates three variables: showSlowWarning, runningSlowlyThreshold, and lastSlowWarningTime. Snail Bait uses showSlowWarning to control whether the game shows the running slowly warning and the runningSlowlyThreshold defines slowest acceptable frame rate. Snail Bait uses lastSlowWarningTime to determine when to next check the game’s frame rate.

Snail Bait’s revealRunningSlowlyWarning() method is listed in Example 17.25.

Example 17.25. Implement a revealRunningSlowlyWarning() method


SnailBait.prototype = {
   ...

   revealRunningSlowlyWarning: function (now, averageSpeed) {
      this.slowlyWarningElement.innerHTML =
         "Snail Bait is running at <i><b>"    +
         averageSpeed.toFixed(0) + "</i></b>"            +
         " frames/second (fps), but it needs more than " +
         this.runningSlowlyThreshold                     +
         " fps to work correctly."

      this.fadeInElements(this.runningSlowlyElement);
      this.lastSlowWarningTime = now;
   },
   ...
};


The revealRunningSlowlyWarning() method constructs the variable part of the warning with the game’s current frame rate and fades in the running slowly element. The method also updates the lastSlowWarningTime, which records the last time the warning was revealed.

17.5.1. Monitor Frame Rate

When the frame rate drops below the game’s running slowly threshold—represented by the runningSlowlyThreshold variable declared in Example 17.26—Snail Bait invokes the revealRunningSlowlyWarning() method discussed above. Snail Bait’s constructor also declares an array of 10 speed samples and an index into that array.

Example 17.26. Constants and variables for monitoring frame rate


var SnailBait = function () {
   ...

   // Running slowly  warning............................................

   this.FPS_SLOW_CHECK_INTERVAL = 2000;  // Check every 2 seconds

   this.DEFAULT_RUNNING_SLOWLY_THRESHOLD  = 40;   // fps
   this.MAX_RUNNING_SLOWLY_THRESHOLD      = 60;   // fps
   this.RUNNING_SLOWLY_FADE_DURATION      = 2000; // seconds

   this.speedSamples = [60,60,60,60,60,60,60,60,60,60];
   this.speedSamplesIndex = 0;

   this.NUM_SPEED_SAMPLES = this.speedSamples.length;
   ...
};


To monitor frame rate, we revise Snail Bait’s animate() method as shown in Example 17.27.

Example 17.27. Revise Snail Bait’s animate() method to check frame rate


SnailBait.prototype = {
   ...

   animate: function (now) {
      // Replace the time passed to this method by the browser
      // with the time from Snail Bait's time system

      now = snailBait.timeSystem.calculateGameTime();

      ...

      if (snailBait.paused) {
         ...
      }
      else { // not paused
         snailBait.fps = snailBait.calculateFps(now);

         if (snailBait.windowHasFocus  &&
             snailBait.playing         &&
             snailBait.showSlowWarning &&
             now - snailBait.lastSlowWarningTime >
             snailBait.FPS_SLOW_CHECK_INTERVAL) {

             snailBait.checkFps(now);
         }
         ...
      }
   },
   ...
};


Snail Bait’s animate() method checks the frame rate when play is underway and two seconds have elapsed since the last check. It checks the frame rate with the checkFps() method, listed in Example 17.28.

Example 17.28. Monitoring Snail Bait’s frame rate


SnailBait.prototype = {
   ...

   checkFps: function (now) {
      var averageSpeed;

      this.updateSpeedSamples(snailBait.fps);

      averageSpeed = this.calculateAverageSpeed();

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


The checkFps() method updates the game’s speed samples and subsequently calculates an average speed from those samples. If the average speed is less than the running slowly threshold, checkFps() reveals the running slowly warning.

The helper methods used by checkFps() are listed in Example 17.29.

Example 17.29. Frame-rate-checking helper methods


SnailBait.prototype = {
   ...

   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;
   },

   updateSpeedSamples: function (fps) {
      this.speedSamples[this.speedSamplesIndex] = fps;
      this.advanceSpeedSamplesIndex();
   },

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


17.5.2. Implement the Running Slowly Warning Event Handlers

When a player clicks the Do not show this warning again button, the browser invokes the element’s click event handler, which is listed in Example 17.30.

Example 17.30. The Don’t show this warning again button’s event handler


snailBait.slowlyDontShowElement.addEventListener(
   'click',

   function (e) {
      snailBait.fadeOutElements(snailBait.runningSlowlyElement,
                                snailBait.RUNNING_SLOWLY_FADE_DURATION);

      snailBait.showSlowWarning = false;
      ...
   }
);


The preceding event handler fades the running slowly warning from view for a duration of two seconds. It then sets the showSlowWarning variable to false so that Snail Bait no longer monitors frame rate. Finally, the event handler puts the game in play.

The onclick event handler for the running slowly warning’s Okay button is listed in Example 17.31.

Example 17.31. The Okay button’s event handler


SnailBait.prototype = {
   ...

   resetSpeedSamples: function () {
      snailBait.speedSamples = [60,60,60,60,60,60,60,60,60,60];
   },
   ...
},

snailBait.slowlyOkayElement.addEventListener(
   'click',

   function (e) {
      snailBait.fadeOutElements(snailBait.runningSlowlyElement,
                                snailBait.RUNNING_SLOWLY_FADE_DURATION);

      snailBait.resetSpeedSamples();
   }
);


The Okay button’s event handler fades out the running slowly warning and the game’s speed samples array, discarding the frame rates the game recorded before the warning was revealed.

17.6. Implement a Winning Animation

If a player lands on the gold button at the end of the game, as shown in Figure 17.8, Snail Bait displays the winning animation shown in Figure 17.9.

Figure 17.8. Falling on the gold button

Image

Figure 17.9. Winning animation

Image

The winning animation fades the game’s canvas, replaces the score with Winner! and shows the animated GIF the game displays when it loads. The game’s revealWinningAnimation() method reveals the animation. That method is invoked by a behavior attached to the gold button.

Snail Bait implements the winning animation with the following steps:

• Implement a behavior for the gold button that invokes revealWinningAnimation()

• Detonate the button in the runner’s fall behavior when the runner falls on the button

• Implement a revealWinningAnimation() method that fades in the winning animation; after displaying the animation for five seconds, end the game

Snail Bait has two buttons. The first is blue and the second is gold, as you can see from Example 17.32.

Example 17.32. Creating buttons, revised


SnailBait.prototype = {
   ...

   createButtonSprites: function () {
      var button;

      for (var i = 0; i < this.buttonData.length; ++i) {
         if (i !== this.buttonData.length - 1) {
            button = new Sprite('button',
                        new SpriteSheetArtist(this.spritesheet,
                                              this.blueButtonCells),
                        [ this.paceBehavior,
                          this.blueButtonDetonateBehavior ]);
         }
         else {
            button = new Sprite('button',
                        new SpriteSheetArtist(this.spritesheet,
                                              this.goldButtonCells),
                        [ this.paceBehavior,
                          this.goldButtonDetonateBehavior ]);
         }
         ...
      }
   },
   ...
};


Both buttons pace back and forth on their platforms. Recall from Chapter 7 that the blue button’s detonate behavior blows up two bees earlier in the level to clear a path for the runner. The gold button’s detonate behavior reveals the winning animation.

As required by all behaviors, the gold button’s detonate behavior implements an execute() method, as you can see in Example 17.33. Snail Bait invokes that execute() method for every animation frame in which the gold button is visible.

Example 17.33. The gold button’s detonate behavior


var SnailBait = function () {
   ...

   this.goldButtonDetonateBehavior = {
      execute: function(sprite, now, fps, lastAnimationFrameTime) {
         if ( ! sprite.detonating) { // trigger
            return;
         }

         snailBait.revealWinningAnimation();
         sprite.detonating = false;
      }
   };
   ...
};


The execute() method first checks to see if the sprite—which is always the gold button because the behavior is attached to it—is detonating; if not, there’s nothing to do, and the method returns. If the button is detonating, the execute() method reveals the winning animation and resets the button’s detonating property to false.

The gold button’s detonating property, which is the trigger for the gold button’s detonate behavior, is false until the runner lands on the gold button. In that case, the runner’s fall behavior sets the detonating property to true, as you can see in Example 17.34.

Example 17.34. Detonate the gold button


SnailBait.prototype = {
   ...
   this.fallBehavior = {
      ...
      processCollision: function (sprite, otherSprite) {
         if (sprite.jumping && 'platform' === otherSprite.type) {
            this.processPlatformCollisionDuringJump(sprite, otherSprite);
         }
         else if ('button' === otherSprite.type) {
            if (sprite.jumping && sprite.descendTimer.isRunning() ||
                sprite.falling) {
               otherSprite.detonating = true;
            }
         }
      },
      ...
};


When the runner’s fall behavior sets the gold button’s detonating property to true, the gold button’s detonate behavior invokes Snail Bait’s revealWinningAnimation() method, as you saw in Example 17.33. That method is listed in Example 17.35.

Example 17.35. Reveal the winning animation


SnailBait.prototype = {
   ...

   revealWinningAnimation: function () {
      var WINNING_ANIMATION_FADE_TIME = 5000,
          SEMI_TRANSPARENT = 0.25;

      this.bgVelocity = 0;
      this.playing = false;
      this.loadingTitleElement.style.display = 'none';

      this.fadeInElements(this.runnerAnimatedGIFElement,
                          this.loadingElement);

      this.scoreElement.innerHTML = 'Winner!';
      this.canvas.style.opacity = SEMI_TRANSPARENT;

      setTimeout( function () {
         snailBait.runnerAnimatedGIFElement.style.display = 'none';
         snailBait.fadeInElements(snailBait.canvas);
         snailBait.gameOver();
      }, WINNING_ANIMATION_FADE_TIME);
   },
   ...
};


The revealWinningAnimation() method fades in the loading screen elements, without the Loading... text that the game displays when it loads resources. As the animated GIF fades in, the canvas fades out to an opacity of 0.25. After five seconds, revealWinningAnimation() hides the animated GIF, fades the canvas in to fully opaque, and ends the game.

17.7. Conclusion

In this chapter you saw how to implement most of Snail Bait’s user interface. Not all game programming involves programming gameplay, and as a result, game developers spend a significant amount of time implementing features that don’t affect gameplay.

At this point we’ve implemented all Snail Bait’s gameplay and most of its user interface. The remaining chapters show you how to implement a developer backdoor, how to store high scores and in-game metrics on the server, and how to deploy your games.

17.8. Exercises

1. Activate Snail Bait’s developer backdoor by pressing CTRL-d. Experiment with the running slowly threshold to see how fast the game runs. Hint: If you set the threshold at 60 fps, the warning will most likely show constantly. If you set it to 10 fps, you should hardly ever see the warning. Somewhere in between lies the game’s frame rate.

2. Move the lives indicator from the left side of the game to the right.

3. Change the opacity of the score element during gameplay from fully opaque to an opacity of 0.5. When the game updates the score element, temporarily change the element’s opacity to fully opaque for 0.5 seconds to make the score element flash whenever the player captures an asset.

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

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