You are previewing HTML5 Canvas.

HTML5 Canvas

Cover of HTML5 Canvas by Steve Fulton... Published by O'Reilly Media, Inc.
  1. HTML5 Canvas
    1. SPECIAL OFFER: Upgrade this ebook with O’Reilly
    2. A Note Regarding Supplemental Files
    3. Preface
      1. Running the Examples in the Book
      2. What You Need to Know
      3. How This Book Is Organized
      4. Conventions Used in This Book
      5. Using Code Examples
      6. We’d Like to Hear from You
      7. Safari® Books Online
      8. Acknowledgments
    4. 1. Introduction to HTML5 Canvas
      1. The Basic HTML Page
      2. Basic HTML We Will Use in This Book
      3. The Document Object Model (DOM) and Canvas
      4. JavaScript and Canvas
      5. HTML5 Canvas “Hello World!”
      6. Debugging with Console.log
      7. The 2D Context and the Current State
      8. The HTML5 Canvas Object
      9. Another Example: Guess The Letter
      10. What’s Next
    5. 2. Drawing on the Canvas
      1. The Basic File Setup for This Chapter
      2. The Basic Rectangle Shape
      3. The Canvas State
      4. Using Paths to Create Lines
      5. Advanced Path Methods
      6. Compositing on the Canvas
      7. Simple Canvas Transformations
      8. Filling Objects with Colors and Gradients
      9. Filling Shapes with Patterns
      10. Creating Shadows on Canvas Shapes
      11. What’s Next
    6. 3. The HTML5 Canvas Text API
      1. Displaying Basic Text
      2. Setting the Text Font
      3. Text and the Canvas Context
      4. Text with Gradients and Patterns
      5. Width, Height, Scale, and toDataURL() Revisited
      6. Final Version of Text Arranger
      7. What’s Next
    7. 4. Images on the Canvas
      1. The Basic File Setup for This Chapter
      2. Image Basics
      3. Simple Cell-Based Sprite Animation
      4. Advanced Cell-Based Animation
      5. Applying Rotation Transformations to an Image
      6. Creating a Grid of Tiles
      7. Zooming and Panning an Image
      8. Pixel Manipulation
      9. Copying from One Canvas to Another
      10. What’s Next
    8. 5. Math, Physics, and Animation
      1. Moving in a Straight Line
      2. Bouncing Off Walls
      3. Curve and Circular Movement
      4. Simple Gravity, Elasticity, and Friction
      5. Easing
      6. What’s Next?
    9. 6. Mixing HTML5 Video and Canvas
      1. HTML5 Video Support
      2. Converting Video Formats
      3. Basic HTML5 Video Implementation
      4. Preloading Video in JavaScript
      5. Video and the Canvas
      6. Video on the Canvas Examples
      7. Animation Revisited: Moving Videos
      8. What’s Next?
    10. 7. Working with Audio
      1. The Basic <audio> Tag
      2. Audio Formats
      3. Audio Tag Properties, Functions, and Events
      4. Playing a Sound with No Audio Tag
      5. Creating a Canvas Audio Player
      6. Case Study in Audio: Space Raiders Game
      7. What’s Next
    11. 8. Canvas Game Essentials
      1. Why Games in HTML5?
      2. Our Basic Game HTML5 File
      3. Our Game’s Design
      4. Game Graphics: Drawing with Paths
      5. Animating on the Canvas
      6. Applying Transformations to Game Graphics
      7. Game Graphic Transformations
      8. Game Object Physics and Animation
      9. A Basic Game Framework
      10. Putting It All Together
      11. The player Object
      12. Geo Blaster Game Algorithms
      13. The Geo Blaster Basic Full Source
      14. Rock Object Prototype
      15. What’s Next
    12. 9. Combining Bitmaps and Sound
      1. Geo Blaster Extended
      2. Creating a Dynamic Tile Sheet at Runtime
      3. A Simple Tile-Based Game
      4. What’s Next
    13. 10. Mobilizing Games with PhoneGap
      1. Going Mobile!
      2. Creating the iOS Application with PhoneGap
      3. Beyond the Canvas
      4. What’s Next
    14. 11. Further Explorations
      1. 3D with WebGL
      2. Multiplayer Applications with ElectroServer 5
      3. Conclusion
    15. Index
    16. About the Authors
    17. Colophon
    18. SPECIAL OFFER: Upgrade this ebook with O’Reilly
O'Reilly logo

A Basic Game Framework

Now that we have gotten our feet wet (so to speak) by taking a peek at some of the graphics, transformations, and basic physics we will use in our game, let’s look at how we will structure a simple framework for all games we might want to create on HTML5 Canvas. We will begin by creating a simple state machine using constant variables. Next, we will introduce our game timer interval function to this structure, and finally, we will create a simple reusable object that will display the current frame rate our game is running in. Let’s get started.

The Game State Machine

A state machine is a programming construct that allows for our game to be in only a single application state at any one time. We will create a state machine for our game, called application state, which will include seven basic states (we will use constants to refer to these states):

  • GAME_STATE_TITLE

  • GAME_STATE_NEW_GAME

  • GAME_STATE_NEW_LEVEL

  • GAME_STATE_PLAYER_START

  • GAME_STATE_PLAY_LEVEL

  • GAME_STATE_PLAYER_DIE

  • GAME_STATE_GAME_OVER

We will create a function object for each state that will contain game logic necessary for the state to function and to change to a new state when appropriate. By doing this, we can use the same structure for each game we create by simply changing out the content of each state function (as we will refer to them).

Let’s take a look at a very basic version of this in action. We will use a function reference variable called currentGameStateFunction, as well as an integer variable called currentGameState that will hold the current application state constant value:

var currentGameState = 0;
var currentGameStateFunction = null;

We will create a function called switchAppState() that will be called only when we want to switch to a new state:

function switchGameState(newState) {
   currentGameState = newState;
   switch (currentState) {

      case GAME_STATE_TITLE:
         currentGameStateFunction = gameStateTitle;
         break;

      case GAME_STATE_PLAY_LEVEL:
         currentGameStateFunctionappStatePlayeLevel;
         break;

      case GAME_STATE_GAME_OVER:
         currentGameStateFunction = gameStateGameOver;
         break;

   }

}

We will call the runGame() function repeatedly in the setInterval() method. runGame() will call the currentGameStateFunction reference variable on each frame tick. This allows us to easily change the function called by runGame() based on changes in the application state:

setInterval(runGame, intervalTime );

function runGame(){
   currentGameStateFunction();
}

Let’s look at the complete code. We will create some shell functions for the various application state functions. Before the application starts, we will call the switchGameState() function, and pass in the constant value for the new function we want as the currentGameStateFunction:

//*** application start
   switchGameState(GAME_STATE_TITLE);

In Example 8-9, we will use the GAME_STATE_TITLE state to draw a simple title screen that will be redrawn on each frame tick.

Example 8-9. The tile screen state

<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {

   canvasApp();

}

   function canvasApp(){

   var theCanvas = document.getElementById("canvas");
   if (!theCanvas || !theCanvas.getContext) {
      return;
   }

   var context = theCanvas.getContext("2d");

   if (!context) {
      return;
   }

   //application states

   const GAME_STATE_TITLE = 0;
   const GAME_STATE_NEW_LEVEL = 1;
   const GAME_STATE_GAME_OVER = 2;

   var currentGameState = 0;
   var currentGameStateFunction = null;

   function switchGameState(newState) {
      currentGameState = newState;
      switch (currentGameState) {

         case GAME_STATE_TITLE:
             currentGameStateFunction = gameStateTitle;
             break;

         case GAME_STATE_PLAY_LEVEL:
             currentGameStateFunctionappStatePlayeLevel;
             break;

         case GAME_STATE_GAME_OVER:
             currentGameStateFunction = gameStateGameOver;
             break;

      }

   }

   function gameStateTitle() {
      ConsoleLog.log("appStateTitle");
      // draw background and text
      context.fillStyle = '#000000';
        context.fillRect(0, 0, 200, 200);
      context.fillStyle = '#ffffff';
      context.font = '20px _sans';
      context.textBaseline = 'top';
      context.fillText  ("Title Screen", 50, 90);

   }

   function gameStatePlayLevel() {
      ConsoleLog.log("appStateGamePlay");
   }

   function gameStateGameOver() {
      ConsoleLog.log("appStateGameOver");
   }

   function runGame(){
      currentGameStateFunction();
   }

   //*** application start
   switchGameState(GAME_STATE_TITLE);

   //**** application loop
   const FRAME_RATE = 40;
   var intervalTime = 1000/FRAME_RATE;
   setInterval(runGame, intervalTime );

}

//***** object prototypes *****

//*** consoleLog util object
//create constructor
function ConsoleLog(){

}

//create function that will be added to the class
console_log = function(message) {
   if(typeof(console) !== 'undefined' && console != null) {
      console.log(message);
   }
}
//add class/static function to class by assignment
ConsoleLog.log = console_log;

//*** end console log object

</script>

Note

Example 8-9 added in the ConsoleLog object from the previous chapters. We will continue to use this utility to create helpful debug messages in the JavaScript log window of the browser.

We will continue to explore the application state machine, and then create one for our game logic states in the upcoming section, Putting It All Together.

The Update/Render (Repeat) Cycle

In any of our application states, we might need to employ animation and screen updates. We will handle these updates by separating our code into distinct update() and render() operations. For example, as you might recall, the player ship can move around the game screen, and when the player presses the up arrow key, the ship’s thrust frame of animation will be displayed rather than its static frame. In the previous examples, we contained all the code that updates the properties of the ship, as well as the code that actually draws the ship, in a single function called drawScreen(). Starting with Example 8-10, we will rid ourselves of this simple drawScreen() function and instead employ update() and render() functions separately. We will also separate out the code that checks for the game-specific key presses into a checkKeys() function.

Let’s reexamine the contents of the drawScreen() function from Example 8-8, but this time break the function up into separate functions for each set of tasks, as shown in Example 8-10.

Example 8-10. Splitting the update and render cycles

function gameStatePlayLevel() {
   checkKeys();
   update();
   render();
}

function checkKeys() {

   //check keys

   if (keyPressList[38]==true){
      //thrust
      var angleInRadians = rotation * Math.PI / 180;
      facingX = Math.cos(angleInRadians);
      facingY = Math.sin(angleInRadians);

      movingX = movingX+thrustAcceleration*facingX;
      movingY = movingY+thrustAcceleration*facingY;

   }

   if (keyPressList[37]==true) {
      //rotate counterclockwise
      rotation−=rotationalVelocity;
   }

   if (keyPressList[39]==true) {
      //rotate clockwise
      rotation+=rotationalVelocity;;
   }
}

function update() {
   x = x+movingX;
   y = y+movingY;
}

function render() {
   //draw background and text
   context.fillStyle = '#000000';
   context.fillRect(0, 0, 200, 200);
   context.fillStyle = '#ffffff';
   context.font = '20px _sans';
   context.textBaseline = 'top';
   context.fillText  ("render/update", 0, 180);

   //transformation
   var angleInRadians = rotation * Math.PI / 180;
   context.save(); //save current state in stack
   context.setTransform(1,0,0,1,0,0); // reset to identity

   //translate the canvas origin to the center of the player
   context.translate(x+.5*width,y+.5*height);
   context.rotate(angleInRadians);

   //drawShip

   context.strokeStyle = '#ffffff';
   context.beginPath();

   //hardcoding in locations
   //facing right
   context.moveTo(−10,−10);
   context.lineTo(10,0);
   context.moveTo(10,1);
   context.lineTo(−10,10);
   context.lineTo(1,1);
   context.moveTo(1,−1);
   context.lineTo(−10,−10);

   context.stroke();
   context.closePath();

   //restore context
   context.restore(); //pop old state on to screen
}

const FRAME_RATE = 40;
var intervalTime = 1000/FRAME_RATE;
setInterval(appStateGamePlay, intervalTime );

We left out the entire application state machine from Example 8-9 to save space. In Example 8-10, we are simply showing what the gameStatePlayLevel() function might look like.

In the section Putting It All Together, we will go into this in greater detail as we start to build out the entire application.

The FrameRateCounter Object Prototype

Arcade games such as Asteroids and Geo Blaster Basic rely on fast processing and screen updates to ensure all game-object rendering and game-play logic are delivered to the player at a reliable rate. One way to tell whether your game is performing up to par is to employ the use of a frame rate per second (FPS) counter. Below is a simple one that can be reused in any game you create on the canvas:

//*** FrameRateCounter  object prototype
function FrameRateCounter() {

   this.lastFrameCount = 0;
   var dateTemp = new Date();
   this.frameLast = dateTemp.getTime();
   delete dateTemp;
   this.frameCtr = 0;
}

FrameRateCounter.prototype.countFrames=function() {
   var dateTemp = new Date();
   this.frameCtr++;

   if (dateTemp.getTime() >=this.frameLast+1000) {
      ConsoleLog.log("frame event");
      this.lastFrameCount = this.frameCtr;
      this.frameLast = dateTemp.getTime();
      this.frameCtr = 0;
   }

   delete dateTemp;
}

Our game will create an instance of this object and call the countFrames() function on each frame tick in our update() function. We will write out the current frame rate in our render() function.

Example 8-11 shows these functions by adding code to Example 8-10. Make sure you add the definition of the FrameRateCounter prototype object to the code in Example 8-10 under the canvasApp() function but before the final <script> tag. Alternatively, you can place it in its own <script\> tags, or in a separate .js file and set the URL as the src= value of a <script> tag. For simplicity’s sake, we will keep all our code in a single file.

Example 8-11 contains the definition for our FrameRateCounter object prototype, as well as the code changes to Example 8-10 that are necessary to implement it.

Example 8-11. The FrameRateCounter is added

function update() {
   x = x+movingX;
   y = y+movingY;
   frameRateCounter.countFrames();
}

function render() {
   // draw background and text
   context.fillStyle = '#000000';
   context.fillRect(0, 0, 200, 200);
   context.fillStyle = '#ffffff';
   context.font = '20px _sans';
   context.textBaseline = 'top';
   context.fillText  ("FPS:" + frameRateCounter.lastFrameCount, 0, 180);

   //...Leave everything else from Example 8-10 intact here
}

frameRateCounter = new FrameRateCounter();
const FRAME_RATE = 40;
var intervalTime = 1000/FRAME_RATE;
setInterval(runGame, intervalTime );

The best content for your career. Discover unlimited learning on demand for around $1/day.