Chapter 18. Developer Backdoor

Game development is easier if you grant yourself special powers that are unavailable to other players. Those special powers make play-testing more efficient and can reveal insights into your game. For example, Figure 18.1 shows Snail Bait drawing collision rectangles and running in slow motion. Additionally, the developer has zoomed in on the action by using the browser’s hotkey for zooming.

Figure 18.1. Turning on collision rectangles and zooming in

Image

Turning on collision rectangles and running the game in slow-motion makes it easier to implement collision detection. With the game running at full speed without collision rectangles, it’s difficult to see exactly how collision detection takes place.

Snail Bait lets you turn on collision rectangles and run the game in slow-motion with a developer backdoor that you activate by pressing CTRL-d when the game is running. The backdoor’s features make it easier to play-test, and therefore to implement, the game. In this chapter, we explore the implementation of Snail Bait’s developer backdoor. More specifically, you will learn how to do the following:

• Reveal and hide a developer backdoor with a hot key (Section 18.3 on p. 485)

• Keep backdoor elements in sync with the game (Section 18.4 on p. 487)

• Draw collision detection bounding boxes (Section 18.5.1 on p. 491)

• Play test slow frame rates and tempos faster than the default (Section 18.6.4 on p. 502)

• Calibrate the running slowly threshold (Section 18.6.4 on p. 502)

• Run the game in slow motion (Section 18.6.5 on p. 503)

• Move directly to a location in a level (Section 18.7 on p. 506)

• Drag the game’s canvas (Section 18.7.5 on p. 511)

We start with an overview of Snail Bait’s developer backdoor before discussing its implementation.


Image Note: A backdoor is a good breeding ground for game features

Besides making it easier to implement games, developer backdoors can also be a source of inspiration for new game features. For example, running the game in slow motion could be the impetus for a power-up that temporarily runs the game in slow motion, making it easier for players to navigate the level.



Image Note: Backdoors vs. Easter eggs

Snail Bait’s developer backdoor has a lot in common with Easter eggs, which reveal hidden features for players instead of the developer. Some Easter eggs are rewards for some achievement, and others just offer random fun.


18.1. Snail Bait’s Developer Backdoor

Snail Bait’s developer backdoor is shown in Figure 18.2.

Figure 18.2. The developer backdoor

Image

The screenshot in Figure 18.2 shows Snail Bait with visible collision rectangles, running at 1/4 speed.

Besides the controls at the top of the game, the developer backdoor also displays a ruler at the bottom of the game’s canvas. That ruler, which the developer backdoor constantly updates, shows the horizontal offset in pixels from the beginning of the level.

The developer backdoor does not interfere in any way with gameplay, and its background is semi transparent so you can see the action underneath as you play the game.

The developer backdoor lets you do the following:

• Change Snail Bait’s running slowly threshold from the default of 40 frames per second (fps)

• Turn off the running slowly warning entirely

• Change the rate at which time flows through the game

• Draw collision rectangles

• Toggle smoke visibility

• Drag the game’s canvas horizontally

Recall from Chapter 11 that Snail Bait implements collision detection with bounding boxes. The developer backdoor makes those rectangles visible when the Draw collision rectangles checkbox is checked. And if the smoke from the smoking holes obscures your view, you can get rid of it by deselecting the Smoke checkbox.

Perhaps the most important feature of Snail Bait’s developer backdoor is its ability to modify the rate at which time flows through the game. That feature makes it possible to run the game in slow motion, which in turn makes it easier to see exactly how events unfold during the game. Besides running the game in slow motion, the developer backdoor also lets you run the game up to twice as fast as normal, which is useful for calibrating the rate at which the game’s action takes place.

One thing you can’t see in Figure 18.2 is the developer’s ability to drag the game’s canvas horizontally when the developer backdoor is visible. Dragging the canvas repositions the runner, so you can go directly to specific sections of the level. You can orient your position with the ruler at the bottom of the backdoor, which shows how many pixels the game has scrolled horizontally. The game constantly updates those values when the backdoor is visible.

Now that we’ve seen the developer backdoor’s features, let’s see how to implement them.


Image Best Practice: Cut development time with a developer backdoor

For most nontrivial games, a developer backdoor should cut your development time by making it easier to implement error-prone or mathematically intensive code, such as collision detection.



Image Best Practice: Keep the cost/benefit ratio in mind

Developer backdoors are useful tools that can be fun and rewarding to implement, but they are not free. Implementing a developer backdoor can take a considerable amount of work, so you want to ensure that the backdoor adds substantially more value than the amount of work you put into it.


18.2. The Developer Backdoor’s HTML and CSS

We start with a nascent developer backdoor that’s devoid of features, as shown in Figure 18.3.

Figure 18.3. Revealing the developer backdoor

Image

Inside the game’s arena (a DIV that contains everything in the game) we add a DIV whose identifier is snailbait-developer–backdoor, as shown in Example 18.1. To begin, that element is empty.

Example 18.1. The HTML for the developer backdoor


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

      <div id='snailbait-arena'>
         <div id='snailbait-developer-backdoor'>
            ...
         </div>
         ...
      </div>
   </body>
</html>


The CSS for the snailbait-developer–backdoor element is shown in Example 18.2.

Example 18.2. The CSS for the developer backdoor


#snailbait-developer-backdoor {
   color: navy;
   position: absolute;
   margin: 0 auto;
   margin-top: 72px;
   margin-left: 1px;
   width: 800px;
   height: 90px;
   border: thin solid brickred;
   background: rgba(255, 255, 200, 0.7);

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

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


The preceding CSS sizes and positions the developer backdoor, in addition to specifying an opacity transition and making the element initially invisible. The developer backdoor, when visible, is partially transparent. That transparency is a result of the element’s background property; the alpha component of the background color is set to 0.7, as you can see in Example 18.2.

Snail Bait accesses the snailbait-developer-backdoor element as shown in Example 18.3.

Example 18.3. Accessing the backdoor element


var SnailBait = function () {
   ...

   this.developerBackdoorElement =
      document.getElementById('snailbait-developer-backdoor');

   this.developerBackdoorVisible = false;
   ...
};


Besides accessing the snailbait-developer-backdoor element, Snail Bait’s constructor also declares a variable named developerBackdoorVisible whose value corresponds to the backdoor’s visibility.

Now that we’ve seen the HTML element for the developer backdoor and how Snail Bait accesses that element in JavaScript, let’s see how the game reveals and hides the backdoor.


Image Note: Snail Bait’s developer backdoor is a heads-up display

By providing a see-through background, Snail Bait’s developer backdoor lets the developer play the game with the backdoor visible. That’s what’s known as a heads-up display (HUD). In Chapter 19, we implement another HUD with an entirely transparent background for displaying high scores.


18.3. Reveal and Hide the Developer Backdoor

With a reference to the developer backdoor in hand, Snail Bait implements a revealDeveloperBackdoor() method, listed in Example 18.4.

The revealDeveloperBackdoor() method uses Snail Bait’s fadeInElements() method, which we discussed in Chapter 5, to fade the snailbait-developer-backdoor element into view. Recall that fadeInElements() relies on the elements it manipulates having a CSS transition on their opacity property. Without the CSS transition on the snailbait-developer-backdoor element, the element would remain invisible. The revealDeveloperBackdoor() method also sets the game’s developerBackdoorVisible property to true and sets the cursor for the game’s canvas to the move cursor.

Example 18.4. Revealing the developer backdoor


SnailBait.prototype = {
   ...

   revealDeveloperBackdoor: function () {
      this.fadeInElements(this.developerBackdoorElement,
                          this.rulerCanvas);
      ...

      this.canvas.style.cursor = 'move';
      this.developerBackdoorVisible = true;
   },
   ...
};


Hiding the developer backdoor is the inverse of revealing it, as you can see in Example 18.5.

Example 18.5. Hiding the developer backdoor


SnailBait.prototype = {
   ...

   hideDeveloperBackdoor: function () {
      this.fadeOutElements(this.developerBackdoorElement,
                           this.rulerCanvas,
                           this.DEVELOPER_BACKDOOR_FADE_DURATION);

      this.canvas.style.cursor = this.initialCursor;
      this.developerBackdoorVisible = false;
   },
   ...
};


Snail Bait’s hideDeveloperBackdoor() method invokes the game’s fadeOutElements() method, sets the developerBackdoorVisible property to false, and restores the cursor for the game’s canvas. See Section 18.7.5, “Drag the Canvas,” on p. 511 for more about the developer backdoor’s cursor.

Snail Bait invokes the revealDeveloperBackdoor() and hideDeveloperBackdoor() methods from its keydown event handler, which is partially listed in Example 18.6.

Example 18.6. Activating the developer backdoor by pressing CTRL-d


window.addEventListener(
   'keydown',

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

      if (key === 68 && e.ctrlKey) { // CTRL-d
         if ( ! snailBait.developerBackdoorVisible) {
            snailBait.revealDeveloperBackdoor();
         }
         else {
            snailBait.hideDeveloperBackdoor();
         }
         return;
      }
      ...
   }
);


When a player presses CTRL-d, Snail Bait reveals the developer backdoor if it’s not visible. If the developer backdoor was visible when the player pressed CTRL-d, the game makes it invisible.

18.4. Update the Developer Backdoor’s Elements

At this point, the developer backdoor doesn’t have any elements, but by the end of this chapter, it will have sliders, checkboxes, and readouts, as you saw in Figure 18.2 on p. 481.

To update those elements when Snail Bait reveals the developer backdoor, the game calls three methods—updateDeveloperBackdoorSliders(), update-DeveloperBackdoorCheckboxes(), and updateDeveloperBackdoorReadouts()—in the revised revealDeveloperBackdoor() method listed in Example 18.7.

As we implement the backdoor’s features throughout the rest of this chapter, we fill in the implementation of those three methods to keep the developer backdoor’s elements in sync with the game when the backdoor is revealed.

Example 18.7. Revealing the developer backdoor, revised


SnailBait.prototype = {
   ...

   updateDeveloperBackdoorSliders: function () {
      // TODO
   },

   updateDeveloperBackdoorCheckboxes: function () {
      // TODO
   },

   updateDeveloperBackdoorReadouts: function () {
      // TODO
   },

   revealDeveloperBackdoor: function () {
      this.fadeInElements(this.developerBackdoorElement,
                          this.rulerCanvas);

      this.updateDeveloperBackdoorSliders();
      this.updateDeveloperBackdoorCheckboxes();
      this.updateDeveloperBackdoorReadouts();

      this.canvas.style.cursor = 'move';
      this.developerBackdoorVisible = true;
   },
   ...
};


At this point we have an empty backdoor with a semi transparent background that we can reveal and hide by pressing CTRL-d. Next, let’s add some features.

18.5. Implement the Developer Backdoor’s Checkboxes

Figure 18.4 shows the developer backdoor with checkboxes.

Figure 18.4. The developer backdoor’s checkboxes

Image

The developer backdoor’s checkboxes let the developer do the following:

• Turn collision rectangles on and off

• Enable or disable the game’s running slowly warning

• Show or hide smoking holes

The HTML for the backdoor’s checkboxes is listed in Example 18.8.

Example 18.8. The HTML for the developer backdoor’s checkboxes


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

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

         <div id='snailbait-developer-backdoor'>
             ...

             <div class='snailbait-group'
                     id='snailbait-collision-rectangles'>
                <input type='checkbox'
                       id='snailbait-collision-rectangles-checkbox'/>
                Draw collision rectangles
            </div>

            <div class='snailbait-group'
                    id='snailbait-detect-running-slowly'>
               <input type='checkbox'
                      id='snailbait-detect-running-slowly-checkbox'
                      checked/>
               Warn when running slowly
            </div>

            <div class='snailbait-group'
                    id='snailbait-smoking-holes'>
               <input type='checkbox'
                      id='snailbait-smoking-holes-checkbox'
                      checked/>
               Smoke
            </div>
         </div>
         ...
      </div>
   </body>
</html>


The preceding HTML has three DIVs, each of which contains a checkbox. The CSS for those elements is listed in Example 18.9.

Example 18.9. The CSS for the developer backdoor’s checkboxes


.snailbait-group {
   position: absolute;
   text-align: center;
}

#snailbait-collision-rectangles {
   margin-left: 540px;
   margin-top: 0.5em;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.5);
}

#snailbait-detect-running-slowly {
   margin-left: 540px;
   margin-top:  2.0em;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.5);
}

#snailbait-smoking-holes {
   margin-left: 540px;
   margin-top: 3.5em;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.5);
}


The CSS class for the DIVs that contain checkboxes is snailbait-group. The browser positions groups absolutely and center-aligns their contents. The preceding CSS also specifies a shadow for the checkboxes’s text to make it appear as though the navy-blue text has been pressed into the backdoor’s yellow background.

Now that we’ve created the checkboxes and added them to the developer backdoor, let’s implement their functionality.

18.5.1. Show and Hide Collision Rectangles

When you’re debugging collision detection with bounding boxes, it’s helpful if you can see collision rectangles for all the game’s sprites, as shown in Figure 18.2. As it currently stands, a sprite’s calculateCollisionRectangle() method returns a JavaScript object representing the sprite’s collision rectangle as discussed in Chapter 11, but sprites cannot draw the rectangle. We give sprites that ability with a Sprite.drawCollisionRectangle() method, listed in Example 18.10.

Example 18.10. Drawing collision rectangles


Sprite.prototype = {
   ...

   drawCollisionRectangle: function (context) {
      var COLLISION_RECTANGLE_COLOR = 'white',
          COLLISION_RECTANGLE_LINE_WIDTH = 2.0,
          r = this.calculateCollisionRectangle();

      context.save();

      context.beginPath();

      context.strokeStyle = COLLISION_RECTANGLE_COLOR;
      context.lineWidth   = COLLISION_RECTANGLE_LINE_WIDTH;

      context.strokeRect(r.left + this.hOffset, r.top,
                         r.right - r.left, r.bottom - r.top);

      context.restore(); // resets strokeStyle and lineWidth
   },
   ...
};


The drawCollisionRectangle() method draws a white, two-pixel-wide rectangle representing the sprite’s collision rectangle.

Sprites draw their collision rectangles only some of the time, so we add a showCollisionRectangle Boolean property to sprites that controls whether they draw the rectangle or not. That property is initially false, as shown in Example 18.11.

Example 18.11. Sprites keep track of whether they draw their collision rectangle


var Sprite = function () {
   ...

   this.showCollisionRectangle = false;
   ...
};


Finally we revise Sprite.draw() to draw the collision rectangle if the sprite’s showCollisionRectangle property is true, as listed in Example 18.12.

Example 18.12. Sprite.draw(), revised to draw collision rectangles


Sprite.prototype = {
   ...

   draw: function (context) {
      context.save();

      context.globalAlpha = this.opacity;

      if (this.visible && this.artist) {
         this.artist.draw(this, context);
      }

      if (this.showCollisionRectangle) {
         this.drawCollisionRectangle(context);
      }

      context.restore();
   },
   ...
};


As a result of the preceding additions to sprites, we can now draw a sprite’s collision rectangle simply by setting its showCollisionRectangle property to true; subsequently, the sprite will draw its collision rectangle in the next animation frame.

To toggle the visibility of sprite collision rectangles when the developer clicks the Draw collision rectangles checkbox, Snail Bait first obtains a reference to the Draw collision rectangles checkbox as shown in Example 18.13.

Example 18.13. Accessing the collision rectangles checkbox element


var SnailBait = function () {
   ...

   this.collisionRectanglesCheckboxElement =
      document.getElementById(
         'snailbait-collision-rectangles-checkbox');
   ...
};


Snail Bait adds a change event handler to the checkbox, as you can see in Example 18.14.

Example 18.14. Handling change events for the collision rectangles checkbox


snailBait.collisionRectanglesCheckboxElement.addEventListener(
   'change',

   function (e) {
      var show = snailBait.collisionRectanglesCheckboxElement.checked;

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


When the developer clicks the Draw collision rectangles checkbox, the preceding event handler iterates over Snail Bait’s sprites, synchronizing each one’s showCollisionRectangle property with the checkbox.

18.5.2. Enable and Disable the Running Slowly Warning

Recall that Snail Bait shows a warning, whose implementation we discussed in Chapter 17, when the game runs slowly. By default, Snail Bait defines slowly as “less than 40 frames per second”. The developer backdoor lets you modify that threshold or turn off the warning entirely, which lets you play-test slow frame rates without interruption and calibrate the running slowly threshold.

To get rid of the running slowly warning, Snail Bait’s constructor obtains a reference to the Warn when running slowly checkbox, as shown in Example 18.15.

Example 18.15. Accessing the running slowly checkbox element


var SnailBait = function () {
   ...

   this.detectRunningSlowlyCheckboxElement =
      document.getElementById(
        'snailbait-detect-running-slowly-checkbox');
   ...
};


When you click the checkbox, the event handler listed in Example 18.16 kicks in.

Example 18.16. Handling change events for the running slowly checkbox


snailBait.detectRunningSlowlyCheckboxElement.addEventListener(
   'change',

   function (e) {
      snailBait.showSlowWarning =
         snailBait.detectRunningSlowlyCheckboxElement.checked;
   }
);


The preceding event handler keeps Snail Bait’s showSlowWarning property in sync with the Warn when running slowly checkbox. That property is the Boolean variable discussed in Chapter 17 that controls whether the game displays the running slowly warning.

18.5.3. Show and Hide Smoking Holes

The developer backdoor lets the developer hide smoking holes. Snail Bait’s constructor obtains a reference to the Smoke checkbox, as shown in Example 18.17.

Example 18.17. Accessing the smoking holes element


var SnailBait = function () {
   ...

   this.smokingHolesCheckboxElement =
      document.getElementById('snailbait-smoking-holes-checkbox');
   ...
};


Snail Bait adds a change event handler to the Smoke checkbox. That event handler is listed in Example 18.18.

Example 18.18. Handling change events for the smoking holes checkbox


snailBait.smokingHolesCheckboxElement.addEventListener(
   'change',

   function (e) {
      snailBait.showSmokingHoles =
         snailBait.smokingHolesCheckboxElement.checked;
   }
);


The preceding event handler keeps Snail Bait’s showSmokingHoles property—which determines whether Snail Bait draws smoking holes—in sync with the Smoke checkbox.

18.5.4. Update Backdoor Checkboxes

To keep the backdoor in sync with the game, Snail Bait updates two of its checkboxes with the updateDeveloperBackdoorCheckboxes() method, which is invoked by revealDeveloperBackdoor(). The updateDeveloperBackdoorCheckboxes() method is listed in Example 18.19.

Example 18.19. Updating the developer backdoor checkboxes


var SnailBait = function () {
   ...

   updateDeveloperBackdoorCheckboxes: function () {
      this.detectRunningSlowlyCheckboxElement.checked =
         this.showSlowWarning;

      this.smokingHolesCheckboxElement.checked =
         this.showSmokingHoles;
   },
   ...
};


The preceding method ensures that the Warn when running slowly and Smoke checkboxes represent the current state of the game when Snail Bait reveals the developer backdoor. Snail Bait doesn’t keep track of whether sprites draw their collision rectangles, so the game does not update the Draw collision rectangle’s checkbox when Snail Bait reveals the developer backdoor.

18.6. Incorporate the Developer Backdoor Sliders

Snail Bait uses custom sliders for setting the running slowly threshold and the game’s time rate. The developer can change a slider’s value by dragging the slider’s knob or clicking in its rail, as shown in Figure 18.5.

Figure 18.5. Manipulating the slider knob by dragging (top) and clicking (bottom)

Image

The implementation of the backdoor’s sliders is far afield from game development and so is not discussed in this book. See Core HTML5 Canvas (Prentice Hall, 2012) to learn how sliders are implemented with the canvas element.

In this section, we focus on how Snail Bait uses sliders in the developer backdoor. First, we add the code for the slider implementation, which resides in a file of its own, to Snail Bait’s js (JavaScript) directory, as you can see from Figure 18.6.

Figure 18.6. The sliders.js file

Image

Sliders are simple to use. As you can see from Table 18.1, which lists the slider methods that Snail Bait uses, you can draw and erase sliders, add change listeners to them, and append them to HTML elements.

Table 18.1. Slider methods

Image

Sliders do not keep track of a value; instead, they have a knobPercent property that represents the percentage of the slider’s maximum value. The knobPercent is a number from 0.0 to 1.0. To calculate a slider’s value, multiply the knobPercent by the slider’s maximum value; for example, the Running slowly threshold slider’s maximum value is 60 (fps), so when the knob is positioned in the middle of the slider, the slider’s knobPercent is 0.5 and the slider’s value is 30.

To incorporate sliders into Snail Bait’s backdoor, we do the following:

• Specify the HTML and CSS for the backdoor’s sliders

• Access slider readouts in Snail Bait’s JavaScript

• Create and initialize the backdoor’s sliders

• Wire the running slowly slider to the game

• Wire the time rate slider to the game

• Wire the game to the time rate slider


Image Note:

Snail Bait creates sliders and appends them to HTML elements with the slider’s appendTo() method because that’s the way sliders were implemented. There are many ways to implement sliders, and components in general. HTML5 now has specifications for standard components, which standardizes their implementations, thereby making them more widespread and useful. However, when this book went to press support for HTML5 components among browsers was sparse. See www.w3.org/TR/components-intro for more information about HTML5 components.


18.6.1. Specify the HTML and CSS for the Backdoor’s Sliders

We include the slider.js file and add the markup for the backdoor sliders to Snail Bait’s HTML, as shown in Example 18.20.

Example 18.20. The HTML for the backdoor’s sliders


<!DOCTYPE html>
<html>
   ...

   <body>
      ...

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

         <div id='developer-backdoor'>
             ...

            <div class='snailbait-group'
                    id='snailbait-running-slowly-threshold'>
               <span class='snailbait-prompt'
                     id='snailbait-running-slowly-prompt'>
                  Running slowly threshold
               </span>

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

               <span class='snailbait-readout'
                     id='snailbait-running-slowly-readout'>
                        40
               </span>&nbsp;fps
            </div>

            <div class='snailbait-group'
                    id='snailbait-time-rate'>
               <span class='snailbait-prompt'
                     id='snailbait-time-rate-prompt'>
                  Time rate</span>

               <div id='snailbait-time-rate-slider'></div>

               <span class='snailbait-readout'
                     id='snailbait-time-rate-readout'>100</span>%
            </div>
         ...

      </div>
      ...

      <script src='js/slider.js'></script>
      ...
   </body>
</html>


Each slider resides in a DIV whose class is snailbait-group. As discussed in Section 18.5, “Implement the Developer Backdoor’s Checkboxes,” on p. 488, groups are absolutely positioned and their elements are center-aligned. Each DIV contains a title above the slider, an empty DIV into which Snail Bait inserts a slider, and the slider’s readout.

The preceding HTML uses four DIVs and four span elements. The CSS for the DIVs is listed in Example 18.21 (there is no CSS for the span elements).

Example 18.21. The CSS for the backdoor’s sliders


#snailbait-running-slowly-threshold {
   margin-left: 30px;
   margin-top: 10px;
   width: 250px;
   height: 50px;
}

#snailbait-running-slowly-slider {
   margin-left: 20px;
   width: 200px;
   height: 30px;
}

#snailbait-time-rate {
   margin-left: 245px;
   margin-top: 10px;
   width: 250px;
   height: 50px;
}

#snailbait-time-rate-slider {
   margin-left: 20px;
   width: 200px;
   height: 30px;
}


Now that you’ve seen the HTML and CSS for the developer backdoor’s sliders, let’s see how Snail Bait accesses those elements and uses them in the game’s JavaScript.

18.6.2. Access Slider Readouts in Snail Bait’s JavaScript

Snail Bait manipulates the sliders and their readouts, so the game’s JavaScript is concerned with the following elements:

snailbait-running-slowly-readout

snailbait-time-rate-readout

snailbait-running-slowly-slider

snailbait-time-rate-slider

The game accesses the readout elements as shown in Example 18.22.

Example 18.22. Accessing the slider readout elements


var SnailBait = function () {
   ...

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

   this.timeRateReadoutElement =
      document.getElementById('snailbait-time-rate-readout');
   ...
};


Snail Bait does not directly access the HTML elements containing the sliders (the running-slowly-slider and time-rate-slider DIVs); instead, it merely references their identifiers, as you’ll see in the next section.

18.6.3. Create and Initialize the Backdoor’s Sliders

Sliders are implemented as JavaScript objects, so you create them with the new operator, as shown in Example 18.23.

Example 18.23. Creating the backdoor sliders


var SnailBait = function () {
   ...

   this.runningSlowlySlider =
      new COREHTML5.Slider('blue',       // stroke style
                           'royalblue'); // fill style
   this.timeRateSlider =
      new COREHTML5.Slider('brickred',   // stroke style
                           'red');       // fill style
   ...
};


Snail Bait’s constructor creates two instances of COREHTML5.Slider, and subsequently the initializeDeveloperBackdoorSliders() method appends them to their appropriate HTML elements, as shown in Example 18.24.

Example 18.24. Initializing the backdoor’s sliders


SnailBait = function () {
   ...

   this.developerBackdoorSlidersInitialized = false;
   ...
};

SnailBait.prototype = {
   ...

   initializeDeveloperBackdoorSliders: function () {
      this.timeRateSlider.appendTo(
         'snailbait-time-rate-slider'
      );
      this.runningSlowlySlider.appendTo(
         'snailbait-running-slowly-slider'
      );

      this.developerBackdoorSlidersInitialized = true;
   },
   ...
};


Given the identifier of an HTML element, a slider’s appendTo() method obtains a reference to the associated element, appends the slider to that element, and resizes the slider to fit snugly within its element.

Snail Bait appends each slider to its appropriate HTML element only once, so the game uses a Boolean variable to keep track of whether it has initialized the sliders. How Snail Bait invokes the initializeDeveloperBackdoorSliders method was discussed in Section 18.6.7, “Update Sliders Before Revealing the Backdoor,” on p. 504.

18.6.4. Wire the Running Slowly Slider to the Game

Snail Bait adds a change listener to the running slowly slider that sets the game’s running slowly threshold to the slider’s current value and updates the running slowly readout element, as shown in Example 18.25.

Example 18.25. The running slowly slider’s change listener


snailBait.runningSlowlySlider.addChangeListener(function (e) {
   var threshold =
      (snailBait.runningSlowlySlider.knobPercent *
         snailBait.MAX_RUNNING_SLOWLY_THRESHOLD).toFixed(0);

   snailBait.runningSlowlyThreshold = threshold;
   snailBait.runningSlowlyReadoutElement.innerHTML = threshold;
});


18.6.5. Wire the Time Rate Slider to the Game

Snail Bait also implements a change listener that synchronizes the game’s time rate with the position of the slider’s knob (represented by the slider’s knobPercent property), as shown in Example 18.26. The change listener also updates the backdoor’s time rate readout.

Example 18.26. The time rate slider’s change listener


snailBait.timeRateSlider.addChangeListener(function (e) {
   // Enforce a minimum value

   if (snailBait.timeRateSlider.knobPercent < 0.01) {
      snailBait.timeRateSlider.knobPercent = 0.01;
   }

   // Set time rate

   snailBait.setTimeRate(snailBait.timeRateSlider.knobPercent *
                        (snailBait.MAX_TIME_RATE));

   // Update the time rate readout

   snailBait.timeRateReadoutElement.innerHTML =
      (snailBait.timeRate * 100).toFixed(0);
});


18.6.6. Wire the Game to the Time Rate Slider

Snail Bait’s running slowly threshold is a fixed commodity. Once the developer sets its value by manipulating the running slowly slider in the developer backdoor, the value does not change until the developer manipulates the slider again.

That’s not the case for the game’s time rate, however. For example, recall that Snail Bait slows time to 10 percent of its normal speed during transitions between lives. To make the developer-backdoor’s time rate slider reflect any changes to the game’s time rate, we modify Snail Bait’s setTimeRate() method, as shown in Example 18.27.

Example 18.27. Synchronizing the time rate


SnailBait.prototype = {
   ...

   setTimeRate: function (rate) {
      this.timeRate = rate;

      this.timeRateReadoutElement.innerHTML =
        (this.timeRate * 100).toFixed(0);

      this.timeRateSlider.knobPercent =
         this.timeRate /  this.MAX_TIME_RATE;
      if (this.developerBackdoorVisible)  {
         this.timeRateSlider.erase();
         this.timeRateSlider.draw(this.timeRate /
                                  this.MAX_TIME_RATE);
      }

      this.timeSystem.setTransducer( function (percent) {
         return percent * snailBait.timeRate;
      });
   },
   ...
};


The revised implementation of the setTimeRate() method updates both the time rate slider’s knob position and the text displayed by the associated readout element.

Because sliders are not sprites, Snail Bait does not automatically draw them every animation frame. So if the backdoor is visible, setTimeRate() manually erases and redraws the time rate slider with the new value. The setTimeRate() method also sets the transducer function for the game’s time system. See Chapter 10 for more information on Snail Bait’s time system.

18.6.7. Update Sliders Before Revealing the Backdoor

To keep the backdoor’s sliders in sync with the game, the revealDeveloperBackdoor() method discussed in Section 18.4, “Update the Developer Backdoor’s Elements,” on p. 487 invokes the game’s updateDeveloperBackdoorSliders() method, listed in Example 18.28.

Example 18.28. Updating the running slowly slider just before revealing the backdoor


SnailBait.prototype = {
   ...

   updateDeveloperBackdoorSliders: function () {
      if ( ! this.developerBackdoorSlidersInitialized)  {
        this.initializeDeveloperBackdoorSliders();
      }

      this.updateRunningSlowlySlider();
      this.updateTimeRateSlider();
   },
   ...
};


Besides updating the backdoor’s two sliders with the updateRunningSlowly-Slider() and updateTimeRateSlider() methods listed in Example 18.29, the preceding method initializes the sliders if they haven’t been initialized.

Example 18.29. Initializing the running slowly slider


SnailBait.prototype = {
   ...

   updateRunningSlowlySlider: function () {
      this.runningSlowlySlider.knobPercent =
         this.runningSlowlyThreshold /
         this.MAX_RUNNING_SLOWLY_THRESHOLD;

      this.runningSlowlySlider.erase();
      this.runningSlowlySlider.draw(this.runningSlowlyThreshold /
                                 this.MAX_RUNNING_SLOWLY_THRESHOLD);
   },

   updateTimeRateSlider: function () {
      this.timeRateSlider.knobPercent =
         this.timeRate * this.MAX_TIME_RATE;

      this.timeRateSlider.erase();
      this.timeRateSlider.draw(this.timeRate / this.MAX_TIME_RATE);
   },
   ...
};


The two preceding methods update their respective sliders by setting the slider’s knobPercent property and subsequently erasing and redrawing the slider.

At this point we’ve discussed the implementation of the controls at the top of the developer backdoor. We close out this chapter by discussing the implementation of the ruler at the bottom of the backdoor and the associated dragging of the game’s canvas.

18.7. Implement the Backdoor’s Ruler

When play-testing near the middle or end of a level, it’s tedious to play through preceding sections to get to your section of interest. Play-testing is more efficient if you can move directly to any location in a level.

Recall that Snail Bait scrolls the background by continuously translating the canvas context and redrawing the background in the horizontal location. The background’s location is fixed, but the coordinate system of the canvas context moves horizontally, so it appears as though the background is scrolling. See Chapter 3 for more details on how Snail Bait scrolls the background.

As the game runs, Snail Bait keeps track of the horizontal offset by which it scrolls the background. Initially, that offset is zero, represented by the game’s STARTING_BACKGROUND_OFFSET constant. If you change that constant and restart Snail Bait, the game will start at the offset you specify. To start the game at a specific location, set STARTING_BACKGROUND_OFFSET to the preferred offset and restart the game.

The ruler at the bottom of Snail Bait’s developer backdoor, illustrated in Figure 18.7, shows the horizontal offset from the start of the game. While the developer backdoor is visible, Snail Bait continuously updates the values displayed by the ruler.

Figure 18.7. The ruler

Image

Creating the ruler and incorporating it into the game requires the following four steps:

• Create and access the ruler canvas

• Fade the ruler

• Draw the ruler

• Update the ruler

Let’s look at each in turn.

18.7.1. Create and Access the Ruler Canvas

The ruler is implemented with a canvas element, as shown in Example 18.30.

Example 18.30. Adding the ruler canvas


<!DOCTYPE html>
<html>
   ...
   <body>
      ...
      <div id='snailbait-arena'>
         <div id='snailbait-developer-backdoor'>
            ...

            <canvas id='snailbait-ruler-canvas'
                    width='800'  height='20'>
               Your browser does not support HTML5 Canvas.
            </canvas>
         </div>
         ...
      </div>
   </body>
</html>


The backdoor’s ruler is 800 pixels wide, which is the same width as the game’s canvas. The ruler’s height, however, is only 20 pixels high. Snail Bait accesses the ruler’s canvas and context in the game’s constructor, as shown in Example 18.31.

Example 18.31. Accessing the ruler canvas and its context


SnailBait = function () {
   ...

   this.rulerCanvas = document.getElementById('snailbait-ruler-canvas');
   this.rulerContext = this.rulerCanvas.getContext('2d');
   ...
};


The CSS for the ruler is shown in Example 18.32.

Example 18.32. The ruler’s CSS


#snailbait-ruler-canvas {
   position: absolute;
   margin-top: -22px;
   border-top: thin solid rgba(255,255,0,0.5);
   border: thin solid rgba(0,0,255,0.5);
   background: rgba(255, 255, 200, 0.7);

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

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


18.7.2. Fade the Ruler

The ruler’s CSS in Example 18.32 exhibits the familiar pattern of initial invisibility with an opacity transition. Snail Bait’s revealDeveloperBackdoor() method and hideDeveloperBackdoor() methods take advantage of that pattern by invoking the game’s fadeInElements() and fadeOutElements() methods, as in Example 18.33.

Example 18.33. Fading the ruler in and out


SnailBait.prototype = {
   ...

   revealDeveloperBackdoor: function () {
      this.fadeInElements(this.developerBackdoorElement,
                          this.rulerCanvas);
      ...
   },

   hideDeveloperBackdoor: function () {
      var DEVELOPER_BACKDOOR_FADE_DURATION =  1000;
      ...

      this.fadeOutElements(this.developerBackdoorElement,
                           this.rulerCanvas,
                           DEVELOPER_BACKDOOR_FADE_DURATION);
   },
   ...
};


Recall from Chapter 5 that Snail Bait’s fadeInElements() and fadeOutElements() methods take a variable-length argument list, meaning they can simultaneously fade multiple elements in or out. To fade the ruler in concert with the backdoor’s controls, we pass a reference to the ruler’s canvas to the fadeInElements() and fadeOutElements() methods.

We specified the ruler canvas in Snail Bait’s HTML, accessed the canvas and its context in Snail Bait’s constructor function, and made the canvas fade in and out in concert with the backdoor’s controls. Next we draw the ruler’s contents.

18.7.3. Draw the Ruler

Snail Bait draws the backdoor’s ruler with the drawRuler() method listed in Example 18.34.

Example 18.34. Drawing the ruler


SnailBait.prototype = {
   ...

   drawRuler: function () {
      var MAJOR_TICK_SPACING = 50,
          MINOR_TICK_SPACING = 10,
          i;

      this.rulerContext.lineWidth = 0.5;
      this.rulerContext.fillStyle = 'blue';

      for (i = MINOR_TICK_SPACING; i < this.BACKGROUND_WIDTH;
           i += MINOR_TICK_SPACING) {
         if (i % MAJOR_TICK_SPACING === 0) {
            this.drawRulerMajorTick(i);
         }
         else {
            this.drawRulerMinorTick(i);
         }
      }
   },
   ...
};


The drawRuler() method draws major and minor tick marks with the drawRulerMajorTick() and drawRulerMinorTick() methods, which are listed in Example 18.35.

Example 18.35. Drawing the ruler tick marks


SnailBait.prototype = {
   ...

   drawRulerMajorTick: function (i) {
      var MAJOR_TICK_BOTTOM = this.rulerCanvas.height,
          MAJOR_TICK_TOP = this.rulerCanvas.height/2 + 2,
          text = (this.spriteOffset + i).toFixed(0);

      this.rulerContext.beginPath();
      this.rulerContext.moveTo(i + 0.5, MAJOR_TICK_TOP);
      this.rulerContext.lineTo(i + 0.5, MAJOR_TICK_BOTTOM);

      this.rulerContext.stroke();
      this.rulerContext.fillText(text, i-10, 10);
   },
   drawRulerMinorTick: function (i) {
      var MINOR_TICK_BOTTOM = this.rulerCanvas.height,
          MINOR_TICK_TOP = 3*this.rulerCanvas.height/4;

      this.rulerContext.beginPath();
      this.rulerContext.moveTo(i + 0.5, MINOR_TICK_TOP);
      this.rulerContext.lineTo(i + 0.5, MINOR_TICK_BOTTOM);
      this.rulerContext.stroke();
   },
   ...
};


The preceding methods use the moveTo() and lineTo() methods of the ruler’s canvas context to create a linear path, and the stroke() method to draw each line. The drawMajorTick() method draws the text associated with the tick with the canvas context’s fillText() method. See Chapter 3 for more information on drawing with the HTML5 canvas element.

Example 18.36 shows the eraseRuler() method, which clears the ruler canvas.

Example 18.36. Erasing the ruler


SnailBait.prototype = {
   ...

   eraseRuler: function () {
      this.rulerContext.clearRect(0, 0, this.rulerCanvas.width,
                                        this.rulerCanvas.height);
   },
   ...
};


18.7.4. Update the Ruler

As the developer plays the game with the developer backdoor visible, Snail Bait continuously updates the backdoor’s ruler with a simple addition to the game’s draw() method, as shown in Example 18.37.

Example 18.37. Snail Bait’s draw() method, revised


SnailBait.prototype = {
   ...

   draw: function (now) {
      ...

      if (this.developerBackdoorVisible) {
         this.eraseRuler();
         this.drawRuler();
      }
   },
   ...
};


Every time Snail Bait calls its draw() method—meaning for every animation frame—it checks to see if the developer backdoor is visible; if so, it redraws the ruler.

Now that you’ve seen how Snail Bait implements the backdoor’s ruler, let’s look at the backdoor’s final feature: dragging the game canvas.

18.7.5. Drag the Canvas

The developer backdoor’s ruler lets the developer restart Snail Bait at an exact location in the game’s only level by setting the background offset to a nonzero initial value, as discussed at the start of Section 18.7, “Implement the Backdoor’s Ruler,” on p. 506. The developer can discover the offset from the backdoor’s ruler. It’s more efficient, however, to simply drag the game’s canvas to a new location, as shown in Figure 18.8.

Figure 18.8. Dragging the game canvas from right to left (note the move cursor just below the backdoor)

Image

To signify that the developer can drag the game’s canvas horizontally when the developer backdoor is visible, Snail Bait changes the cursor for the game’s canvas to a move cursor, as you can see in the upper right of the top screenshot and the upper left in the bottom screenshot in Figure 18.8.

Snail Bait’s constructor records the original cursor for the game’s canvas as shown in Example 18.38.

Example 18.38. Recording the ruler canvas’s original cursor


SnailBait = function () {
   ...

   this.initialCursor = this.canvas.style.cursor;
   ...
};


When Snail Bait reveals the developer backdoor, it sets the cursor to the move cursor. When it hides the backdoor, it restores the cursor to the original cursor recorded by the game’s constructor, as shown in Example 18.39.

Example 18.39. Changing the game’s cursor when the developer backdoor is visible


SnailBait.prototype = {
   ...

   revealDeveloperBackdoor: function () {
      ...

      this.canvas.style.cursor = 'move';
   },

   hideDeveloperBackdoor: function () {
      ...

      this.canvas.style.cursor = this.initialCursor;
   },
   ...
};


Snail Bait implements mouse event handlers to let the developer drag the game’s canvas when the backdoor is visible, as shown in Example 18.40.

Example 18.40. Snail Bait’s mouse event handlers


snailBait.canvas.onmousedown = function (e) {
   if (snailBait.developerBackdoorVisible) {
      snailBait.startDraggingGameCanvas(e);
   }
};

snailBait.canvas.onmousemove = function (e) {
   if (snailBait.developerBackdoorVisible && snailBait.dragging) {
      snailBait.dragGameCanvas(e);
   }
};

window.onmouseup = function (e) {
   if (snailBait.developerBackdoorVisible) {
      snailBait.stopDraggingGameCanvas();
   }
};


Notice the mouse-down and mouse-move event handlers are attached to the game’s canvas, whereas the mouse-up event handler is attached to the game’s window. If the developer inadvertently drags the mouse outside the canvas and then releases the mouse, attaching the mouse-up event handler to the window ensures that we capture that mouse-up event.

When the backdoor is visible, the preceding mouse-down event handler starts dragging the game’s canvas by invoking Snail Bait’s startDraggingGameCanvas() method, which is listed in Example 18.41.

Example 18.41. Start dragging the game canvas


SnailBait.prototype = {
   ...

   startDraggingGameCanvas: function (e) {
      this.mousedown = { x: e.clientX, y: e.clientY };
      this.dragging = true;
      this.runner.visible = false;

      this.backgroundOffsetWhenDraggingStarted =
         this.backgroundOffset;

      this.spriteOffsetWhenDraggingStarted =
         this.spriteOffset;

      e.preventDefault();
   },
   ...
};


The single argument to startDraggingGameCanvas() is the event object the browser passes to Snail Bait’s mouse-down event handler. That event object contains the location of the mouse-down event in its clientX and clientY properties. The startDraggingGameCanvas() method records that location in a mousedown object.

The game is live as the developer drags the game’s canvas, so to keep the runner from colliding with other sprites during the drag, the startDraggingGameCanvas() method makes the runner disappear by setting her visibility to false. The method also sets a dragging Boolean variable to true, and records the game’s offsets for the sprites and the background.

Before the startDraggingGameCanvas() method returns, it invokes the event object’s preventDefault() method. As its name implies, the preventDefault() method prevents the browser from reacting to the event as it normally would by default. Without the call to preventDefault(), the browser selects elements as the developer drags the mouse over them, as you can see in Figure 18.9.

Figure 18.9. Without calling preventDefault(): Accidentally selecting elements when dragging the game’s canvas is an unwanted effect.

Image

Snail Bait’s mouse-move event handler invokes the game’s dragGameCanvas() method when the backdoor is visible and dragging is underway. The dragGameCanvas() method is listed in Example 18.42.

Example 18.42. Dragging the game canvas


SnailBait.prototype = {
   ...

   dragGameCanvas: function (e) {
      var deltaX = e.clientX - this.mousedown.x;

      this.backgroundOffset =
         this.backgroundOffsetWhenDraggingStarted - deltaX;

      this.spriteOffset =
         this.spriteOffsetWhenDraggingStarted - deltaX;
   },
   ...
};


The dragGameCanvas() method calculates how far the mouse has moved horizontally since the developer began dragging the canvas. That delta is subtracted from the start-of-dragging values of the game’s background and sprite offsets, respectively, to set their new values.

When the developer releases the mouse after dragging the canvas, the window object’s mouse up event handler invokes the game’s stopDraggingGameCanvas(), which is listed in Example 18.43.

Example 18.43. Stop dragging the game canvas


SnailBait.prototype = {
   ...

   stopDraggingGameCanvas: function () {
      this.dragging = false;
      this.runner.visible = true;
   },
   ...
};


Snail Bait’s stopDraggingGameCanvas() method turns dragging off by resetting the dragging flag to false and makes the runner visible once again.

18.8. Conclusion

A backdoor with the right features can significantly reduce the time it takes to implement your game. On the other hand, it can take a significant amount of time to implement a backdoor in the first place, so you must be cognizant of the cost-to-benefit ratio of implementing your backdoor.

With the exception of obscuring smoking holes, the features implemented by Snail Bait’s developer backdoor—modifying the flow of time, changing the running slowly threshold, and drawing collision rectangles—are useful for facilitating the development of most games. It should be straightfroward to implement a custom developer backdoor based on the code discussed in this chapter.

18.9. Exercises

1. Play the game with the developer backdoor visible. Watch the time rate slider when you lose a life. Recall that the slider responds to time rate changes.

2. Remove the call to e.preventDefault() from startDraggingGameCanvas(), restart the game, and see if you can recreate the effect shown in Figure 18.9. Then restore the line of code and try again.

3. Implement a slow-motion power-up that Snail Bait activates when the player captures a sapphire. The power-up should run the game in slow motion for a period of time, perhaps a few seconds. Is that implementation an effective power-up as is, or does it need some work?

4. Reveal the developer backdoor by pressing CTRL-d and drag the canvas to reposition the runner on the platform with the gold button. Now you can win the game with (relative) ease by jumping straight up and down to stomp on the gold button. Remember that you can slow time to help you avoid snail bombs while jumping. Also, notice that it’s a bit tricky to reposition the runner. Would you implement repositioning the runner differently? Keep in mind that time spent implementing the backdoor is time spent not implementing the game.

5. Snail Bait’s draw() method first erases, then draws, the ruler when the developer backdoor is visible. Comment out the line of code that erases the ruler, restart the game and activate the backdoor. What does the ruler look like? Why?

6. With the developer backdoor visible, change the time rate to more than 100% to determine the fastest you can comfortably play the game. If you can play at 200%, modify the game’s code so that you can adjust the rate higher than 200%. What happens to the time rate after you lose a life?

7. Change the game’s MAX_TIME_RATE constant so that the developer backdoor’s time rate slider lets you adjust the time up to 5 times the default time rate. Start the game and verify that the default position for the time rate slider’s thumb has changed. See how far you can progress in the game when the time rate is 500%.

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

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