Wednesday, July 18, 2012

How to make a pixel fade effect using AS3: Part 2

Today we finish the pixel fading effect class.

We left everyFrame() function empty last time. This function is the event handler of ENTER_FRAME event, and here we need to add code that will repeat itself every frame.

First thing we want to do is move and draw the particles from the array. Do this by calling updateParticles():

// move and draw particles
updateParticles();

Then we need to update the particleScreen Bitmap object by feeding it a fresh particleData BitmapData, which was edited in updateParticles():

// update screen
particleScreen = new Bitmap(particleData);

After that, we call a function called gradIncrease(). This will gradually increase the number of moving pixels on stage, because we dont want to move all of them at once:

// gradually move pixels
gradIncrease();

Full function:

private function everyFrame(evt:Event):void {
// move and draw particles
updateParticles();
// update screen
particleScreen = new Bitmap(particleData);
// gradually move pixels
gradIncrease();
}

Add the updateParticles() function.

We have an empty BitmapData object called staticData. This will hold the pixels that have already reached their destination. This will dramatically improve performance, because there is no point in handling pixels that already stopped moving the same way we handle moving pixels.

So, we can use this staticData to create the particleData BitmapData every frame. We draw the static pixels on it, and then add the pixels that are not on their destination positions yet.

So, we loop through the elements of pixels array after drawing staticData in particleData, and check if the pixel's "go" flag is set to true. If so, then it must be moved. Set its coordinates using a function movePixel(), pass current coordinate and destination coordinate as parameters. After setting the coordinates, check if current coordinates are the same as destination coordinates. If yes, set this pixel's "go" property to false and draw this pixel to staticData.

Then check one more time if the "go" property is true after the coordinates are set, and draw the pixel to particleData if so:

private function updateParticles():void {
particleData.draw(staticData);
for (var i:int = 0; i < pixels.length; i++) {
// draw pixel
if (pixels[i].go) {
pixels[i].x = movePixel(pixels[i].x, pixels[i].gx);
pixels[i].y = movePixel(pixels[i].y, pixels[i].gy);
if (pixels[i].x == pixels[i].gx && pixels[i].y == pixels[i].gy) {
pixels[i].go = false;
staticData.setPixel32(pixels[i].gx, pixels[i].gy, pixels[i].c);
}
if (pixels[i].go == true) particleData.setPixel32(pixels[i].x, pixels[i].y, pixels[i].c);
}
}
}

The movePixel() function simply calculates the next position of the pixel to ensure smooth movement (its speed depends on the distance). Check if the distance is more than -2 and less than 2, and just set the pixel to its destination position in that case.

private function movePixel(cur:int, go:int):int {
var goes:int = (go - cur)/5;
if (goes<2 && goes>-2) {
goes = go;
return goes;
}
goes += cur;
return goes;
}

And we have just one more function left - gradIncrease(). This one loops through pixel array elements, but not all of them, just a specific amount of elements at once. We declared prevstep and step values, prevstep being 0 and step 250. We'll just use these values to randomly place pixels around (spawn them) and set their "go" property to true. The array index increases after every frame by the amount of pixels previously handled. Basically, we go through the first 250 elements in the first frame, then on the next 250 on the second frame, and so on.

private function gradIncrease():void {
for (var i:int = prevstep; i < prevstep + step; i++) {
if (pixels.length > i) {
pixels[i].go = true;
pixels[i].x = Math.random()*stage.stageWidth;
pixels[i].y = Math.random()*stage.stageHeight;
}
}
prevstep += step;
}

And we are done!

Full code:

package  
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.utils.getTimer;
import flash.utils.Timer;
/**
 * ...
 * @author Kirill Poletaev
 */
public class pixel_fade extends MovieClip
{
private var bitmapData:BitmapData;
private var particleData:BitmapData;
private var particleScreen:Bitmap;
private var staticData:BitmapData;
private var pixels:Array = [];
private var step:int = 250;
private var prevstep:int = 0;
private var startX:int = 0;
private var startY:int = 0;
private var actionTimer:Timer;

public function pixel_fade() 
{
// stop main
(root as MovieClip).stop();
// capture pic
bitmapData = new BitmapData(content.width, content.height, true, 0xffffffff);
bitmapData.draw(content);
content.alpha = 0;
// set start pos
startX = stage.stageWidth / 2 - content.width / 2;
startY = stage.stageHeight / 2 - content.height / 2;
// create particle screen
particleData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
particleScreen = new Bitmap(particleData);
addChild(particleScreen);
// create blank static screen
staticData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
// start by getting all pixel data
getData();
// add listeners
addEventListener(Event.ENTER_FRAME, everyFrame);
}

private function everyFrame(evt:Event):void {
// move and draw particles
updateParticles();
// update screen
particleScreen = new Bitmap(particleData);
// gradually move pixels
gradIncrease();
}

private function gradIncrease():void {
for (var i:int = prevstep; i < prevstep + step; i++) {
if (pixels.length > i) {
pixels[i].go = true;
pixels[i].x = Math.random()*stage.stageWidth;
pixels[i].y = Math.random()*stage.stageHeight;
}
}
prevstep += step;
}

private function updateParticles():void {
particleData.draw(staticData);
for (var i:int = 0; i < pixels.length; i++) {
// draw pixel
if (pixels[i].go) {
pixels[i].x = movePixel(pixels[i].x, pixels[i].gx);
pixels[i].y = movePixel(pixels[i].y, pixels[i].gy);
if (pixels[i].x == pixels[i].gx && pixels[i].y == pixels[i].gy) {
pixels[i].go = false;
staticData.setPixel32(pixels[i].gx, pixels[i].gy, pixels[i].c);
}
if (pixels[i].go == true) particleData.setPixel32(pixels[i].x, pixels[i].y, pixels[i].c);
}
}
}

private function movePixel(cur:int, go:int):int {
var goes:int = (go - cur)/5;
if (goes<2 && goes>-2) {
goes = go;
return goes;
}
goes += cur;
return goes;
}

private function getData():void {
var startTimer:Number = getTimer();
var col:uint;
for (var i:int = 0; i < content.width; i++) {
for (var u:int = 0; u < content.height; u++) {
col = bitmapData.getPixel32(i, u);
if(col != 0xffffffff){
pixels.push( { x:0, y:0, gx:startX+i, gy:startY+u, c:col, go:false } );
}
}
}
var nowTimer:Number = getTimer();
var elapsed:Number = nowTimer - startTimer;
trace("total pixels: " + pixels.length + ", time elapsed: " + elapsed + "ms");
// start timer
actionTimer = new Timer(4000, 1);
actionTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
actionTimer.start();
}

private function onTimer(evt:TimerEvent):void {
(root as MovieClip).play();
}

}

}

There are some tweaks that can be made to the code for variations of effects, but the base of the class is all done and changing the effect will usually only require a few tweaks in the code.

Thanks for reading!

1 comment:

Jury said...

Really very helpful tutorial.
Bookmarked for future use.
Thanks!

Post a Comment