Sky’s the Limit: AgencyNet Shows the Possibilities with Flash CS4 (Part 2 of 3)

NOTE: This is one of three articles commissioned by Adobe and written by separate design agencies to demonstrate the variety of techniques available in Flash CS4 Professional. In these examples, three techniques are shown for creating realistic fireworks effects; all require intermediate to advanced ActionScript 3 skills. Enjoy! —Jeremy Schultz

Introduction by Kristan Jiles, Senior Product Marketing Manager, Adobe Systems Incorporated

Three design agencies that are given an identical initial directive, using the same tool, will return with very different project results. The creative process is essential in determining the end result; it’s how professionals use their tools to bring their work to life. We asked design agencies AgencyNet, AKQA and Vasava to develop a tutorial on how to create a fireworks display using Adobe Flash CS4 Professional to demonstrate how the same tool used to design the same project can yield such unique and differing results. At Adobe, we’re inspired by seeing how each creative uses their knowledge of the product to create amazing and fun projects that can be shared with others. Flash was developed to provide a variety of design and development options for any project and this is only one example. With animation, 3D transformation, rich drawing capabilities, and ActionScript development, we encourage designers and developers to continue experimenting with Flash to produce amazing work!

Tutorial by AgencyNet (Flash 10 required)

Step 1: Preheat oven to 350 degrees.

Firstly, let’s setup the Flash stage and main FLA:

  1. Create a new Flash File (ActionScript 3.0).
  2. Set the size of the stage to 1000 x 500 (although your favorite size will work just fine).
  3. Set the background color of the stage to black or your preferred night sky color.
  4. Set the FPS to 30.
  5. Save your file as Fireworks.fla in a clean folder.

Now create a new Symbol (Insert > New Symbol):

create

  1. Name the symbol Firework Particle.
  2. In the Linkage group, check Export for ActionScript.
  3. Enter the Class name FireworkParticle (should be filled-in by default!).
  4. Click OK.

On the symbol, create a star by drawing a 2×2 yellow circle, and two 0.5 lines forming a cross, like so:

star

Alternatively, you can insert your favorite star bitmap, preferably no larger than 5×5 pixels or so.

Step 2: Bake until golden brown.

Now that we’re all setup, let’s create three .AS files in the same directory as the FLA. (File > New > ActionScript File):

  1. Fireworks.as – This will be our new stage class.
  2. Firework.as – This class will represent one launch/burst of a single firework.
  3. FireworkParticle.as – This class will support the Firework Particle clip we created earlier.

Briefly jump back to your Fireworks.fla file, and (ensuring nothing is selected), set the Document Class to Fireworks.

setup

In Fireworks.as, paste the following code:

package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
// Stage class, handles creation of fireworks and main timer
public class Fireworks extends MovieClip
{
public static const TIMER_BASE:Number = 900;
public static const TIMER_RANDOM:Number = 200;
public static const MAIN_TIMER_UPDATE_INTERVAL:Number = 20;
public static const MAX_FIREWORKS:Number = 7;
private var launch_timer:Timer;
private var main_timer:Timer;
// Stage class constructor
public function Fireworks()
{
startTimer();
main_timer = new Timer(MAIN_TIMER_UPDATE_INTERVAL);
main_timer.addEventListener(TimerEvent.TIMER, handleRuntime);
main_timer.start();
}
// Handler: TimerEvent.TIMER (main_timer)
private function handleRuntime(event:TimerEvent):void
{
for(var i = 0; i < numChildren; i++)
{
var firework = getChildAt(i) as Firework;
firework.update();
}
}
// Handler: TimerEvent.TIMER (launch_timer)
private function handleTimer(event:TimerEvent):void
{
launch_timer.removeEventListener(TimerEvent.TIMER, handleTimer);
// Prevent from having too many at once
if(numChildren < MAX_FIREWORKS)
{
var firework = new Firework();
firework.addEventListener(Event.COMPLETE, removeFirework);
addChild(firework);
}
startTimer();
}
// Launch new firework
private function startTimer():void
{
var duration = TIMER_RANDOM * Math.random() + TIMER_BASE;
launch_timer = new Timer(duration, 1);
launch_timer.addEventListener(TimerEvent.TIMER, handleTimer);
launch_timer.start();
}
// Handler: Event.COMPLETE (removes firework after it is completed)
private function removeFirework(event:Event):void
{
var firework = event.target as Firework;
removeChild(firework);
firework = null;
}
}
}

In Firework.as, paste the following code:

package
{
import flash.display.Bitmap;
import flash.display.BitmapData
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.geom.Vector3D;
// Firework explosion and launch
public class Firework extends MovieClip
{
public static const GRAVITY:Vector3D = new Vector3D(0, 0.018);
public static const LAUNCH_SPEED_X:Number = 0;
public static const LAUNCH_SPEED_Y:Number = -7;
public static const LAUNCH_PARTICLE_ALPHA:Number = 0.03;
public static const PARTICLE_RANDOM:Number = 30;
public static const PARTICLE_BASE:Number = 10;
public static const BITMAP_SIZE:Number = 300;
public static const BITMAP_OFFSET:Number = 150;
public static const DISPERSION_MULTIPLIER:Number = 1;
private var exploded:Boolean = false;
private var launching_particle:FireworkParticle;
private var particle_count:Number;
private var blit_array:Array;
private var trail_bitmap:Bitmap;
private var completed_particles:Number = 0;
private var particle_transform:ColorTransform;
private var transform_colors:Array = new Array
(
new ColorTransform(1, 1, 1, 0.92), // White
new ColorTransform(1, 0.9, 0.9, 0.92), // Red
new ColorTransform(0.9, 1, 0.9, 0.92), // Green
new ColorTransform(0.7, 0.9999, 1, 0.92), // Blue
new ColorTransform(1, 1, 0.9, 0.92), // Yellow
new ColorTransform(1, 1, 1, 0.5) // Black
);
// Firework constructor
public function Firework()
{
// Pick a random particle count and a random color for the trails
particle_count = Math.round(Math.random() * PARTICLE_RANDOM + PARTICLE_BASE);
particle_transform = transform_colors[Math.floor(Math.random()*transform_colors.length)];
addEventListener(Event.ADDED_TO_STAGE, handleAddedToStage);
addEventListener(Event.REMOVED_FROM_STAGE, handleRemovedFromStage)
}
// Update: called by main timer
public function update():void
{
if(exploded)
{
for(var i = 1; i < numChildren; i++)
{
var particle = getChildAt(i);
particle.update(GRAVITY);
if(particle.alpha <= 0 && particle.alive)
{
particle.alive = false;
completed_particles++;
if(completed_particles >= particle_count)
{
dispatchEvent(new Event(Event.COMPLETE));
return;
}
}
}
}
else
{
launching_particle.oscillate();
launching_particle.update(GRAVITY, LAUNCH_PARTICLE_ALPHA);
if(launching_particle.y < BITMAP_OFFSET)
{
removeChild(launching_particle);
explode();
}
}
// Blitting
var newBitmapData:BitmapData = new BitmapData(width, height, true, 0x000000);
newBitmapData.draw(this, null, particle_transform, null, null, true);
blit(newBitmapData);
}
// Handler: Event.ADDED_TO_STAGE
private function handleAddedToStage(event:Event):void
{
// Initialize random target position on stage
x = (stage.stageWidth) * Math.random() - BITMAP_OFFSET;
y = (stage.stageHeight) * Math.random() - BITMAP_OFFSET;
// Initialize bitmap and container
trail_bitmap = new Bitmap();
addChild(trail_bitmap);
blit_array = new Array();
launching_particle = new FireworkParticle();
launching_particle.y = stage.stageHeight - y;
launching_particle.init(LAUNCH_SPEED_X, LAUNCH_SPEED_Y, false);
addChild(launching_particle);
}
// Handler: Event.REMOVED_FROM_STAGE
private function handleRemovedFromStage(event:Event):void
{
destroyBitmaps();
}
// Explode firework
private function explode():void
{
// Initialize particles
for(var i = 0; i < particle_count; i++)
{
var explodeworkParticle:FireworkParticle = new FireworkParticle();
explodeworkParticle.init(Math.sin((Math.PI*2 / particle_count) * i + Math.random()*DISPERSION_MULTIPLIER), Math.cos((Math.PI*2 / particle_count) * i + Math.random()*DISPERSION_MULTIPLIER));
addChild(explodeworkParticle);
}
exploded = true;
}
// Update trail_bitmap with fresh BitmapData
private function blit(bitmapData:BitmapData):void
{
trail_bitmap.bitmapData = bitmapData;
if(blit_array.length < 1)
{
blit_array.push(bitmapData);
}
else
{
blit_array.push(bitmapData);
// Destroy old bitmap data
var oldBitmapData:BitmapData = blit_array.shift();
oldBitmapData.dispose()
oldBitmapData = null;
}
}
// Clean up any lingering bitmaps after being being removed from stage
private function destroyBitmaps():void
{
var imagesLeft:int = blit_array.length;
for(var i = 0; i < imagesLeft; i++)
{
var img:BitmapData = blit_array[i] as BitmapData;
img.dispose();
img = null;
}
}
}
}

Finally, in FireworkParticle.as, paste the following code:

package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Vector3D;
// Firework particle
public class FireworkParticle extends MovieClip
{
public static const NORMALIZE_POSSIBILITY:Number = 0.7;
public static const SPEED_MULTIPLIER:Number = 1.2;
public static const OSCILLATE_AMPLITUDE:Number = 3;
public static const OSCILLATE_FALLOFF:Number = 150;
public var direction:Vector3D;
public var alive:Boolean = true;
private var frame_counter:Number = 0;
// Constructor
public function FireworkParticle()
{
x = 150;
y = 150;
cacheAsBitmap = true;
}
// Initialize particle direction and normalize
public function init(direction_x:Number, direction_y:Number, possible_normalize:Boolean = true):void
{
direction = new Vector3D(direction_x, direction_y);
if(Math.random() < NORMALIZE_POSSIBILITY && possible_normalize)
{
direction.normalize();
}
direction.scaleBy(SPEED_MULTIPLIER);
}
// Shake particle side to side
public function oscillate():void
{
direction.x += Math.sin(frame_counter / OSCILLATE_AMPLITUDE) * (frame_counter / OSCILLATE_FALLOFF);
}
// Update position, alpha, and framecounter (called by parent)
public function update(gravity:Vector3D, delta_alpha = 0.005):void
{
direction = direction.add(gravity);
x += direction.x;
y += direction.y;
alpha -= delta_alpha;
frame_counter++;
}
}
}

And voila! Make sure to save all files, and publish the Fireworks.fla to see some fireworks!

Step 3: Add salt and pepper to taste!

Now that you have some fabulous fireworks, turn your attention to the static constants (THEY_LOOK_LIKE_THIS) towards the top of the Fireworks.as, Firework.as, and FireworkParticle.as. These are constants that modify the behavior of the fireworks—feel free to tweak them to get the desired effect!

To kick things up a notch, you can also add a quick starfield, so that the fireworks have a nicer backdrop than plain black!

Here's one we put together (save it as Starfield.as):

package
{
import flash.display.Bitmap;
import flash.display.BitmapData
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
// Starfield backdrop
public class Starfield extends MovieClip
{
public static const STAR_AMOUNT:Number = 500;
public static const STAR_ALPHA_MAX:Number = 0.4;
// Constructor
public function Starfield()
{
cacheAsBitmap = true;
addEventListener(Event.ADDED_TO_STAGE, handleAddedToStage);
}
// Handler: Event.ADDED_TO_STAGE
private function handleAddedToStage(event:Event):void
{
var bitmap:Bitmap = new Bitmap(new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000));
var star:FireworkParticle = new FireworkParticle();
var translation_matrix:Matrix = new Matrix();
var color_transform:ColorTransform = new ColorTransform();
for(var i = 0; i < STAR_AMOUNT; i++)
{
var scale = Math.random() * 1.5 - 0.5;
translation_matrix.a = scale;
translation_matrix.d = scale;
translation_matrix.tx = Math.random() * stage.stageWidth;
translation_matrix.ty = Math.random() * stage.stageHeight;
color_transform.alphaMultiplier = Math.random() * STAR_ALPHA_MAX;
bitmap.bitmapData.draw(star, translation_matrix, color_transform, null, null, true);
}
addChild(bitmap);
}
}
}

To add it to stage, save the file in the same directory then, in the Fireworks constructor (public function Fireworks()), add the following code after the {

addChild(new Starfield());

Then, in the handleRuntime function, change the line that reads for(var i = 0; i < numChildren; i++) to:

for(var i = 1; i < numChildren; i++)

This ensures we ignore the starfield when updating the fireworks.

Have fun, and enjoy the show!

See the final result!