package { import caurina.transitions.Tweener; import com.bit101.components.CheckBox; import com.bit101.components.HSlider; import com.bit101.components.Label; import com.bit101.components.RadioButton; import net.hires.debug.Stats import flash.display.Shape; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.utils.Timer; /** * AS を始めたら必ず通るのが Particle だと勝手に思ってます。 * ということで挑戦してみました! * 偉大なる先人の方々を参考にさせて頂きました。ありがとうございました! */ [SWF(backgroundColor = 0xffffff, frameRate = 40, width = 465, height = 465)] public class ParticleTest extends Sprite { private var halfW:Number; private var halfH:Number; private var emitter:Emitter; private var emitters:/*Emitter*/Array = []; private var shapes:/*Shape*/Array; private var btns:/*RadioButton*/Array = []; private var current:int = 0; private var isAlpha:Boolean = true; private var num:int = 100; private var timer:Timer; private var labelHSlider:Label; public function ParticleTest() { init(); } private function init():void { halfW = stage.stageWidth * 0.5; halfH = stage.stageHeight * 0.5; // パーティクルの素材を4つ作成 var bubble:Shape = new Shape(); bubble.name = "Bubble"; bubble.graphics.beginFill(0x0DADFC); GraphicsUtil.donuts(bubble.graphics, 5, 10, 0, 360); bubble.graphics.endFill(); bubble.x = stage.stageWidth - bubble.width * 0.5 - 5; bubble.y = bubble.height * 0.5 + 5; this.addChild(bubble); var star:Shape = new Shape(); star.name = "Star"; star.graphics.lineStyle(1, 0xFCC00D); star.graphics.beginFill(0xFCE80D); GraphicsUtil.star(star.graphics, 6, 12); star.graphics.endFill(); star.x = stage.stageWidth + star.width * 0.5 + 5; star.y = star.height * 0.5 + 5; this.addChild(star); var fire:Shape = new Shape(); fire.name = "Fire"; GraphicsUtil.drop(fire.graphics, 20, function():void { fire.graphics.beginFill(0xFC350D); } ); fire.graphics.endFill(); fire.x = stage.stageWidth + fire.width * 0.5 + 5; fire.y = fire.height * 0.5 + 5; this.addChild(fire); var snow:Shape = new Shape(); snow.name = "Snow"; snow.graphics.beginFill(0x0DE8FC); GraphicsUtil.star(snow.graphics, 6, 12, 6); snow.graphics.endFill(); snow.x = stage.stageWidth + snow.width * 0.5 + 5; snow.y = snow.height * 0.5 + 5; this.addChild(snow); shapes = [bubble, star, fire, snow]; var stats:Stats = new Stats(); this.addChild(stats); // パーティクルを表示する ParticleField var field:ParticleField = new ParticleField(stage.stageWidth, stage.stageHeight, 0x00000000); this.addChildAt(field, 0); var n:int = shapes.length; for (var i: int = 0; i < n; i++) { // 表示場所と各素材を持った Emitter emitters[i] = new Emitter(field, shapes[i]); // 表示しているパーティクルがなくなった時 emitters[i].addEventListener(Event.COMPLETE , complete); if (i == 0) { var h:int = stats.height + 10; }else { h = btns[i - 1].y + btns[i - 1].height + 10; } btns[i] = new RadioButton(this, 5, h, shapes[i].name, false, onRadioButtonClick); btns[i].name = i; } var checkBox:CheckBox = new CheckBox(this, 5, btns[btns.length-1].y + btns[btns.length-1].height + 10, "Alpha " + isAlpha, onCheckBoxClick); checkBox.selected = true; var hSlider:HSlider = new HSlider(this, 5, checkBox.y + checkBox.height + 10); hSlider.setSize(60, 10); hSlider.setSliderParams(num, 800, num); hSlider.addEventListener(Event.CHANGE, onHSliderChange); labelHSlider = new Label(this, 5, hSlider.y + hSlider.height + 2); labelHSlider.text = "Particle's " + hSlider.value; // 初期 emitter = emitters[0]; btns[0].selected = true; // 100ms 毎に Emitter から パーティクルを作る timer = new Timer(100); timer.addEventListener(TimerEvent.TIMER , createParticle); timer.start(); // Emitter を更新して、描画する this.addEventListener(Event.ENTER_FRAME , update); } private function createParticle(e:TimerEvent):void { var mx:Number = mouseX; var my:Number = mouseY; var dmx:Number = (mx - (halfW)) / halfW; var dmy:Number = (my - (halfH)) / halfH; // BitmapData.colorTransform() で 3倍弱遅くなる if (isAlpha) { var setAlpha:Function = function ():Number { return Math.random() * 0.4 + 0.6 }; var vAlpha:Number = -0.002; }else { setAlpha = function ():int { return 1 }; vAlpha = 0; } // 個数 var n:int = num; while (n--) { // Emitter から パーティクルを作る var particle:Particle = emitter.generate(); // 個々の動きの設定 particle.ax = - dmx; particle.ay = - dmy; //particle.life = 40; particle.alpha = setAlpha(); particle.vAlpha = vAlpha; //particle.friction = 0.95; particle.vx = Math.random() * 16 - 8; particle.vy = Math.random() * 16 - 8; particle.x = mx + Math.random() * 32 - 16; particle.y = my + Math.random() * 32 - 16; } } private function update(e:Event):void { // Emitter を更新して、描画する emitter.update(); } private function complete(e:Event):void { Tweener.addTween(shapes[current], { x:stage.stageWidth - shapes[current].width * 0.5 - 5, time:1, transition:"easeOutExpo" } ); emitter = emitters[current]; timer.start(); } private function onRadioButtonClick(e:MouseEvent):void { timer.stop(); Tweener.addTween(shapes[current], { x:stage.stageWidth + shapes[current].width * 0.5 + 5, time:1, transition:"easeOutExpo" } ); current = int(e.currentTarget.name); } private function onCheckBoxClick(e:MouseEvent):void { timer.stop(); isAlpha = !isAlpha; e.currentTarget.label = "Alpha " + isAlpha; } private function onHSliderChange(e:Event):void { var n:int = e.currentTarget.value >> 0; labelHSlider.text = "Particle's " + String(n); num = n; } } } import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Sprite; import flash.events.Event; /** * パーティクルを描画する土台 */ class ParticleField extends Sprite { private var _canvas:BitmapData; private var w:int; private var h:int; private var _bgColor:uint; /** * コンストラクタ * @param w 幅 * @param h 高さ * @param _bgColor 背景色 */ public function ParticleField(w:int, h:int, _bgColor:uint) { this.w = w; this.h = h; this._bgColor = bgColor; init(); this.addEventListener(Event.ADDED_TO_STAGE , setStage); } private function setStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, setStage); // パーティクルクラスの初期化 Particle.init(stage); } private function init():void { canvas = new BitmapData(w, h, true, _bgColor); var canvasBitMap:Bitmap = new Bitmap(canvas); canvasBitMap.smoothing = true; this.addChild(canvasBitMap); } /** * パーティクルを描画している BitmapData */ public function get canvas():BitmapData { return _canvas; } public function set canvas(value:BitmapData):void { _canvas = value; } /** * 背景色 */ public function get bgColor():uint { return _bgColor; } } import flash.display.BitmapData; import flash.display.IBitmapDrawable; import flash.events.Event; import flash.events.EventDispatcher; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; /** * パーティクル、素材とその土台を管理するクラス */ class Emitter extends EventDispatcher { // BitmapData に draw できるもの private var material:IBitmapDrawable; private var bmd:BitmapData; private var scalable:Boolean; private var num:int = 5; private var bmds:/*BitmapData*/Array = []; private var particles:/*Particle*/Array = []; private var field:ParticleField; private var point:Point; private var canvas:BitmapData; private var rect:Rectangle; private var bgColor:uint; /** * コンストラクタ * @param field パーティクルを表示する土台 * @param material パーティクルの素材 * @param scalable パーティクルのスケールを有効化するか */ public function Emitter(field:ParticleField, material:IBitmapDrawable, scalable:Boolean = true) { this.field = field; this.material = material; this.scalable = scalable; init(); } private function init():void { // あらかじめ、スケールが 0.2~1 までの大きさのコピーを作っておく。 // Particle に scale が無いので、これで代替。 // 傾きを今後どうするか。 if (scalable) { for (var i: int = 0; i < num; i++) { var w:Number = material["width"] / num * (i + 1); var h:Number = material["height"] / num * (i + 1); bmds[i] = new BitmapData(w, h, true, 0x00000000); var mtx:Matrix = new Matrix(); mtx.scale(1 / num * (i + 1), 1 / num * (i + 1)); mtx.translate(w * 0.5, h * 0.5); bmds[i].draw(material, mtx); } }else { w = material["width"]; h = material["height"]; bmd = new BitmapData(w, h, true, 0x00000000); mtx = new Matrix(); mtx.translate(w * 0.5, h * 0.5); bmd.draw(material, mtx); } // update で使うものをあらかじめ定義してみる。 this.point = new Point(); this.canvas = field.canvas; this.rect = canvas.rect; this.bgColor = field.bgColor; } /** * パーティクルを作成する * @return Particle パーティクル */ public function generate():Particle { // スケールが有効ならば配列から。そうでなければ、単体で。 if (scalable) { // >> 0 は、int() キャストと同じ。 // >> 0 の前を()で囲むと、ちょっと早くなった気がする。気のせいかな。 var particle:Particle = new Particle(bmds[(Math.random() * num) >> 0]); }else { particle = new Particle(bmd); } particles.push(particle); return particle; } /** * Emitter を更新して、描画する */ public function update():void { var i:int = particles.length; if (i <= 0) { // パーティクルがなくなった時 dispatchEvent(new Event(Event.COMPLETE)); } canvas.lock(); // fillRect より早い方法は?? canvas.fillRect(rect, bgColor); while (i--) { // ローカル変数で持ったほうがデータ型が指定できて早い? var particle:Particle = particles[i]; var pbmd:BitmapData = particle.bmd; // 1個のパーティクルの更新と、生存しているか。 var life:Boolean = particle.update(); if (life) { // 描画 point.x = particle.x; point.y = particle.y; field.canvas.copyPixels(pbmd, pbmd.rect, point, null, null, true); }else { // 削除 particles.splice( i, 1 ); } } canvas.unlock(); } } import flash.display.BitmapData; import flash.display.Stage; import flash.geom.ColorTransform; import flash.geom.Rectangle; /** * パーティクルの性質を保持する */ class Particle { // rote と scale がない。 private var _ax:Number = 0; private var _ay:Number = 0; private var _vx:Number = 0; private var _vy:Number = 0; private var _friction:Number = 1; private var _x:Number = 0; private var _y:Number = 0; private var _vAlpha:Number = 0; private var _alpha:Number = 1; private var colorTransFrom:ColorTransform; private var _life:int = 40; private var _isLife:Boolean = true; private var _bmd:BitmapData; private var rect:Rectangle; private static var sw:int; private static var sh:int; /** * コンストラクタ。Emitter から生成される。 * @param _bmd 実際に描画する BitmapData */ public function Particle(_bmd:BitmapData) { this._bmd = _bmd.clone(); sets(); } /** * 初期化。パーティクルがステージ内にあるかを調べるために Stage が必要。 * ParticleField が呼び出す。 * @param s ステージ */ public static function init(s:Stage):void { sw = s.stageWidth; sh = s.stageHeight; } private function sets():void { colorTransFrom = new ColorTransform(); rect = _bmd.rect; } /** * パーティクルの状態を更新。Emitter が更新する。 * @return Boolean パーティクルが生存しているか */ public function update():Boolean { if (_isLife) { // ステージの範囲とalpha if ((_x < 0 || _x > sw) || (_y < 0 || _y > sh) || _alpha <= 0) { remove(); return _isLife; } _life--; if (_life <= 0) { remove(); return _isLife; } _x += _vx; _y += _vy; _vx += _ax; _vy += _ay; _vx *= _friction; _vy *= _friction; // 配置座標を整数化 → 1.25倍くらい _x = _x >> 0; _y = _y >> 0; // alpha を BitmapData に適用 if (_vAlpha != 0) { _alpha += _vAlpha; colorTransFrom.alphaMultiplier = _alpha; // これが重い!! _bmd.colorTransform(rect, colorTransFrom); } } return _isLife; } private function remove():void { _life = 0; _isLife = !_isLife; // 念のため _bmd.dispose(); _bmd = null; } /** * X軸方向への加速度 */ public function get ax():Number { return _ax; } public function set ax(value:Number):void { _ax = value; } /** * Y軸方向への加速度 */ public function get ay():Number { return _ay; } public function set ay(value:Number):void { _ay = value; } /** * X軸方向への速度 */ public function get vx():Number { return _vx; } public function set vx(value:Number):void { _vx = value; } /** * Y軸方向への速度 */ public function get vy():Number { return _vy; } public function set vy(value:Number):void { _vy = value; } /** * X軸上の座標 */ public function get x():Number { return _x; } public function set x(value:Number):void { _x = value >> 0; } /** * Y軸上の座標 */ public function get y():Number { return _y; } public function set y(value:Number):void { _y = value >> 0; } /** * 透過度の変化値 */ public function get vAlpha():Number { return _vAlpha; } public function set vAlpha(value:Number):void { _vAlpha = value; } /** * 透過度 */ public function get alpha():Number { return _alpha; } public function set alpha(value:Number):void { _alpha = value; if (_alpha < 1) { colorTransFrom.alphaMultiplier = _alpha; _bmd.colorTransform(_bmd.rect, colorTransFrom); } } /** * 抵抗 */ public function get friction():Number { return _friction; } public function set friction(value:Number):void { _friction = value; } /** * 生存値 */ public function get life():int { return _life; } public function set life(value:int):void { _life = value; } /** * 生存しているかどうか */ public function get isLife():Boolean { return _isLife; } /** * 実際に描画する BitmapData */ public function get bmd():BitmapData { return _bmd; } } import flash.display.Graphics; import flash.display.Shape; import flash.geom.Point; /** * 既存にない Graphics を描画するクラスです。Graphics を返します。 */ class GraphicsUtil { static private var PI:Number = Math.PI; static private var DEGTORAD:Number = Math.PI / 180; /** * ドーナツ形 * @param g Graphics ターゲット * @param sr Number 描画開始半径 * @param er Number 描画終了半径 * @param sa Number 描画開始角度 * @param ea Number 描画終了角度 * @return Graphics 生成したドーナツ形 */ public static function donuts(g:Graphics, sr:Number, er:Number, sa:Number, ea:Number):Graphics { var sap:Point = Point.polar(sr, sa * DEGTORAD); var eap:Point = Point.polar(er, ea * DEGTORAD); var ap:Point; var cp:Point; g.moveTo(sap.x, sap.y); for (var i:int = sa; i <= ea; i++) { ap = Point.polar(sr, i * DEGTORAD); cp = Point.polar(sr, (i - 0.5) * DEGTORAD); g.curveTo(cp.x, cp.y, ap.x, ap.y); } g.lineTo(eap.x, eap.y); for (i = ea; i >= sa; i--) { ap = Point.polar(er, i * DEGTORAD); cp = Point.polar(er, (i + 0.5) * DEGTORAD); g.curveTo(cp.x, cp.y, ap.x, ap.y); } return g; } /** * 星形 * @param g Graphics ターゲット * @param inR 内側の半径 * @param outR 外側の半径 * @param vertex 頂点の数。5点以上 * @return Graphics 生成した星形 */ public static function star(g:Graphics, inR:Number, outR:Number, vertex:int = 5):Graphics { var points:/*Number*/Array = []; var rad:Number = PI * 2 / vertex; for (var i: int = 0; i < vertex; i++) { var outTheta:Number = (i * rad) - PI * 0.5; var inTheta:Number = outTheta + (rad * 0.5); points[i << 2] = outR * Math.cos(outTheta); points[(i << 2) + 1] = outR * Math.sin(outTheta); points[(i << 2) + 2] = inR * Math.cos(inTheta); points[(i << 2) + 3] = inR * Math.sin(inTheta); } g.moveTo(points[0], points[1]); var n:int = points.length >> 1; for (var j: int = 1; j < n; j++) { g.lineTo(points[j << 1], points[(j << 1) + 1]); } g.lineTo(points[0], points[1]); return g; } /** * 雫形 * @param g Graphics ターゲット * @param r 半径 * @param fill 塗りを実行する関数 * @return Graphics 生成した雫形 */ public static function drop(g:Graphics, r:int, fill:Function):Graphics { for (var i: int = 0; i < r; i++) { fill(); g.drawCircle(0, (r >> 2) - i, (r - i) / 3); } return g; } } Particle Test