Having fun with HTML5 — Canvas, part 5

In part 4, I put togeth­er the foun­da­tions for a mini shoot­ing game, with tar­gets mov­ing on the screen and you can ‘hit’ them by click­ing inside the tar­gets, etc. etc. I promised an update to make it feel more like a game, so here it is!

Adding a background

The black back­ground was more than a lit­tle dull so I had a look around for some freely avail­able images, and to apply them I had two sim­ple choic­es:

  • draw the image onto the main can­vas on each frame
  • use the image as back­ground to the can­vas

I opt­ed for the lat­ter as it seems more effi­cient, it does mean that I need to change the way I’m clear­ing the can­vas for each frame though. In part 1 of this series I men­tioned that by set­ting the width or height of the can­vas ele­ment you can erase the con­tents and reset all the prop­er­ties of its draw­ing con­text, which seems to fit the bill of what I’m try­ing to do here.

The clear func­tion is there­fore changed to:

   1: // clear the canvas page

   2: function clear() {

   3:     // reset the canvas by resizing the canvas element

   4:     canvasElement.width = canvasElement.width;

   5: }

Limit the number of Shots

To make the game more chal­leng­ing (hav­ing an unlim­it­ed num­ber of shots is just too easy), I decid­ed to add a lim­it on the num­ber of shots you can have and after you’ve used up all of them it’s effec­tive­ly game over.

To show how many shots you have left, I want to show a num­ber of bul­lets in the top left cor­ner and depend­ing on the num­ber of shots you have left the bul­lets are shown dif­fer­ent­ly:

  • Five shots or less and you will see a bul­let for each
  • More than five shots and you will see a bul­let fol­lowed by the words x N where N is the num­ber of shots left
  • If no restric­tion on the num­ber of shots (for time attack mode for instance), you see an infin­i­ty sign next to a bul­let

To facil­i­tate these require­ments, I need to be able to track the num­ber of shots left and decre­ment its val­ue every time you click inside the can­vas ele­ment. Thank­ful­ly, the struc­ture is there already, just need some small mod­i­fi­ca­tions:

   1: // first declare a new global variable

   2: /* GLOBAL VARIABLES */

   3: var WIDTH,              // width of the canvas area

   4:     HEIGHT,             // height of the canvas area

   5:     ...

   6:     bullets,            // how many shots left

   7:     ...

   8:

   9: // then add some more steps to the mousedown handler

  10: $("#canvas").mousedown(function (mouseEvent) {

  11:     // ignore if no more bullets left!

  12:     if (bullets <= 0) {

  13:         return;

  14:     }

  15:

  16:     // hit test and add message, etc.

  17:     ...

  18:

  19:     // deduct the number of shots left if a cap is defined

  20:     if (bullets != undefined) {

  21:         bullets--;

  22:     }

  23: });

Then I need to take care of the busi­ness of actu­al­ly show­ing them on the screen, luck­i­ly it wasn’t too hard to cre­ate a basic bul­let look­ing image and an infin­i­ty sym­bol. To make it eas­i­er to use them over and over in my code, I added two <img> ele­ments to the HTML (along with a third which I will explain lat­er) but they’ll be hid­den:

   1: <div class="hide">

   2:     <img id="target" src="images/target.png"/>

   3:     <img id="bullet_target" src="images/bullet_target.png"/>

   4:     <img id="bullet" src="images/bullet.png"/>

   5:     <img id="infinity" src="images/infinity.png"/>

   6: </div>

Back to the Javascript code, there needs to be an extra step in the draw func­tion to add the bul­lets to the scene. To encap­su­late that log­ic I pulled it out into its own func­tion:

   1: // redraw the target boards on the canvas

   2: function draw() {

   3:     // as before

   4:     ...

   5:

   6:     // then the bullets

   7:     drawBullets();

   8: }

   9:

  10: // draw the bullets onto the top left hand corner

  11: function drawBullets() {

  12:     // don't proceed any further if no more bullets left

  13:     if (bullets <= 0) {

  14:         return;

  15:     }

  16:

  17:     // define basica X and Y offsets

  18:     var offsetX = 10, offsetY = 10;

  19:

  20:     // draw the first bullet

  21:     context.drawImage(bulletElement, offsetX, offsetY);

  22:     offsetX += bulletElement.width;

  23:

  24:     if (bullets == undefined) {

  25:         // draw the infinity sign next to the bullet with some padding

  26:         context.drawImage(infinityElement, offsetX + 5, offsetY + 5);

  27:     } else if (bullets > 5) {

  28:         context.textBaseline = "top";

  29:         context.textAlign = "start";

  30:         context.fillStyle = "orange";

  31:         context.font = "bold 40px arial";

  32:

  33:         // draw a count next to the bullet, e.g. "x10"

  34:         context.fillText("x"+bullets, offsetX + 5, offsetY);

  35:     }

  36:     else {

  37:         // otherwise, draw the remaining bullets

  38:         for (var i = 1 ; i < bullets; i++) {

  39:             context.drawImage(bulletElement, offsetX, offsetY);

  40:             offsetX += bulletElement.width;

  41:         }

  42:     }

  43: }

And these are the results:

image image image

Looks the part, if I might say so myself :-)

Using a Custom Cursor

Though a good approx­i­mate, the crosshair cur­sor just isn’t quite up to the task here, a scope-like cur­sor is need­ed here, like the one I cre­at­ed here:

image

(if you’re inter­est­ed in how I made the cur­sor, I used Axi­alis Cur­sor­Work­shop which makes the task sim­ple)

All that’s left is to replace the crosshair cur­sor in CSS, but still keep­ing it as fall­back:

   1: #canvas

   2: {

   3:     cursor: url(../cursors/scope.cur), crosshair;

   4:     background: url(../images/canvas_background.jpg);

   5: }

A sim­i­lar change needs to be applied to the HTML:


   1: <canvas id="canvas" class="block" width="800" height="700"

   2:         onSelectStart="this.style.cursor='url(cursors/scope.cur), crosshair'; return false;"/>

After that you’ll start see­ing the new cur­sor when you run the game.

Adding a Bullet Target

Thanks to my wife Yinan for giv­ing me the idea of hav­ing bonus tar­gets which are hard­er to hit but give you extra bul­lets when they’re hit. You have already seen the HTML changes ear­li­er, and prob­a­bly noticed that I have switched to using an image rather than draw­ing the reg­u­lar tar­get boards by hand..

Much of the changes to sup­port a dif­fer­ent type of tar­get hap­pened in the Javascript code, name­ly the Tar­get class, which is now extend­ed by a new Bul­let­Tar­get class:

   1: // define the Target 'class' to represent an on-screen target

   2: var Target = new Class({

   3:     initialize: function (x, y, radius, dx, dy) {

   4:         // same as before

   5:         ...

   6:

   7:         // hit the target!

   8:         this.hit = function () {

   9:             deleteTarget(_id);

  10:         };

  11:     }

  12: });

  13:

  14: // a target which awards the player with an extra bullet when hit

  15: var BulletTarget = new Class({

  16:     Extends: Target,

  17:     initialize: function (x, y, radius, bonusBullets, dx, dy) {

  18:         var _bonusBullets = bonusBullets;

  19:

  20:         // initialize Target's properties

  21:         this.parent(x, y, radius, dx, dy);

  22:

  23:         this.getBonusBullets = function () {

  24:             return _bonusBullets;

  25:         }

  26:

  27:         // override the draw function to draw the bullet target instead

  28:         this.draw = function () {

  29:             context.drawImage(bulletTargetElement, this.getX(), this.getY(), radius*2, radius*2);

  30:         };

  31:

  32:         // override the hit function to the player some bullets back 

  33:         this.hit = function () {

  34:             bullets += _bonusBullets;

  35:             deleteTarget(this.getId());

  36:         }

  37:     }

  38: });

The log­ic of remov­ing a tar­get from the tar­gets array is now moved out of the Tar­get class and the code which gen­er­ates tar­gets dur­ing ini­tial­iza­tion has also changed to gen­er­ate a bul­let tar­get by a con­fig­urable chance:

   1: /* GLOBAL VARIABLES */

   2: var WIDTH,              // width of the canvas area

   3:     HEIGHT,             // height of the canvas area

   4:     ...

   5:     targetElement,      // the target img element

   6:     targetRadius = 50,  // the radius of the regular targets

   7:     bulletTargetElement,        // the bullet target img element

   8:     bulletTargetRadius = 30,    // the radius of the targets with bullet bonus

   9:     bulletTargetBonus = 3,      // the number of extra bullets for hitting a bullet target

  10:     bulletTargetChance = 0.1,   // the chance of getting a bullet target

  11:     ...

  12:

  13: // function to delete the target with the specified ID

  14: function deleteTarget(id) {

  15:     for (var i = 0; i < targets.length; i++) {

  16:         var target = targets[i];

  17:

  18:         if (target.getId() == id) {

  19:             targets.splice(i, 1);

  20:             break;

  21:         }

  22:     }

  23: }

  24:

  25: // Add targets to the game

  26: function createTargets() {

  27:     for (var i = 0; i < 10; i++) {

  28:         if (Math.random() < bulletTargetChance) {

  29:             targets[i] = new BulletTarget(0, 0, bulletTargetRadius, bulletTargetBonus);

  30:         } else {

  31:             targets[i] = new Target(0, 0, targetRadius);

  32:         }

  33:     }

  34: }

Demo

The full demo can be found here, hope you like it, I’ll be adding even more fea­tures in the near future so come back lat­er if you’re inter­est­ed in see­ing how far this lit­tle game can go!

Related posts:

Hav­ing fun with HTML5 — Can­vas, part 1

Hav­ing fun with HTML5 — Can­vas, part 2

Hav­ing fun with HTML5 — Can­vas, part 3

Hav­ing fun with HTML5 — Can­vas, part 4