Sunday, December 16, 2012

Moving Multiple Objects on our HTML5 Canvas

Let's look at how we can move one of our dogs closer to the finish line. Our example project is relatively simple because the objects in our game never collide with each other. They move along in an orderly manner on their own 'track' until they reach a finish line. We will simply capture a strip of the canvas that contains one of the dogs. Then we will put it back but shifted over 2 pixels, so that the dog will appear to be in a new location.

The image you see above is the actual image that we are capturing. Many captures earlier, the dog was on the far left. Now it is nearly to the finish line. You do not see the finish line in our image, because we don't want it to appear to be moving. We capture this section just a few pixels short of the finish line so when it is put back on the screen, the line is not disturbed.

We should be clear about this. We are not taking anything off of the canvas, we are simply copying a section of it and storing it in memory. Then we put it into the canvas image in a new location. That covers up the original puppy so you don't see any trailing bits of dog dribbled across the screen.

You might think of this as a Copy and Paste function.

Almost any game you create will require an element of unpredictability. We need to chose a dog at random, move him a bit, then chose another and do the same. We repeat this over and over until one of the dog reaches the finish line. We do this with a random number.

Ah! But the image is always the same size and we always put it back in the same place, not far from where we got it. So how do we know when one of the dog reaches the end of the race? This is where our coding gets clever. We are going to capture another image, a small one. It will come from just in front of the finish line. We will store it in a variable testSpot and then check the very first byte in the data to see if it is not our background. REMEMBER the first byte of our background is Hex 00. So if the first byte of this test image is not zero, it has to be the color of one of our dogs. GAME OVER!

Let's start with the random numbers. We have six dogs. Like all things computer, we number them 0 through 5. Look at this code:

        var dog = Math.floor(Math.random() * 6);

We establish a variable called dog and then give it a random value from 0 to 5. This variable will then be used to mathematically determine the Y axis for all the image manipulation. In effect, it identifies which of the six dogs were are going to move at this moment. The X axis of the image location is the same for every dog, so that never changes. The line: dogCatcher(dog); calls the following function which will capture our image:

 function dogCatcher(dog){
     dogImage = ctx.getImageData(canvasLeft, dog * 60 + 15, 577, 55);
     }


We have our usual players in here. The ever present canvasLeft references the edge of our game board. dog is the random number we sent to this function. First we multiply it by 60, which is the distance between two dogs: tail to tail, nose to nose. It represents the Y axis of our graphic. We add 15 to the value because our first dog is dropped a little more than 15 pixels down from the top of the canvas. The 577 is the width of the image we want to capture and the 55 is the height.

Notice that for the first dog, we multiply 0 x 60 = 0 and then 0 + 15 = 15. The second dog will be 1 x 60 = 60 and 60 + 15 = 75. This technique allows us to capture a strip of the canvas that is 557 pixels wide and 55 pixels high that will contain one of our dogs somewhere in the middle. Just like the one you see above.

We are grabbing an image every 60 pixels up and down the screen that is only 55 pixels high. That leaves a 5 pixel gap between them that is undisturbed. We could use that to draw in lines, if we wanted, separating the tracks the dogs run in.

Once we have stored the image we can write it back to the canvas with this line:

        myDogSpot(canvasLeft + 2, dog * 60 + 15);

Of course this line doesn't put the image on the screen itself, it calls a function that we have used before that does that for us. Notice that the math here is nearly the same. dog * 60 + 15 is the Y axis and it is identical to what we captured. But this time we have increased the X axis a very small amount: canvasLeft + 2. As you experiment with these numbers, play around with the + 2 and you can see that it makes a big difference on how fast your puppies trot. But it is really the distance that you are changing.

Now let's talk about the finish line.

We can use the same random value, dog, to pick a spot of the screen to test. In our simple project we don't need to worry about any of the other dogs only the one we just moved. So we will get a bit of the image again:

    testSpot = ctx.getImageData(canvasLeft+575,dog * 60 + 31,10,10);

testSpot is a variable that we declared earlier. It is a place to park the data so we can peek into it and check for the winning pouch. You will see canvasLeft in there again. This time we adding 575 to it because the finish line is at the other edge of the canvas. We still multiply dog * 60 for the same reason we did above. This time we are adding 31 to that value. I figured that out by trial and error. It should pickup the color of the dog's snout right behind it's black nose. The size here, 10 by 10, could be anything, 4 by 4 or 100 by 100. It doesn't matter because we only care about the very first byte, which is the 'red' value of the pixel in the upper left corner of the rectangle. It will be: testSpot.data[0]. Here is how we use it:

        if (testSpot.data[0] > 0){
             clearInterval(delayLoop);
       
            }


We just keep checking that first byte and the first time it isn't zero, GAME OVER, we have a winner. So we clear the interval and everything stops.

Find the line that reads:

        ctx.putImageData(testSpot,20,dog * 60 + 30);

I put that in there just for debugging purposes. Notice that there is no canvasLeft in it. No, this time our Y axis is simply 20. This allows us to park a tiny image on the scratch space to the left of the game and actually see for our self if part of the dog is in there or not. I used it for my trial and error to find the right place on the board to catch the first dog as her nose reaches the finish line. We will want to get rid of this line when the project is completed.

You will be able to copy the complete code below. As you experiment with it on your own computer, add a break point in Firebug so you can watch the test image yourself.  Also, you might find it interesting to actually select testSpot.data[0]and watch it's value. Firebug will let you see all the bytes in the image data which should help you learn even more about how a pixel image is stored in the computer.

CU Next Time ...

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="generator" content="CoffeeCup HTML Editor (www.coffeecup.com)">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=1;">
    <title>Dog Race</title>
    <style>body{color:#FFFFFF}</style>
    <script type="text/javascript">
    var canvasLeft = 120;
   
    var myRed = "#FE0000";         //red - first value is 254
    var myBlue = "#0200DD";        //blue - first value is 2
    var myYellow = "#FCFF00";      //yellow - first value is 252
    var myMagenta = "#660066";     //magenta - first value is 253
    var myOrange = "#FF6600";      //orange is - first value is 255
    var myGreen = "#06FF00";       //green is - first value is 6
    var myBrown = "#993300";       //brown is - first value is 153
    var myBackground = "#007777";  //background -- first value is 0
   
function canvasDogRaces(){

    // Get the canvas element.
    canvas = document.getElementById("myCanvas");

    // Make sure you got it.
    if (canvas.getContext){
        ctx = canvas.getContext("2d");
       
        clearCanvas(myBackground);
       
        drawDog(myGreen, canvasLeft + 20, 10);
        drawDog(myYellow, canvasLeft + 20, 70);
        drawDog(myBrown, canvasLeft + 20, 130);
        drawDog(myRed, canvasLeft + 20, 190);
        drawDog(myBlue, canvasLeft + 20, 250);
        drawDog(myMagenta, canvasLeft + 20, 310);

        

ctx.strokeStyle = 'black'
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.moveTo(canvasLeft + 580,15);
        ctx.lineTo(canvasLeft + 580,380);
        ctx.closePath();
        ctx.stroke();
        delayLoop=self.setInterval(function(){scooter(15)},2);

    }else{

       alert("Your browser does not\n support 'Canvas'");

    }

 }

 var dogImage = 0;
 var delayLoop = 0;
 var runningDogX = canvasLeft;
 var testSpot = 0;

 function scooter(){
     var dog = Math.floor(Math.random() * 6);
     dogCatcher(dog);
     myDogSpot(canvasLeft + 2, dog * 60 + 15);
   
     testSpot = ctx.getImageData(canvasLeft+575,dog* 60 + 31,10,10);
     ctx.putImageData(testSpot,20,dog * 60 + 30);
     if (testSpot.data[0] > 0){
         clearInterval(delayLoop);
       
         }
     }
 function goAgain(){
     location.reload();
     }

 function dogCatcher(dog){
     dogImage = ctx.getImageData(canvasLeft, dog * 60 + 15, 577, 55);
     }
   
function myDogSpot(x,y){
    ctx.putImageData(dogImage,x, y);
    }

 function drawDog(thisColor,dogX,dogY){
      // draw tail
     ctx.beginPath();
     ctx.lineWidth = 3;
     ctx.strokeStyle = 'black';
     ctx.moveTo(dogX + 5,dogY+32);
     ctx.lineTo(dogX-10,dogY + 25);
     ctx.closePath();
     ctx.stroke();
   
     // draw body
     ctx.fillStyle = thisColor;
     ctx.beginPath();    
     ctx.rect(dogX+8,dogY + 30,24,16);
     ctx.rect (dogX,dogY + 35, 10, 25);
     ctx.rect (dogX + 30,dogY + 35, 10, 25);
     ctx.fill();
     ctx.arc(dogX + 8, dogY + 38, 8,  0, 2*Math.PI, true);
     ctx.arc(dogX + 32, dogY + 38, 8, 0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
   
     // draw head
     ctx.beginPath();
     ctx.arc(dogX + 36, dogY + 22, 8,  0, 2*Math.PI, true);
     ctx.closePath();
     ctx.fill();
     ctx.beginPath();
     ctx.arc(dogX + 40, dogY + 22, 8, 0, Math.PI, false);
     ctx.closePath();
     ctx.fill();
   
     // draw nose
     ctx.fillStyle = 'black';
     ctx.beginPath();
     ctx.rect(dogX +46,dogY +22,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw eye
     ctx.beginPath();
     ctx.rect(dogX +40,dogY +20,3,3);
     ctx.closePath();
     ctx.fill();
   
     // draw ear
     ctx.beginPath();
     ctx.arc(dogX + 30, dogY + 18, 4, Math.PI, 0.2 *Math.PI, false);
     ctx.closePath();
     ctx.fill();

 }
   

 function clearCanvas(thisColor){          
       ctx.fillStyle = thisColor;
       ctx.rect(canvasLeft,0,600,400);
       ctx.fill();
 }

 </script>
 </head>
 <body onload="canvasDogRaces()" bgcolor="#339999">    

    <h3>     

        <div id="rowOne_text">Welcome to the Races</div>     

        <div id="rowTwo_text">Pick Your Dog</div>   

    </h3>

 

    <canvas id="myCanvas" width="840" height="400" ;">  

    </canvas>

</body>
</html>

No comments:

Post a Comment