HTML5 game skeleton code for Boulder Bop
HTML5 game skeleton code for Boulder Bop
To get started, review the Boulder Bop skeletal code:
-
Boulder Bop Skeleton
When you view the code, click the game's Start and Sound buttons with the console window open (see Introduction to F12 Developer Tools). We've added a series of usefulconsole.log
statements to make it easier for you to see how the skeletal code works.
Note In order to simplify the code, Boulder Bop uses setInterval instead of requestAnimationFrame. Because requestAnimationFrame offers important benefits, we'll look specifically at how to convert the game from setInterval to requestAnimationFrame in the Replacing setInterveral with requestAnimationFrame in a HTML5 game section.
Markup
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=10" /> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> <title>Boulder Bop Skeleton</title> <style> html { height: 100%; /* Required for liquid/fluid layout. */ margin: 0; padding: 0; } body { height: 100%; /* Required for liquid/fluid layout. */ margin: 0; padding: 0; background-color: #056; } svg { margin: 0; padding: 0; } .metroButton { fill: #09F; cursor: pointer; } .metroButton:hover { fill: #666; } .metroText { fill: white; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; } </style> </head> <body> <svg width="100%" height="100%" viewbox="0 0 1200 800"> <defs> <g id="buttonGraphic"> <rect x="0" y="0" width="105" height="40" /> <!-- Note that this could be replaced with a much more complicated and colorful graphic. --> </g> <lineargradient id="skyGradient" x1="0" y1="0" x2="0" y2="100%"> <stop offset="0%" style="stop-color: darkblue;" /> <stop offset="73%" style="stop-color: #00BFFF;" /> <stop offset="87%" style="stop-color: #87CEFA" /> <stop offset="100%" style="stop-color: #999" /> </lineargradient> </defs> <g transform="translate(0, 10)"> <!-- Define the y-coordinate for the row of buttons and status indicators so that they can be moved vertically as a group. --> <g id="startButton" class="metroButton" transform="translate(10)"> <use xlink:href="#buttonGraphic" /> <!-- "xlink:" is required for WebKit browsers. --> <text class="metroText" x="22" y="31" font-size="30">Start</text> </g> <g id="soundButton" class="metroButton" transform="translate(130)"> <use xlink:href="#buttonGraphic" /> <text class="metroText" x="10" y="31" font-size="30">Sound</text> </g> <a id="infoButton" class="metroButton" xlink:href="boulderBopInfo.html" transform="translate(250)"> <use xlink:href="#buttonGraphic" /> <text class="metroText" x="28" y="31" font-size="30">Info</text> </a> <g id="level" transform="translate(855)"> <text class="metroText" x="30" y="32" font-size="30">Level: 1</text> </g> <g id="score" transform="translate(1028)"> <text class="metroText" x="9" y="32" font-size="30">Score: 0</text> </g> </g> <clipPath id="playingFieldClipPath"> <rect x="0" y="0" width="1180" height="725" /> </clipPath> <g id="playingField" transform="translate(10, 60)" clip-path="url(#playingFieldClipPath)"> <!-- Game objects are always on top of the background. --> <rect id="gameBackground" x="0" y="0" width="1180" height="725" fill="url(#skyGradient)" stroke="white" stroke-width="4" /> <g id="activeGameObjectsContainer"><!-- Active game objects injected via JavaScript here. --></g> <rect id="gameClickPlane" width="1180" height="725" opacity="0"></rect> <!-- Always on top so it can capture all click events. --> <g id="staticGameObjectsContainer"><!-- Static game objects (like bunkers) injected via JavaScript here. --></g> <!-- Note that it makes no sense to click a bunker and bunkers must always be on top. --> </g> </svg> <script> var FPS = 60; // The frames per second for the animations. Anything below 60 starts to look jerky. Adjusting this value does not effect the action-speed of the game. . . . </script> </body> </html>
html { height: 100%; /* Required for liquid/fluid layout. */ margin: 0; padding: 0; } body { height: 100%; /* Required for liquid/fluid layout. */ margin: 0; padding: 0; background-color: #056; }
height: 100%
values along with <svg width="100%" height="100%" ...>
are the key to SVG liquid layout.
Note Liquid
layout is useful because the page can adjust itself to accommodate
whatever form factor (such as phone) is being used to view the page.
This includes the four possible view states of a HTML5 Windows Store
app.
<clipPath id="playingFieldClipPath"> <rect x="0" y="0" width="1180" height="725" /> </clipPath> <g id="playingField" transform="translate(10, 60)" clip-path="url(#playingFieldClipPath)"> <!-- Game objects are always on top of the background. --> <rect id="gameBackground" x="0" y="0" width="1180" height="725" fill="url(#skyGradient)" stroke="white" stroke-width="4" /> <g id="activeGameObjectsContainer"><!-- Active game objects injected via JavaScript here. --></g> <rect id="gameClickPlane" width="1180" height="725" opacity="0"></rect> <!-- Always on top so it can capture all click events. --> <g id="staticGameObjectsContainer"><!-- Static game objects (like bunkers) injected via JavaScript here. --></g> <!-- Note that it makes no sense to click a bunker and bunkers must always be on top. --> </g>
playingField
<g>
element, set its origin at (10, 60) (relative to the SVG viewport), and apply the clipping path:<g id="playingField" transform="translate(10, 60)" clip-path="url(#playingFieldClipPath)">
playingField
child defines the background - a rectangular region using the skyGradient
linear gradient:<rect id="gameBackground" x="0" y="0" width="1180" height="725" fill="url(#skyGradient)" stroke="white" stroke-width="4" />
The next child (
activeGameObjectsContainer
) and last child(staticGameObjectsContainer
) determine the order (in the z-index
sense of the word) that game objects are attached to the DOM:<g id="activeGameObjectsContainer"><!-- Active game objects injected via JavaScript here. --></g> <rect id="gameClickPlane" width="1180" height="725" opacity="0"></rect> <!-- Always on top so it can capture all click events. --> <g id="staticGameObjectsContainer"><!-- Static game objects (like bunkers) injected via JavaScript here. --></g> <!-- Note that it makes no sense to click a bunker and bunkers must always be on top. -->
activeGameObjectsContainer
appears first, all its children are drawn below the contents of staticGameObjectsContainer
. For example, if a bunker image is a child of staticGameObjectsContainer
and a missile object is a child of activeGameObjectsContainer
,
when the bunker fires a missile (towards the sky), the missile is
initially drawn below the bunker image and only becomes visible as it
exits the bunker image.With the middle
gameClickPlane
rectangle, we can capture
all playing field click events except for those that occur on top of
static game objects. For example, clicking a bunker image should not
fire a missile because that could cause the missile to explode within
the bunker. To reiterate, the purpose of the gameClickPlane
"layer" is to convert the screen coordinates from a tap or mouse click
to playing field coordinates. This conversion allows a missile to be
sent from the closest bunker to that point.Next, let's look at the JavaScript for the Boulder Bop Skeleton.
JavaScript
Ignoring the JavaScriptFPS
"constant", all would-be global variables are stored in one single global location:var globals = {};
globals.helpers
and globals.game
. The event handlers are registered by a self-executing, anonymous function ((function() {...})();
).When writing the code, an attempt was made to isolate all game related functionality to
globals.game
. All other "helper" code (less event handler registration) is contained in globals.helpers
. For example, determining the distance between two SVG objects is not game specific per se. So, this code (euclideanDistance
) is contained within the globals.helpers
group as opposed to the globals.game
group.Of these three items (
globals.helpers
, globals.game
, and the event registration), let's look at the simplest first.Event registration
To avoid contaminating the global namespace unnecessarily, event registration is handled by a self-executing anonymous function:(function() { document.getElementById('startButton').addEventListener('click', handleStartButton, false); // Single touch events are transformed into click events in IE10 and above. document.getElementById('soundButton').addEventListener('click', handleSoundButton, false); document.getElementById('infoButton').addEventListener('click', handleInfoButton, false); document.getElementById('gameClickPlane').addEventListener('click', handleGameClickPlane, false); document.getElementsByTagName('svg')[0].addEventListener('dragstart', function(evt) { evt.preventDefault(); }, false); // Don't let the user drag the screen in that it's very easy to mistakenly drag the screen when playing the game at speed. /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ function handleStartButton() { console.log("handleStartButton() called"); var game = globals.game; if (game.getState().over) { game.init(); // Note that this sets spec.paused to true. } if (game.getState().paused) { game.run(); document.querySelector('#startButton text').style.textDecoration = "line-through"; } else { game.pause(); document.querySelector('#startButton text').style.textDecoration = "none"; } } // handleStartButton /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ function handleSoundButton() { console.log("handleSoundButton() called"); var game = globals.game; if (game.getState().sound) { game.setSound('mute'); // Mute any currently playing sounds. game.setState('sound', false); // Do not create any audio objects moving forward. document.querySelector('#soundButton text').style.textDecoration = "line-through"; } else { game.setState('sound', true); // Create audio objects moving forward. document.querySelector('#soundButton text').style.textDecoration = "none"; } } // handleSoundButton /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ function handleInfoButton() { console.log("handleInfoButton() called"); if (!globals.game.getState().paused) { // If the game is not paused, then it's running - so pause it. handleStartButton(); // Pause the game whilst the user looks at the info on the info webpage. } } // handleInfoButton /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ function handleGameClickPlane(evt) { console.log("handleGameClickPlane() fired, (" + evt.pageX + ", " + evt.pageY + ")"); } // handleGameClickPlane })(); // Execute this anonymous function now, thereby wiring up all event handlers.
document.getElementById('startButton').addEventListener('click', handleStartButton, false); // Single touch events are transformed into click events in IE10 and above. document.getElementById('soundButton').addEventListener('click', handleSoundButton, false); document.getElementById('infoButton').addEventListener('click', handleInfoButton, false); document.getElementById('gameClickPlane').addEventListener('click', handleGameClickPlane, false); document.getElementsByTagName('svg')[0].addEventListener('dragstart', function(evt) { evt.preventDefault(); }, false); // Don't let the user drag the screen in that it's very easy to mistakenly drag the screen when playing the game at speed.
function handleStartButton() { var game = globals.game; if (game.getState().over) { game.init(); // Note that this sets spec.paused to true. } if (game.getState().paused) { game.run(); document.querySelector('#startButton text').style.textDecoration = "line-through"; } else { game.pause(); document.querySelector('#startButton text').style.textDecoration = "none"; } } // handleStartButton
game.getState().paused
is true). The user must click the Start button to start the game, invoking game.run()
. The Start button also doubles as a pause button - a paused game is indicated by placing a line through the "Start" text. We next move onto
handleSoundButton
:function handleSoundButton() { console.log("handleSoundButton() called"); var game = globals.game; if (game.getState().sound) { game.setSound('mute'); // Mute any currently playing sounds. game.setState('sound', false); // Do not create any audio objects moving forward. document.querySelector('#soundButton text').style.textDecoration = "line-through"; } else { game.setState('sound', true); // Create audio objects moving forward. document.querySelector('#soundButton text').style.textDecoration = "none"; } } // handleSoundButton
handleSoundButton
function is similar to the handleStartButton
function and can be understood via handleSoundButton
's comments.Here's the
handleInfoButton
event handler:function handleInfoButton() { console.log("handleInfoButton() called"); if (!globals.game.getState().paused) { // If the game is not paused, then it's running - so pause it. handleStartButton(); // Pause the game whilst the user looks at the info on the info webpage. } } // handleInfoButton
handleInfoButton
function pauses the game before showing the user the info page by calling handleStartButton
(which pauses a running game). The game's info page is displayed to the user via a standard anchor tag: <a id="infoButton" class="metroButton" xlink:href="boulderBopInfo.html" transform="translate(250)">...</a>
(as of this writing, xlink
is required for other browsers).Next, we discuss the
handleGameClickPlane
event handler:function handleGameClickPlane(evt) { console.log("handleGameClickPlane() fired, (" + evt.pageX + ", " + evt.pageY + ")"); } // handleGameClickPlane })(); // Execute this anonymous function now, thereby wiring up all event handlers.
handleGameClickPlane
event handler just shows (to the console window) where the user clicked (in screen coordinates).Here's the last event handler:
document.getElementsByTagName('svg')[0].addEventListener('dragstart', function(evt) { evt.preventDefault(); }, false);
preventDefault()
.Next, we discuss skeletal game specific code.
Globals.game
Because Boulder Bop's game objects (in JavaScript) share some common characteristics, using some form of inheritance might simplify and/or speed development. Boulder Bop uses the functional inheritance pattern. Look at this code example to get an idea of how this pattern works:var globals = {}; globals.game = (function() { var that = {}; var PrimaryObject = function(spec) { // Private method. var that = {}; /* Define the methods and properties for the primary object, taking the values of the spec parameter into account. */ return that; } // PrimaryObject var SecondaryObject = function() { // Public method. // Specify the characteristics of a secondary object: var spec = {type: "second", state: "active"}; // Call the PrimaryObject constructor: var that = PrimaryObject(spec); /* Modify the "that" variable's methods and properties as inherited from the PrimaryObject constructor. */ return that; } // SecondaryObject that.SecondaryObject = SecondaryObject; // Make this method public. return that; // This is the "that" that ends up in globals.game. })();
PrimaryObject
constructor typically defines the properties and methods that are to be inherited by objects that call it. For example, the SecondaryObject
constructor calls the PrimaryObject
constructor in order to inherit all of its properties and methods:// Specify the characteristics of a secondary object: var spec = {type: "second", state: "active"}; // Call the PrimaryObject constructor: var that = PrimaryObject(spec);
SecondaryObject
constructor can be called from globals.game
because of the line that.SecondaryObject = SecondaryObject
. The PrimaryObject
constructor, however, cannot be called from globals.game
precisely because it lacks such a line.Now, we move on to the details of the code contained in
globals.game
:globals.game = (function (spec) { // The spec parameter is used as a local static variable in a number of the following methods. var _updateID = null; var _objects = []; // Will contain all of the game's objects. var _gameClickPlaneWidth = document.getElementById('gameClickPlane').width.baseVal.value; // In SVG user units. var _gameClickPlaneHeight = document.getElementById('gameClickPlane').height.baseVal.value; // In SVG user units. var _scoreBox = document.getElementById('score').firstElementChild; // A very small performance optimization (i.e., do this once). var _levelBox = document.getElementById('level').firstElementChild; // A very small performance optimization (i.e., do this once). var that = {}; // The object returned by the Game "constructor". /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var GameObject = function (spec) { // Private constructor. The base game object constructor - all game objects stem from this constructor. var that = {}; that.active = spec.active; if (typeof (that.active) == "undefined" || that.active == null) { that.active = true; } that.type = spec.type || 'unknown'; that.core = spec.core || null; that.x = spec.x; // The initial position of the game object. that.y = spec.y; if (getState().sound) { // True if the user wants sound to be played. False otherwise. that.audio = new Audio(spec.soundPath); // spec.soundPath is a path to an MP3 file. if (that.audio) { // If a valid spec.soundPath path was supplied, play the sound effect for the game object (otherwise don't). that.audio.preload = "auto"; // Attempt to reduce audio latency. that.audio.play(); } // if } // if /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var setPosition = function (x, y) { // Public method. that.x = x || 0; that.y = y || 0; }; // Game.GameObject.setPosition() that.setPosition = setPosition; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var getPosition = function () { // Public. return globals.helpers.createPoint(that.x, that.y); // In the form of an SVG point object, returns the position of this game object's position. }; // Game.GameObject.getPosition() that.getPosition = getPosition; // Make the method public. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var move = function () { // Public. The default move() method is a NOP. }; // Game.GameObject.move() that.move = move; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var collisionDetection = function () { // Public. The default collisionDetection() method is a NOP in that some of the objects created using GameObject() do not move. }; // Game.GameObject.collisionDetection() that.collisionDetection = collisionDetection; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var exited = function () { // Public. return (!that.active); // It's definitely true that a non-active game object has logically exited the game. }; // Game.GameObject.exited() that.exited = exited; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var remove = function () { // Public. var arrayIndex = _objects.indexOf(that); // Return the array index associated with this game object. var core = that.core; if (arrayIndex == -1) { console.log('Error in GameObject.remove(), "that" = ' + that + ' object not found in _objects[], arrayIndex = ' + arrayIndex); // Defensive programming good! return; } var removeChild = core.parentNode.removeChild(core); // Remove the game object from the DOM, and thus from the screen. if (!removeChild) { console.log('Error in GameObject.remove(), "that" = ' + that + ' object was not removed from DOM'); // Defensive programming good! return; } // if _objects.splice(arrayIndex, 1); // Remove the game object from the array of game objects. }; // Game.GameObject.remove() that.remove = remove; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var markActive = function (Boolean) { // Public. that.active = Boolean; // In honor of George Boole, "Boolean" is capitalized (and also because "boolean" is a reserved word). }; // Game.GameObject.markActive() that.markActive = markActive; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ return that; // Return the result of calling the GameObject constructor. } // Game.GameObject() /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var init = function () { // Public. console.log("init() called"); setState("over", false); setState("paused", true); setState("score", 0); setState("level", 1); }; // Game.init() that.init = init; /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var pause = function () { // Public. console.log("pause() called"); setSound('pause'); // Pause any playing sound effects. window.clearInterval(_updateID); spec.paused = true; // This is the spec parameter that is passed into the "Game constructor". }; // Game.pause() that.pause = pause; /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var update = function () { // Private. Update the position of the game objects, etc. console.log("update() called"); for (var i = 0; i < _objects.length; i++) { // Must use _objects.length in that the length of objects can dynamically change. _objects[i].move(); // Move the game object in a way that only this type of game object knows how to do. _objects[i].collisionDetection(); if (_objects[i].exited()) { _objects[i].remove(); // This must be the last thing called in the FOR loop. } // if } // for }; // Game.update() /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var run = function () { // Public. Invoked when the Start button is clicked. console.log("run() called"); setSound('play'); // Play any previously paused sound effects. var msPerFrame = 1000 / spec.fps; // (1000 ms / 1 s ) / (spec.fps frames / 1 s) = the number of milliseconds per frame. _updateID = window.setInterval(update, msPerFrame); // Using setInterval (as opposed to requestAnimationFrame) to ensure precise frames per second rate for the game. spec.paused = false; // This is the spec parameter that is passed into the "Game constructor". }; // Game.run() that.run = run; // Make run() a public method. /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var getState = function () { // Public. console.log("getState() called"); return { fps: spec.fps, // This is the spec parameter passed into the "Game constructor". score: spec.score, level: spec.level, paused: spec.paused, over: spec.over, // A Boolean indicating if the game is over or not. sound: spec.sound // A Boolean indicating if the game should have sound or not. }; // When called, returns an object that provides the current state of the game. }; // Game.getState() that.getState = getState; /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var setState = function (stateItem, value) { // Public. console.log("setState() called"); switch (stateItem) { case "fps": spec.fps = value; break; case "score": spec.score = value; _scoreBox.textContent = "Score: " + spec.score; break; case "level": spec.level = value; _levelBox.textContent = "Level: " + spec.level; break; case "paused": spec.paused = value; // A Boolean value. break; case "over": spec.over = value; // A Boolean value indicating if the game is over or not. break; case "sound": spec.sound = value; // A Boolean value indicating if the game should have sound or not. break; default: console.log("Error in switch of setState()"); } // switch }; // Game.setState() that.setState = setState; /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ var setSound = function (action) { // Public. console.log("setSound() called"); for (var i = 0; i < _objects.length; i++) { if (_objects[i].audio) { switch (action) { case "mute": _objects[i].audio.muted = true; break; case "play": _objects[i].audio.play(); break; case "pause": _objects[i].audio.pause(); break; case "load": _objects[i].audio.load(); break; default: console.log("Error in switch of setSound()"); } // switch } // if } // for } // Game.setSound() that.setSound = setSound; /*---------------------------------------------------------------------------------------------------------------------------------------------------------*/ return that; // The object returned by the Game() constructor. })({ fps: FPS, sound: true }); // Execute this anonymous constructor now, while passing in the constructor's initial state.
_objects[]
array. All game objects are stored within this array but the array does
not grow without bound in that as game objects leave the playing field,
they are removed from _objects[]
. Although not shown in Boulder Bop Skeleton, as game objects are created, they are added to the game by pushing them into _objects[]
using the array's native push
method and removed using splice
. With a number of game objects in
_objects[]
, update
is used to invoke the various game object methods (move
, collisionDetection
, exited
, and remove
), thereby executing the game:var update = function() { // Private. Update the position of the game objects, etc. console.log("update() called"); for (var i = 0; i < _objects.length; i++) { // Must use _objects.length in that the length of objects can dynamically change. _objects[i].move(); // Move the game object in a way that only this type of game object knows how to do. _objects[i].collisionDetection(); if (_objects[i].exited()) { _objects[i].remove(); // This must be the last thing called in the FOR loop. } // if } // for }; // Game.update()
move
, collisionDetection
, exited
, and remove
, even if some of them are null functions ( NOPs). This approach is nice because each game object knows what
move
, collisionDetection
, exited
, and remove
should do for itself. For example, if _objects[2]
is based on an SVG circle, _objects[2].move()
manipulates the circle's cx.baseVal.value
and cy.baseVal.value
values. Similarly, if _objects[3]
is an SVG image, its move
method manipulates the image's x.baseVal.value
and y.baseVal.value
values, and so forth.The
update
function itself is called repeatedly (based on the FPS
value) by setInterval
:var run = function() { // Public. Invoked when the Start button is clicked. console.log("run() called"); setSound('play'); // Play any previously paused sound effects. var msPerFrame = 1000 / spec.fps; // (1000 ms / 1 s ) / (spec.fps frames / 1 s) = the number of milliseconds per frame. _updateID = window.setInterval(update, msPerFrame); // Using setInterval (as opposed to requestAnimationFrame) to ensure precise frames per second rate for the game. spec.paused = false; // This is the spec parameter that is passed into the "Game constructor". }; // Game.run() that.run = run; // Make run() a public method.
setState("paused", true)
in the init
function), the run
function is executed when the Start button is clicked. We've copied the code here for another look:function handleStartButton() { console.log("handleStartButton() called"); var game = globals.game; if (game.getState().over) { game.init(); // Note that this sets spec.paused to true. } if (game.getState().paused) { game.run(); document.querySelector('#startButton text').style.textDecoration = "line-through"; } else { game.pause(); document.querySelector('#startButton text').style.textDecoration = "none"; } } // handleStartButton
GameObject
constructor acts as the parent of all game objects. That is, all game object constructors call GameObject
to inherit its various properties (active
, type
, core
, x
, y
, etc.) and methods (getPosition
, setPosition
, move
, collisionDetection
, etc.) For example, the Missile
constructor could take advantage of this as follows:var Missile = function(spec) { // Private. var that = GameObject(spec); that.core = globals.helpers.createCircle(spec); that.setPosition(that.core.cx.baseVal.value, that.core.cy.baseVal.value); // Set the initial position of the missile. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var exited = function() { // Public. . . . }; // Game.Missile.exited() that.exited = exited; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var move = function() { // Public. . . . }; // Game.Missile.move() that.move = move; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ return that; // Return the object created by the Missile constructor. }; // Game.Missile()
Missile
constructor defines its own exited
and move
methods but inherits everything else from the GameObject
constructor (like the remove
method).Globals.helpers
The helper functions are all straightforward with the possible exception ofcoordinateTransform
, which converts screen coordinates to game coordinates. That is, coordinateTransform
converts the position of where the user clicked or tapped (in screen
coordinates) to its analogous position in the game's playing field (in
game coordinates). For more info, see SVG Coordinate Transformations.
The rest of the skeletal code is relatively straightforward - you
should be able to understand it by reading the associated code comments.Now, let's walk through Boulder Bop Skeleton's flow of control:
- The markup is parsed, setting up the DOM.
- The script block is parsed, setting up
globals.helpers
,globals.game
, and running the anonymous event registering function. globals.game.init()
is invoked, which sets the game's initial state to not-over, paused, on level 1, with a score of 0:var init = function() { // Public. console.log("init() called"); setState("over", false); setState("paused", true); setState("score", 0); setState("level", 1); }; // Game.init() that.init = init;
- When the user clicks the Start button,
game.run()
is executed, which invokes thesetInterval
call that repeatedly executesupdate
- and the game begins.
No comments
Post a Comment