Particle Test
AS を始めたら必ず通るのが Particle だと勝手に思ってます。
ということで挑戦してみました!
偉大なる先人の方々を参考にさせて頂きました。ありがとうございました!
/**
* Copyright kkeisuke ( http://wonderfl.net/user/kkeisuke )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/H33c
*/
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;
}
}