Signature Animation Explained
You'll recognize the movie above as the animation that runs on the entry page for this Web site. It uses a replacement technique based on the Bitmap class, and some shape-drawing effects that employ the Drawing API.
Most of the relevant code here resides in the document class (bmc_starterDocClass). Since that class contains more than 200 lines, though, it's not feasible to discuss every detail. We'll restrict ourselves to the two most interesting features, the random block replacement, and the automated shape drawing.
I. Image replacement in random blocks
The replacement of one image by another that underlies this animation operates on a grid of 25x25-pixel squares. The order in which these squares are replaced is random for each cycle of the animation. Here's how that trick is accomplished.
In the Constructor for our document class, we first create an Array that records all the key points in our grid of squares -- the upper left corner points from which each square will be drawn. Here's the code that does this:
sourceArray = new Array(312);
var sourceCount:int = 0;
for(var n:int=0; n<650; n+=25)
{
for(var p:int=0; p<300; p+=25)
{
refPoint = new Point(n,p);
sourceArray[sourceCount] = refPoint;
sourceCount ++;
}
}
Now, here's how this code does its thing. The graphics (and movie) dimensions for this project are 650x300. 650 divided by 25 is 26; 300 divided by 25 is 12. Therefore, our grid consists of 12 rows of 26 squares. This means our array then must have 12 x 26 entries, or 312. Again, each entry maps one corner point.
To derive the x and y values for those points, we nest one for loop within another. The outer loop counts the X dimension, the inner loop counts the Y. For each pass through the inner loop, we generate a number based on the two loop indices, giving us x and y references for a Point object called refPoint.The Point class simply contains paired numbers.
Each time we record a point, we also increment a third counting variable, independent of the loop indices. This variable is called sourceCount. We use it to advance the index of sourceArray. At the end of this process, we have 312 entries in sourceArray, each one referring to a corner point in our 12x26 grid.
We'll use sourceArray, as the name suggests, to generate a second Array that contains the same 312 corner points, but in random order. So yes, all we're really doing here is randomizing the order of an Array.
The code that handles this operation is located in a custom method called initialize(), which includes all the instructions that set initial conditions for a cycle of the animation. Those instructions are broken out into a separate method so they can be repeated. The sourceArray setup stays in the Constructor because it only needs to be executed once. The same source array can be re-shuffled many times.
Here's the shuffler:
for(var a:int=0; a<312; a++)
{
while(!unique)
{
var match:Boolean = false;
var candidate:int = Math.floor(Math.random()*312);
for(var z:int=0; z<pickedArray.length; z++)
{
if(candidate == pickedArray[z]) match = true;
}
if(!match) unique = true;
}
planArray[a] = candidate;
pickedArray.push(candidate);
unique = false;
}
There's a fair amount going on in this chunk of code -- perhaps more than a more elegant programmer would find necessary. But the solution works, and makes a certain kind of sense, so we'll explain it.
We start out with a for loop that runs from 0 to 311, using the index a. As you might suspect, this loop lets us run through all the items in sourceArray (whose 312 entries are numbered 0 to 311).
Inside this loop, we immediately enter another loop, this one a while. You may remember that a while loop runs indefinitely, until its expressed conditions are met. The condition we're looking for here is a value of false in a flag variable called unique. This variable is set to false at the top of the initialize() method, before we start into either loop.
Inside the while loop, we set a flag variable called match to false, then pick a random number between 0 and 311 and assign it to a local variable called candidate. This number is our candidate for selection in our attempt to build a random re-shuffling of the items in sourceArray.
Now, we can accept our candidate only if it represents an item that has not already been selected. To ensure this, we run yet another for loop, this time reviewing all the elements in yet another Array, called pickedArray. This Array has been initialized as blank -- an Array with no elements. So the first time through, we compare our candidate with zero elements in pickedArray; but as you'll see, the contents of pickedArray grow as we make selections.
If at any point during the scan of pickedArray we match our candidate with one of its items, match becomes true. If we don't see any matches, match remains false.
After we've checked every item in pickedArray, we ask if match is false. If it is, then unique becomes true. That is, we've made sure that our candidate number is not in the list of numbers previously selected. Once unique is true, we automatically exit our while loop, having successfully chosen a candidate.
We assign candidate to item a of planArray. The variable a is the index of our outer loop, and planArray is the container that holds our shuffled version of sourceArray. (In effect, it ends up as a shuffled copy of sourceArray.)
We finish up with two crucial details. First, we use the push() method of the Array class to append candidate to pickedArray, which will prevent us from picking that number in the future. Finally, we set unique back to false, so we can perform another iteration of the while loop on the next pass through the main for loop.
At the end of this convoluted process, the 312 elements in planArray represent the 312 elements of sourceArray, in a randomized order.
As you can probably guess, when we do the actual block replacement, using the now familiar mechanism of getPixels() and setPixels() on BitmapData, we use the points stored in planArray as the reference data for each block to be drawn. This is how we manage to draw our blocks in random order.
II. Drawing rounded rectangles
The variously sized rounded rectangles that appear during the random block replacement are added as an independent visual effect. It's possible to run the block replacement process without them, and vice versa. They are connected (literally) at one point: the reference point for drawing each rectangle is also an item in sourceArray. If you watch closely, you'll see that a replacement block always appears in the upper left corner of a rectangle. (However, blocks sometimes appear without rectangles, as we'll explain below.)
Here's the code that draws the rounded rectangles. It occurs within blockOut(), the callback that handles block replacement. We've had to break one line (invovation of the drawRoundRect() method, because of excessive length. It was originally written with no break.
RR.graphics.lineStyle(1+Math.random()*2,Math.floor(Math.random()*0xFFFFFF), 0.7);
RR.graphics.beginFill(Math.floor(Math.random()*0xFFFFFF), 0.22);
var rf:int = 40+Math.random()*50;
if(theCount%3 == 0)
{
RR.graphics.drawRoundRect(sourceArray[planArray[outCount]].x,
sourceArray[planArray[outCount]].y, rf*1.55, rf,65,10);
}
The first two statements here should be familiar from our coverage of the Drawing API. Line width and the color of line and fill are all randomized.
The variable rf (for random factor) controls the width and height of our rounded rectangles. the width is always 55% greater than the height, which keeps the shapes proportional, even though their sizes vary. The last two parameters in the drawRoundRect() method control the edge curvature of the rounded shapes. They remain constant.
Finally, notice that the drawing operation only happens if a variable called theCount is evenly divisible by 3. This variable increments every time blockOut() is called. As a result, we only draw rounded rectangles once for every three block replacements, at iterations 3,6,9,12, etc. This explains why some blocks appear without associated rectangles. The decision to restrict the rectangles in this way was purely aesthetic.
III. Source files
As we said, these notes cover only the most interesting parts of the animation program. We have left many details out of the account. You can read the complete code, line-for-line, by downloading the contents of the directory classPageAnimation within MULTIMEDIA in the shared account on student-iat.
|
|