一個前のエントリで出した、大量のパーティクルを描画ってヤツですが、眠くてやる気が出ないのでなんとなく解説しちゃいます。多分応用は効くはず。
まず大切なのは、こんなもの真面目に全部描画しないことです。たぶん、次のSWFを見ればその意味が分かると思います。
つまり、四分円の分だけパーティクル(ドット)を描画して、applyFilterでグローをかけて、あとはこの四分円を回転させながらコピーすることで円を作るのです。これだけで、一気にバーティクルの量が四倍に!!
というわけで、25,000描画していると言いましたが、実際には1/4の6,400ぐらい分の計算量しかかかっていないというわけです。節約節約。
俺が作ったヤツは手抜きしてるのでつなぎ目が不自然だけど、これを奇麗にすれば多分実用に耐えるはず。あなたも今日から四倍のパーティクルを出してみよう。
以下ソース
package
{
import flash.display.Sprite;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.geom.Point;
import flash.filters.BlurFilter;
import flash.filters.GlowFilter;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.utils.getTimer;
import flash.display.StageScaleMode;
// default-size 800 800
public class Particle extends Sprite
{
public function Particle()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.frameRate = 60;
_bitmap = new BitmapData(400, 400, true);
_master = new BitmapData(800, 800, false);
_matrix00 = new Matrix();
_matrix00.rotate(180 / 180 * Math.PI);
_matrix00.translate(400, 400);
_matrix01 = new Matrix();
_matrix01.rotate(90 / 180 * Math.PI);
_matrix01.translate(400, 400);
_matrix10 = new Matrix();
_matrix10.rotate(270 / 180 * Math.PI);
_matrix10.translate(400, 400);
_matrix11 = new Matrix();
_matrix11.translate(400, 400);
addChild(new Bitmap(_master));
_field = new TextField();
_field.textColor = 0xFFFFFF;
_field.autoSize = TextFieldAutoSize.LEFT;
_time = getTimer();
addChild(_field);
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
private var _bitmap:BitmapData;
private var _master:BitmapData;
private var _step:uint = 0;
private var _rad:Number = 0;
private var _matrix00:Matrix;
private var _matrix01:Matrix;
private var _matrix10:Matrix;
private var _matrix11:Matrix;
private var _zero:Point = new Point(0, 0);
private var _filter:GlowFilter = new GlowFilter(0xFFFFFF, 1.0, 8, 8, 5, 1.0);
private var _field:TextField;
private var _time:uint;
private function enterFrameHandler(event:Event):void
{
_step = (_step + 1) % 4;
_rad = (_rad + (0.2 / 180 * Math.PI));
if (_rad > (2 / 180 * Math.PI)) {
_rad -= (2 / 180 * Math.PI);
}
_master.lock();
var numParticles:uint = drawBitmap(_bitmap, _step, _rad);
drawMaster(_master, _bitmap);
_master.unlock();
var t:uint = getTimer();
_field.text = numParticles * 4 + ' particles / ' + Math.floor(1000 / (t - _time)) + ' fps';
_time = t;
}
private function drawBitmap(bitmap:BitmapData, step:Number, rad:Number):uint
{
var numParticles:uint = 0;
var rect:Rectangle = new Rectangle(0, 0, 2, 2);
bitmap.fillRect(bitmap.rect, 0x00000000);
for (var r:Number = rad; r <= (Math.PI / 2); r += (2 / 180 * Math.PI)) {
var cosR:Number = Math.cos(r);
var sinR:Number = Math.sin(r);
for (var s:Number = step; s <= 570; s += 4) {
++numParticles;
bitmap.setPixel32(cosR * s, sinR * s, 0xFFFFFFFF);
}
}
bitmap.applyFilter(bitmap, bitmap.rect, _zero, _filter);
return numParticles;
}
private function drawMaster(dest:BitmapData, source:BitmapData):void
{
dest.fillRect(dest.rect, 0x00000000);
dest.draw(source, _matrix00);
dest.draw(source, _matrix01);
dest.draw(source, _matrix10);
dest.draw(source, _matrix11);
}
}
}
を描画するってのをやってみてたら、多すぎてわけわからなくなりました。パーティクルっていうかドットだけど。
約25,000ドット + ブラー効果付きです。これって多い方なのかな?多いっていうならやり方解説する。
なんか技術系の話題は久々?
今日ご紹介するのはBitmapData.lock/unlockメソッドですが、皆さんご存知でしょうか。ドキュメントを見ると、
この BitmapData オブジェクトが変更されたときに、BitmapData オブジェクトを参照するすべてのオブジェクト (たとえば Bitmap オブジェクト) が更新されないように、イメージをロックします。パフォーマンスを向上させるには、setPixel() メソッドまたは setPixel32() メソッドを何度も呼び出す前後に、このメソッドを unlock() メソッドとともに使用してください。
と書いてあります。
多分ほとんどの人がBitmapDataを表示するために、addChild(new Bitmap(bitmapData))とかやってると思いますが、実はsetPixelやsetPixelsをすると、画面の表示を更新するために、Bitmap(BitmapDataではない)の更新処理も入ります。setPixelを何度も呼び出せばその分だけ更新処理が入ります。しかし、例えばENTER_FRAMEハンドラ内でsetPixelを何度も呼び出してBitmapDataを更新するときなど、この何度も更新処理が入る、というのは明らかに無駄です。ENTER_FRAME1回につき1回更新処理が入ればいいわけですから。
というわけで、この更新処理を制御するためのメソッドがlock/unlockです。
早速次のコードで速度比較をしてみましょう。
package
{
import flash.display.Sprite;
import flash.display.BitmapData;
import flash.utils.getTimer;
import flash.display.Bitmap;
public class LockUnlock extends Sprite
{
public function LockUnlock()
{
var bitmap:BitmapData = new BitmapData(800, 800);
addChild(new Bitmap(bitmap));
for (var i:uint = 0; i < 3; ++i) {
trace(drawBitmap(bitmap) + 'ms');
}
}
private function drawBitmap(bitmap:BitmapData):uint
{
var start:uint = getTimer();
var w:uint = bitmap.width;
var h:uint = bitmap.height;
for (var x:uint = 0; x < w; ++x) {
for (var y:uint = 0; y < h; ++y) {
bitmap.setPixel(x, y, 0xFFFFFF);
}
}
return getTimer() - start;
}
}
}
addChildした状態の800x800のBitmapDataに対して、全ピクセルに対してsetPixelをする時間を計測x3をやってみます。結果は次の通り。
224ms 208ms 203ms
次に、lock/unlock処理を加えてみます。
bitmap.lock();
for (var i:uint = 0; i < 3; ++i) {
trace(drawBitmap(bitmap) + 'ms');
}
bitmap.unlock();
結果は次の通り。
129ms 131ms 136ms
差は歴然ですのでお試しあれ。
