AdobeMAXがなんかもりあがってますね。英語をしゃべれるようになるはずがもう10月…。

英語そっちのけで作った某サンプルの解説します。

ケサランパサランみたいなものの正体は基本的にはパーティクルです。

パーティクルの軌跡を残して毛みたいに見えるようにしています。

こういうときはとりあえずデータの塊をクラスにするといいです。

class Particle {
    public var x:Number = 0;
    public var y:Number = 0;
    public var vx:Number = 0;
    public var vy:Number = 0;
    public function Particle( x:Number=0, y:Number=0, vx:Number=0, vy:Number=0 ) {
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
    }
}

そうしたら次はパーティクルのグループをつくります。これももうたくさん作る予感がするのであらかじめクラスにしちゃいます。xとyをもっててそこから生えるイメージ。

class FurBall {
    public var x:Number = 0;
    public var y:Number = 0;
    private var _particles:Vector.<Particle> = new Vector.<Particle>();
    public function FarBall( volume:int = 300, radius:int = 60 ){
        const RADIAN :Number = 2*Math.PI;
        for( var i:int=0; i<volume; ++i ) {
            var angle:Number= Math.random()*RADIAN;
            var p:Particle = new Particle();
            p.x  = 0;
            p.y  = 0;
            p.vx = Math.cos(angle);
            p.vy = Math.sin(angle);
            p.life = Math.random()*20 + radius;
            _particles.push(p);
        }
    }
}

こんなかんじでボリュームと半径を入れると勝手に毛パーティクルが生成されます。このままだとなにも起こらないのでこれをENTER_FRAMEイベントで動かしていきます。volumeとかの変数名はFur以外のものであれば適宜しっくりくる名前にしてください。

package {
    import flash.display.*;
    import flash.events.*;

    public class FlashTest extends Sprite {
        private var _canvas:BitmapData;
        private var _fur:FurBall;
        public function FlashTest() {
            _canvas = new BitmapData( 465, 465, true, 0x00 );
            
            var bm:Bitmap;
            addChild( bm = new Bitmap(_canvas) );
            bm.smoothing = true;
            
            addEventListener( Event.ENTER_FRAME, enterframe ); 
        
            _fur = new FurBall(Math.random()*60+200, Math.random()*90+10);
            _fur.x = stage.stageWidth /2;
            _fur.y = stage.stageHeight /2;
        }
        private function enterframe(e:Event):void{
            _canvas.lock();
            _canvas.unlock();
        }
    }
}
class FurBall {
    //...省略
    public function draw( canvas:BitmapData ) :void {
            for each( var p:Particle in _particles ) {
                if( p.life > 0 ) {
                     p.x += p.vx;
                     p.y += p.vy;
                     canvas.setPixel32( p.x, p.y, 0xFFFFFF );
                     p.life--;
                }
            }    
    }
}

まずメインクラスでは描画用のBitmapDataを用意しておきます。ENTER_FRAMEイベントでちょっとずつ毛を描画します。

毛の伸びかたと描画はFurBallクラスに描いておきます。

計算は簡単に速度(vx, vy)を位置(x, y)にちょっとずつ加算していきます。またlifeという毛の寿命を減らして、0になったらもう伸びなくする(描画も計算もしない)ようにします。このへんはパーティクルの基本ですね。ここも生き物のシミュレートだったらLife、光や爆発だったらEnergyなど適宜読み替えれば応用できる概念ですね。

これでステージの中央に毛の塊ぽいものがかけると思います。

これだとふわふわ感がいまいちなのでさらに手をくわえます。毛のやわらかいうねりを出してみます。

var tx:int= p.x+Math.cos(p.life/6);
var ty:int= p.y+Math.sin(p.life/6);

ここでは回転をつかいます。新たにうねり用に変数をつくってもよいのですが、ここでは簡単にlifeを角度にみたてて、三角関数で微妙なうねりを出しています。Sin/Cosは-1~1を返すのでうねり具合を強めたければMath.sin(p.life/6)*2とかすればいいです。うねりのサイクルは6の数字を変えればいいです。数字が小さいほど細かくうねります。

パーティクルは動きが単調なときに、こういった周期性をもたせたり、ランダムな微動(ブラウン運動)やフラクタルノイズの合成など組み合わせて変化をつけることが多いですね。

最後に毛が硬そうなので毛先を細らせる処理をします。

    public function draw( canvas:BitmapData ) :void {
            for each( var p:Particle in _particles ) {
                if( p.life > 0 ) {
                     p.x += p.vx;
                     p.y += p.vy;
                     
                     var tx:int= p.x+Math.cos(p.life/6);
                     var ty:int= p.y+Math.sin(p.life/6);
                     
                     var a:uint =  canvas.getPixel32( tx, ty ) >> 24;
                     var ta:uint = Math.min((p.life/90)*0xff+ a,0xFF); 
                     var tc:uint = 0xFFFFFF | ta<<24;
                     
                     canvas.setPixel32( tx+x, ty+y, tc );
                     p.life--;
                }
            }    
    }

ちょっと入り組んでますが、 taのところに注目してください。ライフが減っていくほどアルファが小さくなります。ただし0xff(256)を超えると色が反転してしまうのでMath.minで制限します。

このアルファ値を色(0xFFFFFF)にビット演算で合成して最終的な色をつくりcanvasに書き込みます。

これでのびればのびるほど毛が細く(薄く)なる毛ができました。

なお、前後の処理は書き込み先の色を上書きしてしまうので、うまく合成できるように元の透明度を加算してます。これできれいにアルファブレンド処理がされたように見えます。(本当はRGB各色でやったほうがいいですがこの例は白黒なので)

あとはせっかくクラス化してあるし複数作ってもいけるようにFurBallを配列に入れて複数描画するようにしたり、クリックしたポイントにFurBallの座標をセットしたりする感じです。

ENTER_FRAMEで描画しないで、あらかじめforで個別のBitmapDataに転写しておくほうが実用的かもしれないですね。

HTML5飯