|

|
Playing Around With Canvas - Funky Qix Animation
Submitted by Rolf - Wed, 5 Oct 2011
Arguably the best feature of HTML5 is canvas. Canvas allows you to draw with Javascript in a reserved square in the webpage. And if you can draw, you can probably do animation or at least fake it. And that is exactly what I am doing here.
First let me show you the end result. Based on the old game Qix I had on the Atari, it might also remind you of a Windows 3.1 screensaver. It's a flying entity, for lack of a better word, with a fading tail. To see it, you need a relatively new browser. Most versions of Firefox and Chrome handle it nicely, but you need at least version 9 if you are using Internet Explorer.
It being the first few steps in canvas animation, I wrote it very quickly and sloppy. The algorithms are far from complex or even efficient, but you will understand what I'm doing and you will get an idea of how many of the features of canvas work.
First (or last, whatever, but it is crucial to do this obviously) we need to reserve a space in the webpage for the canvas. Conveniently enough, the HTML-tag to do that is called <canvas>.
<canvas id="canvas" width="560" height="560"></canvas> On to the script. We will build the 'qix' from the head onward. The rest, the tail can be seen as later phases of the head: every segment of the tail will be drawn where the head has been before.
The simplest way I could think of, was to treat the 'qix' as an array of start and end coordinates. I didn't bother to implement coordinates as a 2-element array, so my 'qix' consists of 4 1-dimensional arrays (pointxone, pointyone, pointxtwo, pointytwo), representing the x and y coordinates of the start and end points and all its phases. The head of the 'qix' being the first (0) elements of the arrays. I have always sucked at explaining technical stuff. So the head will be drawn from coordinates (pointxone[0],pointyone[0]) to (pointxtwo[0],pointytwo[0]). The end of the tail, the most faded segment, is a line from (pointxone[tail-1],pointyone[tail-1]) to (pointxtwo[tail-1],pointytwo[tail-1]).
We will declare some global constants and variables first.
var maxx = 560; // width of the canvas var maxy = 560; // height of the canvas var tail = 50; // segments of the tail var speed = 10; // distance the qix can travel (affects angle) var pointxone = new Array(tail); // the start x-coordinates of the qix var pointyone = new Array(tail); // the start y-coordinates of the qix var pointxtwo = new Array(tail); // the end x-coordinates of the qix var pointytwo = new Array(tail); // the end y-coordinates of the qix var plusxone; // horizontal direction of start point var plusyone; // vertical direction of start point var plusxtwo; // horizontal direction of end point var plusytwo; // vertical direction of end point Following these declarations, we will initiliaze the 'qix' in the function init(). This function will be called once only.
function init() { // pick a starting x,y for both ends of the qix // let's start in dead center pointxone[0] = maxx / 2; pointyone[0] = maxy / 2; pointxtwo[0] = maxx / 2; pointytwo[0] = maxy / 2; // pick a x,y direction for both ends // the ends are completely independent of each other plusxone = parseInt(Math.random() * 2 * speed) - speed; plusyone = parseInt(Math.random() * 2 * speed) - speed; plusxtwo = parseInt(Math.random() * 2 * speed) - speed; plusytwo = parseInt(Math.random() * 2 * speed) - speed; // fill up the qix from head to tail // every segment will start where the head is now for (t=tail-1; t>0; t--) { pointxone[t] = pointxone[t-1]; pointyone[t] = pointyone[t-1]; pointxtwo[t] = pointxtwo[t-1]; pointytwo[t] = pointytwo[t-1]; } // set an interval (affects speed) for flying the qix setInterval("draw()", 10); }Call init() from the onLoad event in the page's <body onLoad="init()"> tag, or if you can't edit the <body> tag for some reason, with a setTimeout('init()', 1000) to make sure the canvas and everything is loaded before trying to run the script.
The function init() will trigger the animation by calling draw(). This is the function draw():
function draw() { var canvas = document.getElementById("canvas"); if (canvas.getContext) { // confirmed canvas var ctx = canvas.getContext("2d"); // just assume it's 2d // shift the segments from tail to head for (var t=tail-1; t>0; t--) { pointxone[t] = pointxone[t-1]; pointyone[t] = pointyone[t-1]; pointxtwo[t] = pointxtwo[t-1]; pointytwo[t] = pointytwo[t-1]; } // move the head in direction pointxone[0] += plusxone; pointyone[0] += plusyone; pointxtwo[0] += plusxtwo; pointytwo[0] += plusytwo; // collision detection for dummies // if outside, negate the appropriate direction if (pointxone[0]<=0) { plusxone = parseInt(Math.random()*speed); plusyone = parseInt(Math.random()*2*speed)-speed; } else if (pointxone[0]>=maxx-1) { plusxone = parseInt(Math.random()*speed)-speed; plusyone = parseInt(Math.random()*2*speed)-speed; } if (pointyone[0]<=0) { plusyone = parseInt(Math.random()*speed); plusxone = parseInt(Math.random()*2*speed)-speed; } else if (pointyone[0]>=maxy-1) { plusyone = parseInt(Math.random()*speed)-speed; plusxone = parseInt(Math.random()*2*speed)-speed; } if (pointxtwo[0]<=0) { plusxtwo = parseInt(Math.random()*speed); plusytwo = parseInt(Math.random()*2*speed)-speed; } else if (pointxtwo[0]>=maxx-1) { plusxtwo = parseInt(Math.random()*speed)-speed; plusytwo = parseInt(Math.random()*2*speed)-speed; } if (pointytwo[0]<=0) { plusytwo = parseInt(Math.random()*speed); plusxtwo = parseInt(Math.random()*2*speed)-speed; } else if (pointytwo[0]>=maxy-1) { plusytwo = parseInt(Math.random()*speed)-speed; plusxtwo = parseInt(Math.random()*2*speed)-speed; } // and draw the qix // from last segment of the tail, toward the head for (var t=tail-1; t>=0; t--) { if (t==tail-1) { // drawing in canvas is not an exact // science and to prevent ghosting, // we need to make sure the last // (faded out) segment is bigger and // wider than the previous ones ctx.lineWidth = 4; ctx.lineCap = 'square'; } else { ctx.lineWidth = 1; ctx.lineCap = 'round'; } // fade out color // front of the qix is red, tail end is lighter ctx.strokeStyle = "rgb(255," + (255-Math.floor(255/tail)*(tail-t-1)) + "," + (255-Math.floor(255/tail)*(tail-t-1)) + ")"; ctx.beginPath(); ctx.moveTo(pointxone[t],pointyone[t]); ctx.lineTo(pointxtwo[t],pointytwo[t]); ctx.stroke(); } } }And that is all. Some day I might optimize the code, but that means it will get less readable, so I'm not in a hurry. Experimenting and developing new stuff has a higher priority for me.
I hope this helped someone, good luck!
|
|