Wednesday, December 12, 2012

Problems with Motion in Canvas and HTML5

If you have kept up with the previous posts and tried the code out for yourself you are ready to see things move on our canvas. Up to now, HTML5 has served us well and we are finding all the right tools to do the jobs we need to do. Unfortunately, that isn't the case when you want to create a simple animation and move objects across the screen.

Let's start with what might look like the obvious way to do this task and see just why it doesn't work.

Add the highlighted line below after the two that come before it.

        dogCatcher();
        myDogSpot(0,0);
        scooter(15);


We will be adding a function that will move our dogs across the screen. We will call it scooter and pass one variable to it which will be the Y axis of our graphics. It determines how far down the canvas our moving image will be placed.

Let's set up a simple FOR LOOP in the new function and increment the graphic across the screen:

        function scooter(dogY){
           for (var i = 0; i < 500; i++){
               myDogSpot(canvasLeft + i, dogY)
           }          
        }





If you try this out you should see that the page loads and the first dog suddenly appears at the other side of the game board. There is no illusion of motion here. Things happen so quickly that the image seems to just jump from one side of the screen to the other. What we would like here is a simple delay that we could call that would slow the execution of the code down long enough to give our eyes a chance to see the image moving across the screen.

Unfortunately, there is nothing in JavaScript that does that for us. There are, however, some functions that come close.

  • setInterval() - executes a function, over and over again, at specified time intervals
  • setTimeout() - executes a function, once, after waiting a specified number of milliseconds
 You can find a complete explanation of these functions on the W3Schools.com web site.

We will use the first of the two. We will turn our interval timer on by changing:
       scooter(15);
to:
       delayLoop=self.setInterval(function(){scooter(15)},20);

This calls the same routine, which we will rewrite, once every 20 milliseconds. We will get rid of the FOR LOOP because this new line above will do the job of calling the routine for us over and over.
We will need to declare a variable to hand the X coordinate used for placing the image on the screen. Here is the new function:

     var delayLoop = 0;
     var runningDogX = canvasLeft;

     function scooter(y){
         runningDogX = runningDogX + 2;
         myDogSpot(runningDogX,y);
         if (runningDogX > 640){
             clearInterval(delayLoop);
             }
         }

The new function is called repeatedly until runningDogX is greater than 640. When that happens, the interval is shut down.

You will find the full code of this example at the bottom of this post. You can copy it and paste it into your own editor. Experiment with it. Change the values and check out the result.

This routine is very similar to many examples that you will find on the web. Like those examples, this one works fine because there are no more instructions after we turn on the interval timer. As the code for our project expands, it will soon be clear that this creates many problems. JavaScript doesn't stop executing instructions while it waits for the interval timer to clock itself out. It just keeps right on with whatever you have next. When the interval times out, it stops what it's doing and executes the function we have called.

To make practical use of this tool, we have to be sure that we write our code so that JavaScript can't find anything it needs to do until after function scooter() has finished moving our image. This task is not as simple as you might think and may result in surprises on you screen that are difficult to debug. For now, give it a try.

<!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);

        dogCatcher();
        myDogSpot(0,0);
        delayLoop=self.setInterval(function(){scooter(15)},20);

    }else{

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

    }

 }

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


 function scooter(y){
     runningDogX = runningDogX + 2;
     myDogSpot(runningDogX,y);
     if (runningDogX > 640){
         clearInterval(delayLoop);
         }
     }

 function dogCatcher(){
     dogImage = ctx.getImageData(canvasLeft, 15, 70, 400);
     }
   
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