今回はActionScript 2(以下、AS2)で花火をシミュレートしてみよう。AS2の知識以外にも多少の数学を前提にするので、ちょっと取っ付きにくい方がいるかもしれないが、あくまで「考え方」を理解してもらえるように説明していきたい。

花火とパーティクル

まず、花火は一般にパーティクルという技法を用いる。パーティクル演算とは簡単にいうと、ある環境の中での多数の粒子の動きを物理シミュレートするものだ。その粒子を煙や水などの不定形なものに見立てることで、いろいろな場面で応用が利く表現方法だ。花火の場合はそのまま粒子が火花になる。

演算などというと難しそうだが、処理の流れ自体は意外とシンプルだ。実際の花火の要素を簡単にあらわすと次の3つになる。

 

1. 爆発 →花火を生成する

 

2. 拡散 →動きを計算する

 

3. 発光 →花火らしい描画する

計算自体はMCなどは使わずに座標データのみで行うのでObjectクラスに粒子データとして生成し、配列に格納してゆく

/* [初期化] */
// あらかじめ計算用のオブジェクトをたくさん生成する
var particleList:Array = new Array(); //花火ひとつ分のパーティクル粒子の集まり
var count:Number = 100; // 花火ひとつあたりの粒子の数
for( var i:Number=0; i < count; ++i ) {
var particle:Object = new Object(); // 粒子1つ分の情報
particleList.push( particle );
}
// 毎フレームの処理
onEnterFrame = function () {
/* [計算] */
for( var i:Number=0; i < count; ++i ) {
var particle:Object = particleList[i];
// 個別のパーティクルの計算処理
}
/* [描画] */
}

火花の特性を考える

花火の火花は発火してからしばらく発光し、やがて消えてゆく。運動の速度などにかかわらず光り続けるということは、爆発のエネルギーが空気中で消えていくイメージなので、「持続エネルギー」という概念を捉えるとよさそうだ。また持続性は時間とともに減衰していくので、これを時間ごとに計算することで表現できる。

このように最終的にエネルギーが一定量を下回ったらその火花を消す。逆にいえば持続エネルギーが尽きるまで物理演算処理などが行われることになる

/* [初期化] */
particle.energy = 10; // 初期エネルギーを設定する
/* [計算] */
//毎フレームの処理
particle.energy *= 0.95; // ちょっとずつエネルギー減衰させる
//計算をやめるとき
if( particle.energy < 0.1 ) continue;

加速度と重力の表現

花火は基本的に、中心より八方に飛び散るので、空気中の火花の動きは爆発したときの方向(角度)と勢い(速度)で決まる。ひとまず座標(100, 100)から5の勢いで、ランダムな方向に初速を与える。毎フレーム、現在位置に速度を足すことで火花の動きが表現できる。

/* [初期化] */
// 粒子の初期値を設定する
var angle:Number = 360*Math.random(); //角度
var velocity:Number = 5; //勢い
particle.x = 100;
particle.y = 100;
// 角度と初速からX,Yの速度成分を設定する
particle.vx = Math.cos( angle ) *velocity;
particle.vy = Math.sin( angle ) *velocity;
/* [計算] */
//毎フレームの処理
particle.x += particle.vx;
particle.y += particle.vy;

確認する

とりあえず何かを描画しないとわからないので、MovieClipの描画メソッドで点を表示する。

/* [初期化] */
// 表示用MC
var viewmc:MovieClip = this.createEmptyMovieClip("viewmc",0);
/* [描画] */
// 描画処理準備
this.viewmc.clear();
this.viewmc.lineStyle( 3, 0xFFFFFF );
// 各点の描画処理
this.viewmc.moveTo( particle.x, particle.y );
this.viewmc.lineTo( particle.x, particle.y );
this.viewmc.lineTo( particle.x+0.5, particle.y+0.5 );

画像をクリックするとサンプルが開く。このままでは何も影響がないので無機質な等速直線運動だ

火花はほとんど重さがないが、多少の重力や空気抵抗の影響も受けるので、それも計算する。力を合成する計算は基本的に全部足せばいいので、順に計算してゆこう

/* [計算] */
// 空気抵抗(動いている方向に割減)
particle.vx *= 0.95;
particle.vy *= 0.95;
// 重力(粒子の運動にかかわらず常に下に働く)
particle.vx += 0;
particle.vy += 0.03;

これでとりあえず花火らしい動きができているはずなので、画像をクリックしてサンプルを確認してみよう

計算と描画をわけて処理する

ここまで、持続エネルギー、速度、空気抵抗、重力などいくつもの処理がでてきた。いろいろな影響を計算する場合、あらかじめ合算してしまってから、後で見た目の反映をすることで、とても見やすくなり調整しやすくなる。また描画が1回ですむので負荷の軽減にもなる。

しかしよく考えると、1回の処理につき計算と描画のそれぞれで、同じ配列を複数回ループ処理するといった非効率な面もある。非効率ではあるが、あまり同時に処理を書くとデバッグや調整がしづらいので、ひとまず分けて作っておいて、後記する最適化の部分で無駄を省くようにするといいだろう。

蛇足だが、どのようなプログラムでも、初期化、状態処理、見た目の反映と切り分けて実装することで、一見冗長になるが、長い目で見て開発しやすいコードになることが多いと思う。稼動パフォーマンスとのトレードオフについても同様だ。

もっと花火らしさを工夫する

ひとまず輪状に花が咲いたが、これだと花火らしさにいまいち欠ける。もっとばらけた感じを出すためにランダムさを加える処理をしてゆこう。

粒子の初速をきめたとき、勢い(Velocity)を均一にしていたのをランダムに変更する。

//角度と初速からX,Y成分の速度を決定
var velocity:Number = 5*Math.random();
particle.vx = Math.cos( angle ) *velocity;
particle.vy = Math.sin( angle ) *velocity;

また、エネルギーが一緒だと粒子の消え際が同時になり不自然なので、持続エネルギーもややランダムにし、部分的に乱数をかけることで5以上10以内に設定する。

particle.energy = 5 + 5*Math.random();

これで確認してみるとどうだろう。少し花火らしさが出てきたのではないだろうか

火花の表現

最後に見た目の仕上げとして火花の表現の処理をしよう。MCのインスタンスを粒子ごとに生成すると、大変処理が重くなると予想されるので画面全体をビットマップ処理することで演出する。

先ほどとの変更点は、まず点描画用のMCとは別にビットマップ用にMCを生成し、そちらにビットマップを描画してゆく。描画と同時に半透明のビットマップを重ねることで以前の火花を少しずつ消しこんでゆき、余韻を表現する。次に赤い半透明のMCをオーバーレイで重ねて着色する。

//描画用の準備
this.createEmptyMovieClip( "drawmc", 0 ); //旧viewmc
this.createEmptyMovieClip( "viewmc", 1 );
var bm:BitmapData = new BitmapData( 300, 300, true, 0x00000000 ); //点描画用
var bm2:BitmapData = new BitmapData( 300, 300, true, 0x60000000 ); //余韻用
this.viewmc.attachBitmap( bm, 0 );
//色をつけるためのカラーマスク(オーバーレイ)
this.mask.swapDepths(10);
//ループ後、画面全体のダンプを描画する
bm.draw( this.bm2 );
bm.draw( this.drawmc );

これで一通り完成だ。画像をクリックしてサンプルを確認しよう。音を付けたらかなり花火らしくなるのではないだろうか

なるべくチューニングするために

1.余計な計算はしない

小さな計算処理でも一つ計算が増えると×粒子数分負荷が増える。少しでも効率よく高速に計算するようチューニングしよう。

2.ループ内での変数の宣言やリテラルの生成を減らす

変数の宣言やリテラル(変数じゃない文字列や数字)は、内部でメモリを確保する処理が入るため、大量にループすると負荷になる可能性がある。あらかじめ宣言した変数を使いまわす。ループ内で変化しない値はループ外で変数化しておくなどの工夫をすると高速化できる。

// 改善前
for( … ) {
particle.energy * = 0.95;//数値リテラル
}
// 改善後
var damp:Number = 0.95;//
for( … ) {
particle.energy * = damp;
}
3.ローカル変数を使う

クラスのプロパティやグローバル変数よりローカル変数の方が高速に処理できます。ループ内で何度もアクセスする部分は注意が必要だ。

4.ビットマップやMCの使用回数を抑える

FlashPlayerで一番負荷が高いのは、描画に関係する処理。見た目にこだわりつつもなるべく少ない処理でキレイに表現できるよう工夫しよう。今回の余韻用ビットマップのように1回生成すればいいものは、その都度生成しないで使いまわそう。

まとめ

最終的に最適化処理したものにクリックイベントで花火を適当に生成するようにしたソースを作成した。複数の花火を描画するところで、若干描画のタイミングなど調整してある。

サンプルを開いたら画面をクリックしてみよう。複数の花火が打ちあがる

サンプルのパラメータを変えると、さらにいろいろな花火を作ることもできるだろう。このサンプルは花火を目指したが、パラメータや描画処理を変えていけば、ちがった表現もできると思うので、試していただきたい。

最終的にはマニアックな部分にも触れたが、一度に難しいことをしないで、わかるところからちょっとずつ肉付けしていくプロセスが大切だ。複雑な部分は切り分けて解決していくとよいだろう。本稿を通して、案外簡単だなと思っていただけたら幸いだ。

提供:マイナビ転職エージェント

会員登録はこちら

「上流でWEBサイトの企画・プロデュースに携わりたい!」「待遇を良くしたい!」 「自分の経験・スキルではどうなのだろう…?」「忙しくて転職活動する時間がない!」 そんなあなたに…転職活動を総合的にサポートしてくれるプロを探してみませんか? 「マイナビ転職エージェント」は、人材紹介型転職情報サイト。 あなたの転職活動を成功に導くプロのコンサルタントがきっと見つかります! 非公開求人情報など、転職情報サイトでは掲載されていない求人情報も満載です!

毎日コミュニケーションズはプライバシーマークを取得しています。