Beautifl - Flash Gallery

Thumbnail : Particle Test
Particle Test
kkeisuke 2010-01-26 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

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;
	}
	
}