Introducing graphics

Is this really a course about games? Where are the graphics?

Introduction

[Note: drawing within a canvas was studied in detail during the W3C HTML5 Part 1 course, in week 3.]

Good news! We will add graphics to our game engine in this lesson!  To-date we have talked of “basic concepts”; so without further ado, let’s draw something, animate it, and move shapes around the screen 🙂

Let’s do this by including into our framework the same “monster” we used during the HTML5 Part 1 course.

HTML5 canvas basic usage: drawing a monster

How to draw a monster in a canvas: you can try it online at JSBin.

Small monster drawn in a canvas

HTML code (declaration of the canvas):

  1. <!DOCTYPE html>
  2. <html lang=“en”>
  3. <head>
  4.    <meta charset=“utf-8”>
  5.    <title>Draw a monster in a canvas</title>
  6. </head>
  7. <body>
  8.    <canvas id=“myCanvas” width=“200” height=“200”></canvas>
  9. </body>
  10. </html>

The canvas declaration is at line 8. Use attributes to give it a width and a height, but unless you add some CSS properties, you will not see it on the screen because it’s transparent!

Let’s use CSS to reveal the canvas, for example, add a 1px black border around it:

  1. canvas {
  2.    border: 1px solid black;
  3. }

And here is a reminder of best practices when using the canvas, as described in the HTML5 Part 1 course:

    1. Use a function that is called AFTER the page is fully loaded (and the DOM is ready), set a pointer to the canvas node in the DOM.
    2. Then, get a 2D graphic context for this canvas (the context is an object we will use to draw on the canvas, to set global properties such as color, gradients, patterns and line width).
    3. Only then can you can draw something,
    4. Do not forget to use global variables for the canvas and context objects. I also recommend keeping the width and height of the canvas somewhere. These might be useful later.
    5. For each function that will change the context (color, line width, coordinate system, etc.), start by saving the context, and end by restoring it.

Here is JavaScript code which implements those best practices:

  1. // useful to have them as global variables
  2. var canvas, ctx, w, h;
  3. window.onload = function init() {
  4.   // Called AFTER the page has been loaded
  5.   canvas = document.querySelector(“#myCanvas”);
  6.   // Often useful
  7.   w = canvas.width;
  8.   h = canvas.height;
  9.   // Important, we will draw with this object
  10.   ctx = canvas.getContext(‘2d’);
  11.   // Ready to go!
  12.   // Try to change the parameter values to move
  13.   // the monster
  14.   drawMyMonster(10, 10);
  15. };
  16. function drawMyMonster(x, y) {
  17.   // Draw a big monster!
  18.   // Head
  19.   // BEST practice: save the context, use 2D transformations
  20.   ctx.save();
  21.   // Translate the coordinate system, draw relative to it
  22.   ctx.translate(x, y);
  23.   // (0, 0) is the top left corner of the monster.
  24.   ctx.strokeRect(0, 0, 100, 100);
  25.   // Eyes
  26.   ctx.fillRect(20, 20, 10, 10);
  27.   ctx.fillRect(65, 20, 10, 10);
  28.   // Nose
  29.   ctx.strokeRect(45, 40, 10, 40);
  30.   // Mouth
  31.   ctx.strokeRect(35, 84, 30, 10);
  32.   // Teeth
  33.   ctx.fillRect(38, 84, 10, 10);
  34.   ctx.fillRect(52, 84, 10, 10);
  35.   // BEST practice: restore the context
  36.   ctx.restore();
  37. }

In this small example, we used the context object to draw a monster using the default color (black) and wireframe and filled modes:

    • ctx.fillRect(x, y, width, height): draws a rectangle whose top left corner is at (x, y) and whose size is specified by the width andheight parameters; and both outlined by, and filled with, the default color.
    • ctx.strokeRect(x, y, width, height): same but in wireframe mode.
    • Note that we use (line 30) ctx.translate(x, y) to make it easier to move the monster around. So, all the drawing instructions are coded as if the monster was in (0, 0), at the top left corner of the canvas (look at line 33). We draw the body outline with a rectangle starting from (0, 0). Calling context.translate “changes the coordinate system” by moving the “old (0, 0)” to (x, y) and keeping other coordinates in the same position relative to the origin.
    • Line 19: we call the drawMonster function with (10, 10) as parameters, which will cause the original coordinate system to be translated by (10, 10).
    • And if we change the coordinate system (this is what the call to ctx.translate(…) does) in a function, it i good practice to always save the previous context  at the beginning of the function and restore it at the end of the function (lines 27 and 50).

Animating the monster and including it in our game engine

Ok, now that we know how to move the monster, let’s integrate it into our game engine:

    1. add the canvas to the HTML page,
    2. add the content of the init() function to the start() function of the game engine,
    3. add a few global variables (canvas, ctx, etc.),
    4. call the drawMonster(…) function from the mainLoop,
    5. add a random displacement to the x, y position of the monster to see it moving,
    6. in the main loop, do not forget to clear the canvas before drawing again; this is done using the ctx.clearRect(x, y, width, height)function.

You can try this version online at JSBin

Screenshot of a trembling monster in a 60 f/s animation

HTML code:

  1. <!DOCTYPE html>
  2. <html lang=“en”>
  3. <head>
  4. <meta charset=“utf-8”>
  5. <title>Trembling monster in the Game Framework</title>
  6. </head>
  7. <body>
  8. <canvas id=“myCanvas” width=“200” height=“200”></canvas>
  9. </body>
  10. </html>

JavaScript complete code:

  1. // Inits
  2. window.onload = function init() {
  3.   var game = new GF();
  4.   game.start();
  5. };
  6. // GAME FRAMEWORK STARTS HERE
  7. var GF = function(){
  8.   // Vars relative to the canvas
  9.   var canvas, ctx, w, h;
  10.   …
  11.   var measureFPS = function(newTime){
  12.     …
  13. };
  14. // Clears the canvas content
  15. function clearCanvas() {
  16.    ctx.clearRect(0, 0, w, h);
  17. }
  18. // Functions for drawing the monster and perhaps other objects
  19. function drawMyMonster(x, y) {
  20.    …
  21. }
  22. var mainLoop = function(time){
  23.     // Main function, called each frame
  24.     measureFPS(time);
  25.     // Clear the canvas
  26.     clearCanvas();
  27.     // Draw the monster
  28.     drawMyMonster(10+Math.random()*10, 10+Math.random()*10);
  29.     // Call the animation loop every 1/60th of second
  30.     requestAnimationFrame(mainLoop);
  31. };
  32. var start = function(){
  33.     …
  34.     // Canvas, context etc.
  35.     canvas = document.querySelector(“#myCanvas”);
  36.     // often useful
  37.     w = canvas.width;
  38.     h = canvas.height;
  39.     // important, we will draw with this object
  40.     ctx = canvas.getContext(‘2d’);
  41.     // Start the animation
  42.     requestAnimationFrame(mainLoop);
  43. };
  44. //our GameFramework returns a public API visible from outside its scope
  45. return {
  46.    start: start
  47. };
  48. };

Explanations:

    • Note that we now start the game engine in a window.onload callback (line 2), so only after the page has been loaded.
    • We also moved 99% of the init() method from the previous example into the start() method of the game engine, and added thecanvas, ctx, w, h variables as global variables to the game framework object.
    • Finally, in the main loop we added a call to the drawMonster() function, injecting randomicity through the parameters:  the monster is drawn with an x,y offset of between 0 and 10, in successive frames of the animation.
    • And we clear the previous canvas content before drawing the current frame (line 35).

If you try the example, you will see a trembling monster. The canvas is cleared and the monster drawn in random positions, at around 60 times per second!

In the next part of this week’s course, we’ll see how to interact with it using the mouse or the keyboard.

Leave a comment