I’ve always had a interest in particle systems stemming from playing a lot of video games as a kid (wait, still do) and wondering how explosions and blood splatters were created. I figured by developing my own basic particle system library I would be able to 1) learn more about ActionScript, 2) have a greater understanding of OOP, and 3) do something cool!
A quick, but necessary reminder: I am a Flash designer attempting to learn more about ActionScript development so please follow at your own risk and if you see an error please leave a comment.
We’ll begin with an ActionScript 2.0 version since I need a little refresher. Revisiting AS2 makes me appreciate AS3 that much more. Here’s a sample usage of the particle system:
(this performance test was inspired by the GreenSock Performance Tests)
These sites are good reads and helped point me in the right direction: Particle System API and “Particle Systems” by Allen Martin. The library is simple and consists of only 2 classes: a Particle class and an Emitter class. The Particle class will represent a single particle that is instantiated by the Emitter class.
//AS2///////////////////////////////////////////////////////////////////////////
//
// Emitter
//
// Copyright (c) 2009 Jason Woan. All rights reserved.
//
// Do what you want with the code. Credit is nice but not necessary. Use at
// your own risk and discretion. I won't be held responsible if your site
// blows up. Enjoy!
//
////////////////////////////////////////////////////////////////////////////////
/**
* This class is the blueprint to a single particle maintaining its inherent
* properties and performing position calculations. Use as a MovieClip's
* linkage class.
*
* @version 0.1.0
* @langversion ActionScript 2.0
* @playerversion Flash 8.0
*
* @author Jason_Woan
* @since 2009-05-14
*/
dynamic class com.jasonwoan.speck.Particle extends MovieClip {
//--------------------------------------
// PUBLIC VARIABLES
//--------------------------------------
public var xVelocity:Number = 1;
public var yVelocity:Number = 1;
public var xAccel:Number = 0;
public var yAccel:Number = 0;
//public var rotationalVelocity:Number = 0;
//public var mass:Number = 1;
public var age:Number = 0;
public var life:Number = 10;
public var dead:Boolean = false;
public var direction:Number = 45;
public var speed:Number = 1;
public var fade:Boolean = false;
public var scale:Number = 100;
public var shrink:Boolean = false;
public var xMinBound:Number;
public var xMaxBound:Number;
public var yMinBound:Number;
public var yMaxBound:Number;
public var restitution:Number;
//--------------------------------------
// PRIVATE & PROTECTED VARIABLES
//--------------------------------------
private var _xPrevious:Number;
private var _yPrevious:Number;
private var _scaleStart:Number;
//--------------------------------------
// CONSTRUCTOR
//--------------------------------------
/**
* @constructor
*/
public function Particle() {
// calculate directional velocity
var radDirection:Number = this.direction * Math.PI / 180;
xVelocity = this.speed * Math.cos(radDirection);
yVelocity = this.speed * Math.sin(radDirection);
this._xscale = this._yscale = this.scale;
}
//--------------------------------------
// PUBLIC METHODS
//--------------------------------------
public function update():Void
{
if (!dead) {
// save previous position
this._xPrevious = this._x;
this._yPrevious = this._y;
// check bounds
if (this.xMinBound != undefined) {
if (this._x + this.xVelocity < this.xMinBound) {
this.xVelocity = -1 * this.restitution;
}
}
if (this.xMaxBound != undefined) {
if (this._x + this.xVelocity > this.xMaxBound) {
this.xVelocity = -1 * this.restitution;
}
}
if (this.yMinBound != undefined) {
if (this._y + this.yVelocity < this.yMinBound) {
this.yVelocity *= -1 * this.restitution;
}
}
if (this.yMaxBound != undefined) {
if (this._y + this.yVelocity > this.yMaxBound) {
this.yVelocity *= -1 * this.restitution;
}
}
// update position
this._x += this.xVelocity;
this._y += this.yVelocity;
// update velocity
this.xVelocity += this.xAccel;
this.yVelocity += this.yAccel;
this.age++;
// particle shape and alpha dynamics
if (this.fade) {
this._alpha = 100 - Math.round(this.age / this.life * 100);
}
if (shrink) {
this._xscale = this._yscale = this.scale - Math.round(this.age / this.life * scale);
}
// check for death
if (this.age == this.life) {
this.dead = true;
this._visible = false;
}
}
}
public function revive():Void
{
if (this.dead) {
this.dead = false;
this._visible = true;
this.age = 0;
// reset velocities
var radDirection:Number = this.direction * Math.PI / 180;
this.xVelocity = this.speed * Math.cos(radDirection);
this.yVelocity = this.speed * Math.sin(radDirection);
}
}
}
//AS2///////////////////////////////////////////////////////////////////////////
//
// Emitter
//
// Copyright (c) 2009 Jason Woan. All rights reserved.
//
// Do what you want with the code. Credit is nice but not necessary. Use at
// your own risk and discretion. I won't be held responsible if your site
// blows up. Enjoy!
//
////////////////////////////////////////////////////////////////////////////////
import mx.utils.Delegate;
import com.tbwachiat.speck.Particle;
/**
* The Emitter manages the creation of particles.
*
* @version 0.1.0
* @langversion ActionScript 2.0
* @playerversion Flash 8.0
*
* @author Jason_Woan
* @since 2009-05-14
*/
class com.jasonwoan.speck.Emitter {
//--------------------------------------
// PRIVATE & PROTECTED VARIABLES
//--------------------------------------
private var _gravity:Number = 0;
// number of particles per step
private var _birthRate:Number = 3;
// the life of each particle in seconds
private var _life:Number = 100;
private var _scale:Number = 100;
private var _scaleMin:Number;
private var _scaleMax:Number;
private var _speed:Number = 1;
private var _direction:Number = 45;
private var _randomDirection:Boolean = false;
private var _xAccel:Number = 0;
private var _yAccel:Number = 0;
private var _xMin:Number = 0;
private var _xMax:Number = 0;
private var _yMin:Number = 0;
private var _yMax:Number = 0;
private var _xMinBound:Number;
private var _xMaxBound:Number;
private var _yMinBound:Number;
private var _yMaxBound:Number;
private var _restitution:Number = 1;
private var _fade:Boolean = false;
private var _shrink:Boolean = false;
private var _emitter:MovieClip;
private var _particleID:String;
private var _particles:Array;
private var _oldestIndex:Number = 0;
//--------------------------------------
// CONSTRUCTOR
//--------------------------------------
/**
* @constructor
*/
public function Emitter(pEmitter:MovieClip, pParticleID:String, pParameters:Object) {
_emitter = pEmitter;
_particleID = pParticleID;
_particles = new Array();
// check parameter object
for (var prop in pParameters) {
switch (prop) {
// the life of a particle in frames
// the particle will be recycled if dead
case "life" :
if (pParameters[prop] > 0) _life = pParameters[prop];
break;
// the number of particles generated per update on enter frame
case "birthRate" :
if (pParameters[prop] > 0) _birthRate = pParameters[prop];
break;
// a positive acceleration of a particle in the y-direction
case "gravity" :
_gravity = pParameters[prop];
break;
// the initial x and y scale of a particle
case "scale" :
_scale = pParameters[prop];
break;
// the minimum initial x and y scale of a particle
// use in conjuction with scaleMax
case "scaleMin" :
_scaleMin = pParameters[prop];
break;
// the maximum initial x and y scale of a particle
// use in conjuction with scaleMin
case "scaleMax" :
_scaleMax = pParameters[prop];
break;
// the distance traveled by the particle per enter frame
case "speed" :
_speed = pParameters[prop];
break;
// acceleration of the particle in the x-direction
case "xAccel" :
_xAccel = pParameters[prop];
break;
// acceleration of the particle in the y-direction
case "yAccel" :
_yAccel = pParameters[prop];
break;
// the minimum initial x position to create a particle
case "xMin" :
_xMin = pParameters[prop];
break;
// the maximum initial x position to create a particle
case "xMax" :
_xMax = pParameters[prop];
break;
// the minimum initial y position to create a particle
case "yMin" :
_yMin = pParameters[prop];
break;
// the maximum initial y position to create a particle
case "yMax" :
_yMax = pParameters[prop];
break;
// the left "wall" of the emitter
case "xMinBound" :
_xMinBound = pParameters[prop];
break;
// the right "wall" of the emitter
case "xMaxBound" :
_xMaxBound = pParameters[prop];
break;
// the "ceiling" of the emitter
case "yMinBound" :
_yMinBound = pParameters[prop];
break;
// the "floor" of the emitter
case "yMaxBound" :
_yMaxBound = pParameters[prop];
break;
// if true, the particle will fade away in proportion
// to the particles age and life
case "fade" :
_fade = pParameters[prop];
break;
// if true, the particle will shrink away in proportion
// to the particles age and life
case "shrink" :
_shrink = pParameters[prop];
break;
// the amount of velocity retained when a particle
// hits a wall, ceiling or floor
case "restitution" :
_restitution = pParameters[prop];
break;
// the initial direction of a particle in degrees or
// "random" string
case "direction" :
// check for random string
if (pParameters[prop] == "random") {
_randomDirection = true;
} else {
if (typeof(pParameters[prop]) == "number") _direction = pParameters[prop];
}
break;
}
}
}
//--------------------------------------
// GET / SET
//--------------------------------------
/**
* @private
*/
public function get numParticles():Number
{
return _particles.length;
}
//--------------------------------------
// PUBLIC METHODS
//--------------------------------------
/**
* Starts the emitter.
*/
public function start():Void
{
_emitter.onEnterFrame = Delegate.create(this, update);
}
/**
* Stops the emitter.
*/
public function stop():Void
{
delete _emitter.onEnterFrame;
}
//--------------------------------------
// PRIVATE & PROTECTED INSTANCE METHODS
//--------------------------------------
/**
* Step update method.
*/
private function update():Void
{
// update all particles
var i:Number = _particles.length;
while (i--) {
_particles[i].update();
}
// create particles specified in birth rate
var particle:MovieClip;
var d:Number;
var startX:Number;
var startY:Number;
var scale:Number;
i = _birthRate;
while (i--) {
d = (_randomDirection) ? Math.round(Math.random() * 360) : _direction;
startX = _xMin + Math.round(Math.random() * (_xMax - _xMin));
startY = _yMin + Math.round(Math.random() * (_yMax - _yMin));
if (_scaleMin != undefined && _scaleMax != undefined) {
scale = _scaleMin + Math.round(Math.random() * (_scaleMax - _scaleMin));
} else {
scale = _scale;
}
// resuse dead particles first
if (_particles[_oldestIndex].dead) {
particle = _particles[_oldestIndex];
particle.revive();
particle._x = 0;
particle._y = 0;
if (_oldestIndex + 1 < _particles.length) {
_oldestIndex++;
} else {
_oldestIndex = 0;
}
} else {
// create a new particle
particle = _emitter.attachMovie(_particleID, _particleID + _particles.length, _emitter.getNextHighestDepth(), {direction:d, speed:_speed, scale:scale});
_particles.push(particle);
}
// set particle properties
particle.life = _life;
particle._x = startX;
particle._y = startY;
particle.xAccel = _xAccel;
particle.yAccel = _yAccel + _gravity;
particle.fade = _fade;
particle.shrink = _shrink;
particle.xMinBound = _xMinBound;
particle.xMaxBound = _xMaxBound;
particle.yMinBound = _yMinBound;
particle.yMaxBound = _yMaxBound;
particle.restitution = _restitution;
}
}
}
First add the particle system package to the class path in the Publish Settings or ActionScript 2.0 preferences. Create a particle MovieClip in Flash and point its linkage class to the Particle class location.

Create an empty MovieClip that will serve as the particle container where all particles instances will be created. Create an instance of the Emitter and assign the empty container MovieClip as the first argument and the linkage identifier of the particle symbol as the second argument. Don’t forget to import the particle system package (coined speck). Use the start and stop methods of the Emitter object to control the system.
import com.jasonwoan.speck.*; var e:Emitter = new Emitter(emitterMC, "FireParticle"); e.start();
This is the most basic usage of this particle system. To customize the default emitter, you’ll want to add an object of parameters as a third argument when instantiating an Emitter. Please refer to the comments in the Emitter class for a description of valid parameters and their effects. Try messing around with the sample usage at the beginning of the post.
This is by no means a robust particle system. Ideally, I’d like to create a ParticleSystem object that would manage any number of Emitters and apply global forces like gravity and surface friction. I would also add a cue parameter so an Emitter can be tweened. I’ll save most of those updates for the AS3 version due to performance issues with AS2. This particle system library is lightweight and will probably serve well for simple particle effects.
I came across the following “gotcha” during development:
In ActionScript 2.0, an array used as an instance property should be initialized inside the constructor or any subsequent method and NOT when declaring the instance variable. When you initialize and declare and array instance property the value seems to be treated globally and multiple instances of that object will share the value even though it is access modifier is set to private. For example:
Bad
class MyClass{
private var myArray:Array = [];
public function MyClass() {
}
}
Good
class MyClass{
private var myArray;
public function MyClass() {
myArray = [];
}
}
I’ll post the AS3 version as soon as I finish it. Thanks for reading and comments/questions/suggestions/recommendations are appreciated.