The workhorse of my particle effects is the BitmapSprite class:
package com.robotacid.gfx {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* Provides a less cpu intensive version of a Sprite
* Ideal for particles, but not for complex animated characters or large animations
* Also operates as a super class to BitmapClip
*
* @author Aaron Steed, robotacid.com
*/
public class BitmapSprite {
public var x:int, y:int, width:int, height:int;
public var dx:int, dy:int;
public var rect:Rectangle;
public var data:BitmapData;
public static var p:Point = new Point();
public static var mp:Point = new Point();
public static var bounds:Rectangle;
public function BitmapSprite(mc:DisplayObject = null) {
x = y = 0;
if(mc != null){
bounds = mc.getBounds(mc);
data = new BitmapData(Math.ceil(bounds.width), Math.ceil(bounds.height), true, 0x00000000);
data.draw(mc, new Matrix(1, 0, 0, 1, -bounds.left, -bounds.top));
width = bounds.width;
height = bounds.height;
dx = bounds.left;
dy = bounds.top;
rect = new Rectangle(0, 0, Math.ceil(bounds.width), Math.ceil(bounds.height));
}
}
public function render(destination:BitmapData, frame:int = 0):void{
p.x = x + dx;
p.y = y + dy;
destination.copyPixels(data, rect, p, null, null, true);
}
public function init(data:BitmapData, dx:int = 0, dy:int = 0):void{
this.data = data;
this.width = data.width;
this.height = data.height;
rect = new Rectangle(0, 0, data.width, data.height);
this.dx = dx;
this.dy = dy;
}
/* Given a plane of multiple bitmaps that have been tiled together, calculate which bitmap(s) this
* should appear on and render to as many as required to compensate for tiling
*
* bitmaps is a 2d Vector of tiled bitmapdatas
*/
public function multiRender(bitmaps:Vector.<Vector.<Bitmap>>, scale:int = 2880, frame:int = 0):void{
var inv_scale:Number = 1.0 / scale;
var h:int = bitmaps.length;
var w:int = bitmaps[0].length;
// take point position
p.x = x + dx;
p.y = y + dy;
// find bitmap boundaries in tiles
var left_tile_x:int = (p.x * inv_scale) >> 0;
var top_tile_y:int = (p.y * inv_scale) >> 0;
var right_tile_x:int = ((p.x + width) * inv_scale) >> 0;
var bottom_tile_y:int = ((p.y + height) * inv_scale) >> 0;
// logically the bitmap will only be painted onto 1, 2 or 4 tiles, we can use conditionals for this
// to speed things up
// Of course with the option of scale, this could mean painting to many more bitmaps, and such a
// task can fuck right off for the time being
// only one tile to paint to
if(left_tile_x == right_tile_x && top_tile_y == bottom_tile_y){
if(left_tile_x > -1 && left_tile_x < w && top_tile_y > -1 && top_tile_y < h){
mp.x = p.x - (scale * left_tile_x);
mp.y = p.y - (scale * top_tile_y);
bitmaps[top_tile_y][left_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
}
// two tiles to paint to
else if(left_tile_x == right_tile_x && top_tile_y != bottom_tile_y){
if(left_tile_x > -1 && left_tile_x < w && top_tile_y > -1 && top_tile_y < h){
mp.x = p.x - (scale * left_tile_x);
mp.y = p.y - (scale * top_tile_y);
bitmaps[top_tile_y][left_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
if(left_tile_x > -1 && left_tile_x < w && bottom_tile_y > -1 && bottom_tile_y < h){
mp.x = p.x - (scale * left_tile_x);
mp.y = p.y - (scale * bottom_tile_y);
bitmaps[bottom_tile_y][left_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
} else if(left_tile_x != right_tile_x && top_tile_y == bottom_tile_y){
if(left_tile_x > -1 && left_tile_x < w && top_tile_y > -1 && top_tile_y < h){
mp.x = p.x - (scale * left_tile_x);
mp.y = p.y - (scale * top_tile_y);
bitmaps[top_tile_y][left_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
if(right_tile_x > -1 && right_tile_x < w && top_tile_y > -1 && top_tile_y < h){
mp.x = p.x - (scale * right_tile_x);
mp.y = p.y - (scale * top_tile_y);
bitmaps[top_tile_y][right_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
}
// four tiles to paint to
else if(left_tile_x != right_tile_x && top_tile_y != bottom_tile_y){
if(left_tile_x > -1 && left_tile_x < w && top_tile_y > -1 && top_tile_y < h){
mp.x = p.x - (scale * left_tile_x);
mp.y = p.y - (scale * top_tile_y);
bitmaps[top_tile_y][left_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
if(right_tile_x > -1 && right_tile_x < w && top_tile_y > -1 && top_tile_y < h){
mp.x = p.x - (scale * right_tile_x);
mp.y = p.y - (scale * top_tile_y);
bitmaps[top_tile_y][right_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
if(left_tile_x > -1 && left_tile_x < w && bottom_tile_y > -1 && bottom_tile_y < h){
mp.x = p.x - (scale * left_tile_x);
mp.y = p.y - (scale * bottom_tile_y);
bitmaps[bottom_tile_y][left_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
if(right_tile_x > -1 && right_tile_x < w && bottom_tile_y > -1 && bottom_tile_y < h){
mp.x = p.x - (scale * right_tile_x);
mp.y = p.y - (scale * bottom_tile_y);
bitmaps[bottom_tile_y][right_tile_x].bitmapData.copyPixels(data, rect, mp, null, null, true);
}
}
}
/* Creates an array of bitmaps to render to stitched together to compensate for the minimum bitmap size
*
* holder is the Sprite that will stand as parent to all these bitmaps
*/
public static function createMultiRenderArray(width:int, height:int, holder:Sprite, scale:int = 2880):Vector.<Vector.<Bitmap>>{
var w:int = Math.ceil(width / scale);
var h:int = Math.ceil(height / scale);
var bitmaps:Vector.<Vector.<Bitmap>> = new Vector.<Vector.<Bitmap>>(h, true);
var r:int, c:int;
var bitmapdata:BitmapData, bitmap:Bitmap;
for(r = 0; r < height; r += scale){
width = scale;
bitmaps[(r / scale) >> 0] = new Vector.<Bitmap>(w, true);
for(c = 0; c < width; c += scale){
if(c + width > width) width = width - c;
if(r + height > height) height = height - r;
bitmapdata = new BitmapData(width, height, true, 0x00000000);
bitmap = new Bitmap(bitmapdata);
bitmap.x = c;
bitmap.y = r;
bitmaps[(r / scale) >> 0][(c / scale) >> 0] = bitmap;
holder.addChild(bitmap);
}
}
return bitmaps;
}
}
}
I basically have a bitmap that is tracking the view port that I clean each frame with fillRect and then render all the particles to. This method allows upto 800 particles before the chug sets in.
The particles then use verlet integration to fly about and raycast for collision with the walls.
When the particles hit a wall and resolve, the multiRender function is called. Because maps will be bigger than the maximum bitmap size in flash (2880 x 2800 pixels), I use createMultiRenderArray to make a massive bitmap out of smaller bitmaps tiled together.
So I have a big transparent bitmap that sits over the entire level that I can paint anything onto. And that's what I paint the blood on to when it comes to a rest. Allowing me to destroy the particle and recover cpu usage.
I could effectively allow the player to tag walls if I wanted to, with no loss of performance.