Tuesday, February 23, 2010

A bit of motion

A canvas element with a single immobile square on it is boring. There might be an argument made for the exciting demonstration of power and such, but that argument is best left to wiser people than I. Instead, I want to have squares that move!

Moving squares means squares that keep track of their location, and maybe even speed. Since I also want to have squares of different sizes and colors, without much else different about the squares, it would also make sense to keep track of how big they are. So, I want to create an array to keep track of all of the boxes and a constructor function to create them. In addition, I'm going to want a global variable that keeps track of time, to make the physics and such work correctly.

...
   var boxes = new Array();
   var time = 0;
   
   Box = function(x,y,vx,vy,size,color){
    this.x     = x;  // x position
    this.y     = y;  // y position
    this.vx    = vx; // x velocity
    this.vy    = vy; // y velocity
    this.size  = size;
    this.color = color;
   }
...
These boxes should move at their given velocities and bounce off the edges, so I'll need a function that works out where a given box should be:
...
   boxPhysics = function(box){
    dt = (new Date().getTime())-time; // Time since last frame
    newx = box.x+dt*box.vx;
    if(newx > 320){
     box.vx = -Math.abs(box.vx);
     newx = box.x+dt*box.vx;
    }
    if(newx < 0){
     box.vx = Math.abs(box.vx);
     newx = box.x+dt*box.vx;
    }
    var newy = box.y + dt*box.vy;
    if(newy > 480){
     box.vy = -Math.abs(box.vy);
     newy = box.y + dt*box.vy;
    }
    if(newy < 0){
     box.vy = Math.abs(box.vy);
     newy = box.y + dt*box.vy;
    }
    box.x = newx;
    box.y = newy;
   }
...
This function will keep the boxes bouncing as though there is a peg sticking out the center that runs into the sides of the canvas. I did this on purpose. If you would rather have the boxes bounce whenever an edge of the box makes contact with the frame of the canvas, modify the if statements to check for contact with a bounding box that is smaller by half the box's size. So, for the x-position, the two if statements would look like this:
...
if(newx > 320-box.size/2){...}
if(newx < 0+box.size/2){...}
...

Now that I have a way to create and re-locate the boxes, I need a way to draw them. Before, I only had to draw a box once, since the image displayed by the canvas never changed. Now, I am looking to make an animation, so I need a function I can call repeatedly, every time I want to update the frame.

...
   draw = function(){
    newContext.fillStyle='white';
    newContext.fillRect(0,0,320,480); // Clear the canvas
    for(i=0;i<boxes.length;i++){
     box = boxes[i];
     boxPhysics(box);                 // Move the box, as needed
     newContext.fillStyle=box.color;
     newContext.fillRect(box.x-box.size/2,box.y-box.size/2,
                                          box.size,box.size);
    }
    time = new Date().getTime(); // Update the global time
   }
...

Now that I have a way to store boxes, a way to create boxes, and even a way to draw boxes, it would be nice to have some way for the user to specify where these boxes should go. Specifically, I want to let the user (myself) click anywhere within the canvas to create a box. It helps to make two functions for this:

...
   makeBox = function(x,y){  // Makes box with random color + velocity
    var clr = "rgba(";
    for (i = 0; i < 3; i++) { //Loop to specify the color
      var v = Math.floor(Math.random()*256); // 0-255;
      clr += v + ",";
    }
    clr += "0.7)";
    var vx = (Math.random()-1.0)/6;
    var vy = (Math.random()-1.0)/6;
    boxes.push(new Box(x, y,vx,vy,90,clr))
   }

   clicker = function(event){ // Function to respond to mouse clicks
    var x = 0;
    var y = 0;
    if(event){
     x = event.pageX - newCanvas.offsetLeft;
     y = event.pageY - newCanvas.offsetTop;
    }
    makeBox(x,y);
   }
...
In order to make the clicker function respond to mouse clicks I still need to modify the original use of the canvas tag. I might as well also change the size of the canvas to the size of an iPhone's screen:
...
<canvas id='newCanvas' width='320' height='480'
style="border: 1px dashed; width:320px;height:480px" 
onmousedown="clicker(event);">
...
And finally, I need to modify the initialization function to set up the starting time right, and then start a timer that will repeatedly call the draw function:
...
init = function(){ // This function will start things
    time = new Date().getTime();
    setInterval("draw();",20);
   }
...

And so, the result of all that 'hard' work is below. Click inside the canvas to make joyful, bouncing squares appear. Canvas not supported in your browser.

Next time, I make the boxes explode!

No comments:

Post a Comment