It feels like a lit­tle while since I last played around with the <can­vas> ele­ment, so I spent some time over the week­end and put together a sim­ple paint­ing app using the can­vas and here is the end result.

Here’s some screenshots:

image image image

Spray Tool

One of the inter­est­ing things with this sim­ple paint­ing app is the spray tool, here’s what I did to achieve the effect:

   1: var _intervalId,    // used to track the current interval ID

   2:     _center;        // the current center to spray

   3:  

   4: function getRandomOffset() {

   5:     var randomAngle = Math.random() * 360;

   6:     var randomRadius = Math.random() * radius;

   7:  

   8:     return {

   9:         x: Math.cos(randomAngle) * randomRadius,

  10:         y: Math.sin(randomAngle) * randomRadius

  11:     };

  12: }

  13:  

  14: this.startDrawing = function (position) {

  15:     _center = position;

  16:  

  17:     // spray once every 200 milliseconds

  18:     _intervalId = setInterval(this.spray, 10);

  19: };

  20:  

  21: this.finishDrawing = function (position) {

  22:     clearInterval(_intervalId);

  23: };

  24:  

  25: this.spray = function () {

  26:     var centerX = _center.X, centerY = _center.Y, i;

  27:  

  28:     for (i = 0; i < density; i++) {

  29:         var offset = getRandomOffset();

  30:         var x = centerX + offset.x, y = centerY + offset.y;

  31:  

  32:         drawingCxt.fillRect(x, y, 1, 1);

  33:     }

  34: };

The start­Draw­ing func­tion is called when the mouse­Down or a sub­se­quent mouse­Move event is raised, it in turn sets up an inter­val event which is trig­gered every 10 mil­lisec­onds and draws one pixel using the cur­rently con­fig­ured colour within the radius of the mouse pointer.

When the mouseUp event is raised, the inter­val event han­dler is unbound.

Sav­ing the Can­vas to an Image

One of the cool things you can do with the <can­vas> ele­ment is the abil­ity to get a data:URL con­tain­ing a rep­re­sen­ta­tion of the image as a PNG file, which you can then use as the source for an <img> ele­ment so that the user can then save.

The code required to do this is as sim­ple as call­ing toDataURL() on the <can­vas> ele­ment and then use the returned value to set the src prop­erty on the <img> element.

Demo

You can try out the live demo at http://sketchy.theburningmonk.com or via the short­link http://lnk.by/fghis.

If you’re inter­ested in the source code, you can get it off github here.

 

Enjoy!

Share

Another pro­duc­tive week­end bares fruit for another mini game! This time I’ve put together a dojo-themed mini game whose sole pur­pose is to test your abil­ity to do sim­ple (well, mostly) arith­metic cal­cu­la­tions in your head and your tol­er­ance for my sense of humour (which is not so bad I hope ;-) ).

Screen­shots

Here’s some screen­shots taken from the game:

image

image

image

image

image

image

image

As you can see, the main game loop is sim­ple: see some dia­logue from your oppo­nent, he asks a ques­tion, you answer, if you answer cor­rectly within time then your oppo­nent loses health point, when the opponent’s HP is depleted then you’ve defeated him and move onto the next opponent.

So far there’re 7 oppo­nents in the game, each with his own set of dia­logues and ques­tions become pro­gres­sively harder, your score is tracked and you can get a big bonus in score if you answer the ques­tions quickly (and cor­rectly of course!) and man­age to string together a suc­ces­sion of cor­rect answers! As the oppo­nents become tougher, their ques­tions will yield greater scores too, and not to men­tion they’ll be harder to beat as they have a higher HP value which means you have more chance to bet­ter your all-time top score!

Game Flow

With­out going through all the steps of mak­ing the game (maybe I’ll do that at a later date), the basic flow of the game goes some­thing like this:

image

The tricky thing is then how do you con­trol this flow eas­ily so that depend­ing on the state you’re in the right action is trig­gered when the play presses the ENTER key.

In the end I set­tled for a mech­a­nism where the DOM’s onkey­down event is han­dled by this bit of code:

   1: // hook up the onkeydown event to execute the 'next' action whatever it might be

   2: document.onkeydown = function(event) {

   3:     if (event.keyCode == 13) {

   4:         nextAction();

   5:     }

   6: };

The nex­tAc­tion vari­able is updated at each step of the game loop so that when the player presses the ENTER key the game does the right thing. The only devi­a­tion from this pat­tern is when I try to get the answer from a player, because there’s a time restric­tion on answer­ing the ques­tions I needed to set up a timer so that when it times out it’ll trig­ger the state tran­si­tion as well.

Play

You can play the game at:

http://mathdojo.theburningmonk.com

or via the short­ened link:

http://LNK.by/fgzcu

Enjoy! Do let me know if you have any sug­ges­tions, dif­fi­culty level, etc. etc.

Share

I saw this tuto­r­ial the other day, it’s cool but I fan­cied tak­ing it a step fur­ther and make it use­ful as an app rather than just a fancy way to show some struc­tured data on the screen. Here’s a list of the fea­tures which I wanted to add:

  • being able to edit the title and con­tent of the notes
  • being able to save all your notes
  • being able to add new notes
  • being able to delete notes you no longer want
  • ran­dom­ize the colour of the notes more

HTML and CSS changes

I ini­tially con­sid­ered using the new con­tente­d­itable attribute in HTML5 to make the title and con­tent editable, but set­tled on using textarea instead as it offers bet­ter sup­port for mul­ti­ple line text and you can eas­ily add place­holder text as well as set a limit on the length of the title.

I added two images as but­tons to add a new note and save the state of all the notes and put a a bor­der around the notes to make them stand out a lit­tle more.

Here’s the updated HTML:

   1: <body>

   2:     <div id="controls">

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

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

   5:     </div>

   6:

   7:     <ul id="notes">

   8:     </ul>

   9: </body>

And the CSS to go along with it:

   1: * {

   2:     maring: 0;

   3:     padding: 0;

   4: }

   5:

   6: .hide {

   7:     display: none;

   8: }

   9:

  10: body {

  11:     font-size: 100%;

  12:     margin: 15px;

  13:     background: #666;

  14:     color: #fff;

  15: }

  16:

  17: #controls img {

  18:     width: 28px;

  19:     height: 30px;

  20:     background: rgba(0, 0, 0, 0);

  21: }

  22:

  23: #controls img:hover, #controls img:focus {

  24:     -webkit-transform: scale(1.2);

  25:       -moz-transform: scale(1.2);

  26:       -o-transform: scale(1.2);

  27:       position:relative;

  28: }

  29:

  30: ul, li {

  31:     list-style: none;

  32: }

  33:

  34: ul {

  35:     overflow: hidden;

  36:     padding: 15px;

  37: }

  38:

  39: ul li {

  40:     margin: 15px;

  41:     float: left;

  42:     position: relative;

  43: }

  44:

  45: ul li div {

  46:     text-decoration: none;

  47:     color: #000;

  48:     background: #ffc;

  49:     display: block;

  50:     height: 150px;

  51:     width: 150px;

  52:     padding: 10px;

  53:     -moz-box-shadow: 5px 5px 7px rgba(33, 33, 33, 1);

  54:     -webkit-box-shadow: 5px 5px 7px  rgba(33, 33, 33, .7);

  55:     box-shadow: 5px 5px 7px rgba(33, 33, 33, .7);

  56:     -moz-transition: -moz-transform .15s linear;

  57:     -o-transition: -o-transform .15s linear;

  58:     -webkit-transition: -webkit-transform .15s linear;

  59:     border-style: solid;

  60: }

  61:

  62: ul li div img {

  63:     padding: 1px 3px;

  64:     margin: 10px;

  65:     position: absolute;

  66:     top: 0;

  67:     right: 0;

  68: }

  69:

  70: ul li textarea {

  71:     font-family: 'Chewy', arial, sans-serif;

  72:     background: rgba(0, 0, 0, 0); /* transparent background */

  73:     resize: none;

  74:     padding: 3px;

  75:     border-style: none;

  76: }

  77:

  78: .note-title {

  79:     font-size: 140%;

  80:     font-weight: bold;

  81:     height: 30px;

  82:     width: 70%;

  83: }

  84:

  85: .note-content {

  86:     font-size:120%;

  87:     height: 100px;

  88:     width: 95%;

  89: }

  90:

  91: ul li:nth-child(even) div {

  92:   -o-transform:rotate(4deg);

  93:   -webkit-transform:rotate(4deg);

  94:   -moz-transform:rotate(4deg);

  95:   position:relative;

  96:   top:5px;

  97: }

  98:

  99: ul li:nth-child(3n) div {

 100:   -o-transform:rotate(-3deg);

 101:   -webkit-transform:rotate(-3deg);

 102:   -moz-transform:rotate(-3deg);

 103:   position:relative;

 104:   top:-5px;

 105: }

 106:

 107: ul li:nth-child(5n) div {

 108:   -o-transform:rotate(5deg);

 109:   -webkit-transform:rotate(5deg);

 110:   -moz-transform:rotate(5deg);

 111:   position:relative;

 112:   top:-10px;

 113: }

 114:

 115: ul li div:hover, ul li div:focus {

 116:   -moz-box-shadow:10px 10px 7px rgba(0,0,0,.7);

 117:   -webkit-box-shadow: 10px 10px 7px rgba(0,0,0,.7);

 118:   box-shadow:10px 10px 7px rgba(0,0,0,.7);

 119:   -webkit-transform: scale(1.25);

 120:   -moz-transform: scale(1.25);

 121:   -o-transform: scale(1.25);

 122:   position:relative;

 123:   z-index:5;

 124: }

 125:

 126: /* define 3 different colours for the notes */

 127: ul li div.colour1 {

 128:     background: #ffc;

 129: }

 130: ul li div.colour2 {

 131:     background: #cfc;

 132: }

 133: ul li div.colour3 {

 134:     background: #ccf;

 135: }

There’s a cou­ple of things to note here:

  • the textarea ele­ment has a trans­par­ent background
  • the textarea ele­ment is not resizable
  • the textarea ele­ment has a place­holder text

Here’s what the page looks like now:

image

Adding inter­ac­tions with Javascript

Adding a new note

In the CSS above, you can see that I had defined three CSS class:

   1: /* define 3 different colours for the notes */

   2: ul li div.colour1 {

   3:     background: #ffc;

   4: }

   5: ul li div.colour2 {

   6:     background: #cfc;

   7: }

   8: ul li div.colour3 {

   9:     background: #ccf;

  10: }

when we cre­ate a new note, all we need to do to ran­dom­ize its colour is to add one of these classes to the ele­ment randomly.

Here’s the event han­dler for the ‘new’ image button:

   1: $(document).ready(function() {

   2:     notes = $("#notes"); // get references to the 'notes' list

   3:     ...

   4:     // clicking the 'New Note' button adds a new note to the list

   5:     $("#btnNew").click(function() {

   6:         addNewNote();

   7:     });

   8:     ...

   9: });

  10:

  11: //  adds a new note to the 'notes' list

  12: function addNewNote(class, title, content) {

  13:     // if class is not specified, use a random colour class

  14:     if (!class) {

  15:         class = "colour" + Math.ceil(Math.random() * 3);

  16:     }

  17:

  18:     // add a new note to the end of the list

  19:     notes.append("<li><div class='" + class + "'>" +

  20:                  "<textarea class='note-title' placeholder='Untitled' maxlength='10'/>" +

  21:                  "<textarea class='note-content' placeholder='Your content here'/>" +

  22:                  "<img class='hide' src='images/close.png'/>" +

  23:                  "</div></li>");

  24:

  25:     // get the new note that's just been added and attach the click event handler to its close button

  26:     var newNote = notes.find("li:last");

  27:     newNote.find("img").click(function() {

  28:         newNote.remove();

  29:     });

  30:

  31:     // hook up event handlers to show/hide close button as appropriate

  32:     addNoteEvent(newNote);

  33:

  34:     // if a title is provided then set the title of the new note

  35:     if (title) {

  36:         // get the title textarea element and set its value

  37:         newNote.find("textarea.note-title").val(title);

  38:     }

  39:

  40:     // if a content is provided then set the content of the new note

  41:     if (content) {

  42:         // get the content textarea element and set its value

  43:         newNote.find("textarea.note-content").val(content);

  44:     }

  45: }

  46:

  47: function addNoteEvent(noteElement) {

  48:     noteElement.focus(function () {

  49:         $(this).find(".img").removeClass("hide");

  50:     }).hover(function() {

  51:         $(this).find("img").removeClass("hide");

  52:     }, function () {

  53:         $(this).find("img").addClass("hide");

  54:     });

  55: }

as you can see, this event han­dler calls the addNewNote func­tion which is respon­si­ble for adding a new note to the notes unordered list and sets its class, title or con­tent depend­ing on the argu­ments. In this par­tic­u­lar usage it’ll pick one of the three colour classes ran­domly and add the class to the new note.

Once a note has been added, I then get a ref­er­ence to it and cre­ate an event han­dler for its close but­ton so that when its clicked it’ll remove this note from the DOM.

The addNo­teEvent func­tion is then charged with adding the focus and hover in/out event han­dlers to the note to show and hide the close but­ton so that it’s only shown when the note is focused/hovered on.

Sav­ing the notes

I have pre­vi­ously cover the use of HTML5’s local and ses­sion stor­age in this post already, what I’m doing here is very sim­i­lar but a lit­tle more involved.

When the ‘save’ but­ton is clicked, I need to inspect each of the notes and remem­ber the class, title text (remem­ber, you can’t get the text that’s been entered into the textarea by get­ting the HTML of the ele­ment) and con­tent text in a bespoke object, the result­ing array of objects is then JSON seri­al­ized using Dou­glas Crock­ford’s Json.js library. It this is JSON string that’s saved into the local storage.

On the other end, when the page has been loaded and the DOM event fired (i.e. when JQuery.ready fires) I will need to check if the JSON string is in the local stor­age, if it is, then dese­ri­al­ize the string back to an object array and add the notes back in in the same order:

   1: var notes;

   2: var count = 0;

   3:

   4: $(document).ready(function() {

   5:     notes = $("#notes"); // get references to the 'notes' list

   6:

   7:     // load notes from local storage if one's available

   8:     var storedNotes = localStorage.getItem("notes");

   9:     if (storedNotes)

  10:     {

  11:         // passes the stored json back into an array of note objects

  12:         var notesArray = JSON.parse(storedNotes);

  13:         count = notesArray.length;

  14:

  15:         for (var i = 0; i < count; i++) {

  16:             var storedNote = notesArray[i];

  17:             addNewNote(storedNote.Class, storedNote.Title, storedNote.Content);

  18:         }

  19:     }

  20:

  21:     ...

  22:

  23:     // clicking the 'Save' button saves the state of the notes board to the local storage                

  24:     $("#btnSave").click(function() {

  25:         var notesArray = new Array();

  26:

  27:         // for each of the notes add a bespoke note object to the array

  28:         notes.find("li > div").each(function (i, e) {

  29:             // save the class attribute of the div, as well as the text for the title and content text areas

  30:             var colourClass = $(e).attr("class");

  31:             var title = $(e).find("textarea.note-title");

  32:             var content = $(e).find("textarea.note-content");

  33:

  34:             notesArray.push({ Index: i, Title: title.val(), Content: content.val(), Class: colourClass });

  35:         });

  36:

  37:         // json encode it

  38:         var jsonStr = JSON.stringify(notesArray);

  39:

  40:         // and save the json string into local storage

  41:         localStorage.setItem("notes", jsonStr);

  42:

  43:         // info the user it's done

  44:         alert("Notes saved");

  45:     });

  46:

  47:     // add a note to the list if there aren't any

  48:     if (count == 0)

  49:     {

  50:         $("#btnNew").click();

  51:     }

  52: });

Demo

You can try out the full demo at:

http://stickynote.theburningmonk.com

or using the short link:

http://LNK.by/fhbba

Enjoy!

UPDATE 27/02/2011:

Thanks to Joey Beechey for point­ing out to me in the com­ment that the demo doesn’t work in Safari and after a bit of dig­ging I found that the prob­lem is with the addNewNote func­tion which has a para­me­ter named ‘class’ which is a reserved word. I found another ref­er­ence of this prob­lem here too, which men­tions the same prob­lem but with the vari­able name ‘abstract’ instead!

I’ve also put the whole thing into a zip file which you can down­load from here, so feel free to play around with it yourself!

Ref­er­ences:

Dou­glas Crockford’s JSON-js project on github

John Resig’s blog post which includes some handy exam­ples on how to use the JSON-js library

Orig­i­nal tuto­r­ial which inspired this blog post

Share

In part 4, I put together 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 choices:

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

I opted 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 num­ber of Shots

To make the game more chal­leng­ing (hav­ing an unlim­ited num­ber of shots is just too easy), I decided to add a limit on the num­ber of shots you can have and after you’ve used up all of them it’s effec­tively 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 differently:

  • 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­ity sign next to a bullet

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

   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­ally show­ing them on the screen, luck­ily it wasn’t too hard to cre­ate a basic bul­let look­ing image and an infin­ity sym­bol. To make it eas­ier 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 later) but they’ll be hidden:

   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 logic I pulled it out into its own function:

   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 Cus­tom 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 needed here, like the one I cre­ated here:

image

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

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

   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 Bul­let Target

Thanks to my wife Yinan for giv­ing me the idea of hav­ing bonus tar­gets which are harder to hit but give you extra bul­lets when they’re hit. You have already seen the HTML changes ear­lier, 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, namely the Tar­get class, which is now extended 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 logic 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 later if you’re inter­ested 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

Share

Fol­low­ing on from part 3 where we basi­cally made a lit­tle app that lets you scrib­ble with HTML5’s can­vas ele­ment, let us push on and see what else we can achieve with the can­vas element.

The log­i­cal next step would be to made ani­ma­tions, and give you a way to inter­act with the ani­ma­tion. So how about a lit­tle game that lets you shoot mov­ing tar­gets like those clas­sic Point Blank games? Hands up who’s up for that?

image

Good good, that’s decided then! :-)

To Start Off..

First, we’ll need a sim­ple HTML page, some­thing like my pre­vi­ous demos would do:

   1: <div id="wrapper">

   2:     <h1>HTML5 Canvas Demo</h1>

   3:     <h3>Shoot 'em!</h3>

   4:     <aside id="message"></aside>

   5:

   6:     <div class="hide">

   7:         <canvas id="target" width="101" height="101" class="hide"/>

   8:     </div>

   9:

  10:     <div class="block">

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

  12:                 onSelectStart="this.style.cursor='crosshair'; return false;"/>

  13:     </div>

  14: </div>

Noth­ing fancy here, the CSS for this page is also pretty basic too, the only thing worth not­ing is that to get the crosshair cur­sor inside the can­vas I included this in my CSS:

   1: #canvas

   2: {

   3:     cursor: crosshair;

   4: }

The onS­e­lect­Start line in the can­vas HTML is to ensure that the crosshair cur­sor is used when you click and hold inside the can­vas element.

You might have also noticed that I had spec­i­fied two can­vas ele­ments in the HTML, this is to sim­plify the task of draw­ing mov­ing tar­gets onto the main can­vas. With the 2D con­text object’s draw­Im­age func­tion, you can draw an img, can­vas, or video ele­ment so I can draw the tar­get onto the tar­get can­vas dur­ing ini­tial­iza­tion and then reuse it in the main canvas.

Draw­ing the Tar­get board

Next, I need to draw the tar­get boards, and make them look like this, but with­out the score on the rings:

image

(Why not just use an image? Because where’s the fun in that! ;-) )

As you can imag­ine, we can cre­ate a tar­get board look­ing like this by draw­ing 6 cir­cles, with the inner­most 2 cir­cles hav­ing roughly half the radius of the other 4 and the out­er­most 2 cir­cles hav­ing the inversed fill/stroke colour com­bi­na­tion. Expand the below sec­tion to see the ini­tial­ize­Tar­get­Can­vas func­tion which takes a can­vas ele­ment and draws a tar­get board in inside it:

   1: // Draws the target canvas

   2: function initializeTargetCanvas(element) {

   3:     var baseX = 1.5, baseY = 1.5;

   4:

   5:     // get the width and height of the element

   6:     var width = (element.width - baseX * 2) / 2,

   7:         height = (element.height - baseY * 2) / 2;

   8:

   9:     // work out the necessary metrics to draw the target

  10:     var radius = Math.min(width, height),

  11:         centreX = baseX + radius,

  12:         centreY = baseY + radius,

  13:         ringWidth = radius / 10;

  14:

  15:     // get the 2D context to start drawing the target!

  16:     var context = element.getContext("2d");

  17:     context.lineWidth = "2";

  18:

  19:     // define function to draw a ring

  20:     var drawRing = function (strokeStyle, fillStyle, ringRadius) {

  21:         context.strokeStyle = strokeStyle;

  22:         context.fillStyle = fillStyle;

  23:

  24:         // draw the circle

  25:         context.beginPath();

  26:         context.arc(centreX, centreY, ringRadius, 0, Math.PI * 2, true);

  27:         context.closePath();

  28:

  29:         context.stroke();

  30:         context.fill();

  31:     };

  32:

  33:     // draw the rings for each score

  34:     drawRing("#000", "#FFF", radius);

  35:     drawRing("#000", "#FFF", radius -= (ringWidth * 2));

  36:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  37:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  38:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  39:     drawRing("#FFF", "#000", radius -= ringWidth);

  40: }

Ani­mat­ing the Tar­get Boards

Being able to draw an already cre­ated tar­get board onto the can­vas page is one thing, but how do I ani­mate them using the can­vas? Hav­ing looked at a few other exam­ples it seems the com­mon way to do ani­ma­tion with the can­vas is to sim­ply clear the can­vas and redraw the con­tents on a reg­u­lar inter­val. This seems a lit­tle low level and requires a bit of plumb­ing but I’m sure it won’t be long (if not already) before some good, solid frame­works emerge to make these tasks easier.

For now though, let me illus­trate how you might cre­ate this frame by frame ani­ma­tion yourself.

If you sep­a­rate the respon­si­bil­i­ties of the app, broadly speak­ing you end up with:

  • a View – i.e. the can­vas ele­ment, respon­si­ble for dis­play­ing the targets
  • a Model – the objects rep­re­sent­ing the tar­gets in the scene, respon­si­ble for keep­ing track of their cur­rent posi­tion, etc.

If you’ve dealt with any sort of MVC/MVP/MVVM pat­tern then this kind of sep­a­ra­tion of duty should be famil­iar to you already. On each frame (or if you pre­fer, every time the set­Inter­val del­e­gate func­tion gets invoked), you update the posi­tion of the tar­gets in the model, then redraw the tar­gets onto the can­vas to reflect their updated positions:

image

This is all you need to do to cre­ate a basic ani­ma­tion. So in my case, I need a sim­ple Tar­get object to keep track of:

  • X, Y coor­di­nates of the top left hand cor­ner of the target
  • the radius of the target
  • the direc­tion and speed (per frame) it’s mov­ing at

using MooTools here’s the class I arrived at this Tar­get ‘class’:

   1: /* GLOBAL VARIABLES */

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

   3:     HEIGHT,             // height of the canvas area

   4:

   5:     targets = new Array(),  // the live targets

   6:     targetId = 0,        // the current target ID

   7:

   8:

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

  10: var Target = new Class({

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

  12:         var _id, _x, _y, _radius, _dx, _dy, is;

  13:

  14:         _id = targetId++;

  15:

  16:         // the X and Y coordinate of the top left corner

  17:         _x = x;

  18:         _y = y;

  19:

  20:         // the radius of the target

  21:         _radius = radius;

  22:

  23:         // the rate of movement in the X and Y direction

  24:         if (dx) {

  25:             _dx = dx;

  26:         } else {

  27:             _dx = Math.ceil(Math.random() * 10);

  28:         }

  29:         if (dy) {

  30:             _dy = dy;

  31:         } else {

  32:             _dy = Math.ceil(Math.random() * 10);

  33:         }

  34:

  35:         // getters

  36:         this.getId = function () {

  37:             return _id;

  38:         }

  39:

  40:         this.getX = function () {

  41:             return _x;

  42:         };

  43:

  44:         this.getY = function () {

  45:             return _y;

  46:         };

  47:

  48:         this.getRadius = function () {

  49:             return _radius;

  50:         };

  51:

  52:         // move the target to its position for the next frame

  53:         this.move = function () {

  54:             _x += _dx;

  55:             _y += _dy;

  56:

  57:             // change direction in X if it 'hits' the border

  58:             if ((_x + _radius * 2) >= WIDTH || _x <= 0) {

  59:                 _dx *= -1;

  60:             }

  61:

  62:             // change direction in Y if it 'hits' the border

  63:             if ((_y + _radius * 2) >= HEIGHT || _y <= 0) {

  64:                 _dy *= -1;

  65:             }

  66:         };

  67:

  68:         // draws the target on the canvas

  69:         this.draw = function () {

  70:             context.drawImage(targetElement, _x, _y);

  71:         };

  72:

  73:         // hit the target!

  74:         this.hit = function () {

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

  76:                 var target = targets[i];

  77:

  78:                 if (target.getId() == _id) {

  79:                     targets.splice(i, 1);

  80:                     break;

  81:                 }

  82:             }

  83:         };

  84:     }

  85: });

The draw func­tion, which is invoked at reg­u­lar inter­vals, first clears the can­vas (by fill­ing it with the back­ground colour) then goes through all the tar­gets in the tar­gets array and updates their loca­tion and then draw them onto the canvas:

   1: // clear the canvas page

   2: function clear() {

   3:     context.fillStyle = "#000";

   4:     context.fillRect(0, 0, WIDTH, HEIGHT);

   5: }

   6:

   7: // redraw the target boards on the canvas

   8: function draw() {

   9:     // clear the canvas page first

  10:     clear();

  11:

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

  13:         targets[i].move();

  14:         targets[i].draw();

  15:     }

  16: }

This will give you the basic ani­ma­tion loop, here’s how it looks with 10 mov­ing tar­gets on screen at the same time:

image

Adding Inter­ac­tions

Now that the ani­ma­tions are in place, let’s add some player inter­ac­tions. The inter­ac­tions I’m after here is sim­ple, click in the can­vas and knock out any (can be one or more) tar­get that’s clicked on.

I’ve already gone over the process of cal­cu­lat­ing the coor­di­nates of the click in respect to the can­vas in a pre­vi­ous blog post here, this is what that code looks like:

   1: // works out the X, Y position of the click INSIDE the canvas from the X, Y 

   2: // position on the page

   3: function getPosition(mouseEvent, element) {

   4:     var x, y;

   5:     if (mouseEvent.pageX != undefined && mouseEvent.pageY != undefined) {

   6:         x = mouseEvent.pageX;

   7:         y = mouseEvent.pageY;

   8:     } else {

   9:         x = mouseEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;

  10:         y = mouseEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;

  11:     }

  12:

  13:     return { X: x - element.offsetLeft, Y: y - element.offsetTop };

  14: }

If I can work out where you’ve clicked in the can­vas’ coor­di­nate sys­tem, then I can sim­ply run a hit test against each mov­ing tar­get and com­pare the dis­tance between the click and the cen­tre of the tar­get and the target’s radius:

image

If the dis­tance is smaller or equal to the radius then the click hap­pened INSIDE the tar­get and there­fore it’s a hit, oth­er­wise it’s a miss. Sounds rea­son­able enough? Here’s the code that takes a posi­tion (a sim­ple object with X and Y posi­tion of the click inside the can­vas’ coor­di­nate sys­tem) and return the tar­gets it has hit:

   1: // check if the player managed to hit any of the live targets

   2: function hitTest(position) {

   3:     var hitTargets = new Array();

   4:

   5:     // check if the position is within the bounds of any of the live targets

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

   7:         var target = targets[i];

   8:

   9:         var targetCentreX = target.getX() + target.getRadius(),

  10:             targetCentreY = target.getY() + target.getRadius();

  11:

  12:         // work out the distance between the position and the target's centre

  13:         var xdiff = position.X - targetCentreX,

  14:             ydiff = position.Y - targetCentreY,

  15:             dist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(ydiff, 2));

  16:

  17:         // if that distance is less than the radius of the target then the

  18:         // position is inside the target

  19:         if (dist <= target.getRadius()) {

  20:             hitTargets.push(target);

  21:         }

  22:     }

  23:

  24:     return hitTargets;

  25: }

To hook this up, I added an event han­dler to the mouse­down event on the can­vas dur­ing initialization:

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

   2:     // get the coordinates of the click inside the canvas

   3:     var position = getPosition(mouseEvent, this);

   4:

   5:     // find out which targets were hit

   6:     var hitTargets = hitTest(position);

   7:

   8:     // hit the targets

   9:     for (var i = 0; i < hitTargets.length; i++) {

  10:         hitTargets[i].hit();

  11:     }

  12: }

And now, when you ‘hit’ a tar­get it’ll be removed from the array of mov­ing tar­gets and there­fore won’t be drawn again when the can­vas is refreshed in the next frame.

Adding Noti­fi­ca­tions

Finally, for this first-pass imple­men­ta­tion of a mini-shooting game, I’d like to add some noti­fi­ca­tions to tell you when you’ve hit some­thing, or when you’ve missed completely!

This is slightly trick­ier than the tar­gets as the mes­sages should not stay around for­ever until some user-triggered action, instead it should be shown for a given amount of time (or frames). To facil­i­tate this require­ment, I cre­ated another ‘class’ called Mes­sage:

   1: // define the Message 'class' to represent an on-screen message

   2: var Message = new Class({

   3:     initialize: function (x, y, message, duration) {

   4:         var _id, _x, _y, _message, _duration;

   5:

   6:         _id = messageId++;

   7:

   8:         // X, Y coordinates of where to display the message

   9:         _x = x;

  10:         _y = y;

  11:

  12:         // the message

  13:         _message = message;

  14:

  15:         // how many frames to display the message for

  16:         _duration = duration;

  17:

  18:         this.getId = function () {

  19:             return _id;

  20:         }

  21:

  22:         this.draw = function () {

  23:             if (_duration >= 0) {

  24:                 context.textBaseline = "middle";

  25:                 context.textAlign = "center";

  26:                 context.fillStyle = "#FFF";

  27:                 context.strokeStyle = "#000";

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

  29:

  30:                 // draw the message at the specified X, Y coordinates

  31:                 context.fillText(_message, _x, _y);

  32:

  33:                 _duration--;

  34:             } else {

  35:                 // remove the message

  36:                 for (var i = 0; i < messages.length; i++) {

  37:                     var message = messages[i];

  38:

  39:                     if (message.getId() == _id) {

  40:                         messages.splice(i, 1);

  41:                         break;

  42:                     }

  43:                 }

  44:             }

  45:         }

  46:     }

  47: });

The Mes­sage objects can only been drawn a num­ber of times, after which it will remove itself from the array of mes­sages cur­rently being displayed.

To make it a bit more inter­est­ing, I defined a num­ber of mes­sages which will be dis­played depend­ing on how many tar­gets you’ve man­aged to hit at once:

   1: // define the messages to show

   2: var hitMessages = new Array();

   3: hitMessages[0] = "MISS";

   4: hitMessages[1] = "HIT!";

   5: hitMessages[2] = "DOUBLE HIT!!";

   6: hitMessages[3] = "HAT-TRICK!!!";

   7: hitMessages[4] = "UN~BELIEVABLE!!!!";

   8: hitMessages[5] = "OH MY GOSH!!";

On the mouse­down event han­dler (see above) I added these cou­ple of lines to push a new mes­sage to the mes­sages stack to be dis­played for 30 frames, and the mes­sage is deter­mined by how many tar­gets was hit:

   1: // use one of the defined messages if possible, otherwise use a default

   2: var hitMessage = hitMessages[hitTargets.length];

   3: if (hitMessage == undefined)

   4: {

   5:     hitMessage = "TOO GOOOOOOOOD..";

   6: }

   7:

   8: messages.push(new Message(position.X, position.Y, hitMessage, 30));

For exam­ple, if you man­aged to hit three tar­gets one shot:

image

Pretty neat, eh? :-)

Demo

Here is the full demo, over the next cou­ple of days, I will pol­ish it up and add some more fea­tures to make it feel more gamey and post another update. In the mean time though, feel free to play around with it and let me know of any sug­ges­tions you have on how to improve it!

Ref­er­ences:

HTML5 can­vas cheat sheet

Dive into HTML5 – peeks, pokes and pointers

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 5

Share