Beautifl - Flash Gallery

Thumbnail : パーティクル祭りの原点「Liquid10000」を初級者が解説してみる。
パーティクル祭りの原点「Liquid10000」を初級者が解説してみる。
Hiiragi 2009-08-27 All rights reserved

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

package {

	import flash.display.Stage;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display.Sprite;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.geom.ColorTransform;
	import flash.geom.Rectangle;
	import flash.geom.Point;
	import flash.events.Event;
	import flash.events.MouseEvent;

	[SWF(width=465, height=465, backgroundColor=0x000000, frameRate=30)];

	
	/*
	 * パーティクル祭りの原点「Liquid10000」を解説してみる。
	 * [冒頭のコメントに追記あり](Download後の閲覧推奨)
	 * 
	 * 前回のPerlinNoiseはこれの伏線だったのだよ!(偶然ですけど)
	 * 今回はPerlinNoiseの復習と、ビット演算子の勉強になります。
	 * 
	 * 前々回
	 * http://wonderfl.kayac.com/code/03190641c00b157abc0dbe7d6a513fa80ec987db
	 * 前回
	 * http://wonderfl.kayac.com/code/cafcb78df40b35b0c0d571dc8fd8f1bda791e4b4
	 * 
	 * フォースマップっていう考え方らしいですが、
	 * PerlinNoiseの色を速度に変換するらしいです。
	 * てっきり難しい物理演算を使ってると思ってたよ・・・。
	 * 
	 * あと、前々回にやった花火の処理も加わってますので、
	 * 前々回・前回の内容を知ってると、にやけることができると思います。
	 * 
	 * これが分かると、あのsnowとかの作り方もわかるかな・・・?
	 * 次回はそれにするかもしれません。しないかもしれません。
	 * 
	 * パーティクル祭りに関してはclockmaker様のまとめが詳しいです。
	 * この記事を元に、自身で高速化を試してみるのもありだと思います。
	 * http://clockmaker.jp/blog/2009/04/particle/
	 * http://clockmaker.jp/blog/2009/04/particle_fes/
	 * 
	 * Fork元を製作された方へ。
	 * ・Forkを拒否されたい場合は、何かしらのアクションでお伝えください。
	 * 	削除いたします。(できれば生暖かく見守っていただけると嬉しいです)
	 * 
	 * 中級者・上級者の方へ。
	 * ・何かアドバイスや校正箇所があれば、ご指摘お願いします。
	 * 
	 * 初心者・初級者の方へ。
	 * ・一緒に勉強しましょう。何か質問があればどうぞ。
	 *  自分も初級者なので、答えられるか分かりませんが。(笑
	 * 
	 * [追記]
	 * 朝起きて見てみたらFavoriteの多さにびっくりして目が覚めました。
	 * あくまで解説のみで、自分の作品ではないということで恐縮ではありますが、
	 * こういう試みが支持されてるということは、自分の考えは間違ってなかったんだなと、
	 * 確信している次第です。
	 * 
	 * そしてこういう解説ができるのも、勉強したいと思わせられる素敵な作品たちが
	 * このwonderflに溢れているからだと思います。製作者の皆さんに多謝。
	 * 
	 * これからもマイペースでやっていきたいと思いますが、あまりに高度なものは
	 * 初級者の自分では解析不可能なので、こういう解説を中級者・上級者の方も
	 * してくださったら、もっとwonderflのすばらしさというのが共有できるんじゃないかと
	 * 思う次第です。
	 * 
	 * あ、あと、clockmakerさん、
	 * 「extends Progression 拡張機能コンテスト」ダブル受賞おめでとうございますー。
	 * 
	 */
	
	 /*
	  * おおまかな流れ
	  *  1.パーティクルを10000個作る
	  *  2.パーティクル情報を打ち込むためのBitmapを作る
	  *  3.PerlinNoiseを作る
	  *  4.パーティクル情報を打ち込むためのBitmapの色を落とす
	  *  5.PerlinNoiseの情報を元にパーティクルの加速度・速度・位置を求める
	  *  6.求められたパーティクルの情報に従って、Bitmapに色を打ち込む
	  *  7.4・5・6の処理をパーティクル毎に処理する
	  *  8.4・5・6・7の処理を毎フレーム行う
	  */
	public class Liquid10000 extends Sprite {

		private const nums:uint = 10000;
		private var bmpDat:BitmapData;
		private var vectorDat:BitmapData;
		private var randomSeed:uint = Math.floor( Math.random() * 0xFFFF );
		private var bmp:Bitmap;
		private var vectorList:Array;
		private var rect:Rectangle;
		private var cTra:ColorTransform;

		public function Liquid10000() {
			initialize();
		}
		private function initialize():void {

			//stage関連の設定
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;

			//Bitmapを作ってaddChild(描画される、メインのBitmap)
			bmpDat = new BitmapData( 465, 465, false, 0x000000 );
			bmp= new Bitmap( bmpDat );
			addChild( bmp );
			
			//PerlinNoiseを描く(addChildされてないのが肝)
			//PerlinNoiseについては、前回やりましたね。なので解説は省略。
			vectorDat= new BitmapData( 465, 465, false, 0x000000 );
			randomSeed = Math.floor( Math.random() * 0xFFFF );
			vectorDat.perlinNoise( 230, 230, 4, randomSeed, false, true, 1 | 2 | 0 | 0 );
			//どんなPerlinNoiseか見たい人は、下のaddChildのコメントアウトを消して、
			//EnterFrameのaddEventListenerをコメントアウトしてみてください。
			//引数からもわかりますが、赤と緑(RG)しか使われていませんね。
			//これは理由があります。記憶の片隅にとどめておいてください。
			//addChild(new Bitmap(vectorDat));
			
			//stageの大きさの矩形範囲を設定
			//colorTransformでの範囲設定に使われます。
			rect = new Rectangle( 0, 0, 465, 465 );
			
			//時間経過とともにパーティクルの残像を暗くして、軌跡を作るための色設定。
			//花火でもありました。重要なパラメータです。
			cTra= new ColorTransform( 0, .8, .8, .9 );

			//パーティクルを入れるための配列
			vectorList= new Array();

			//パーティクルを1万個作ってvectorListに押し込む
			for (var i:uint = 0; i < nums; i++) {
				//パーティクルの場所:X・YともにRandom
				var px:Number = Math.random()*465;
				var py:Number = Math.random() * 465;
				//X・Y軸の加速度
				var av:Point = new Point( 0, 0 );
				//X・Y軸の速度
				var vv:Point = new Point( 0, 0 );
				//最初に求めたpx,pyを代入
				var pv:Point = new Point( px, py );
				//一番下に定義されているパーティクルの情報を持つVectorDat型のオブジェクトを生成して
				//位置情報・速度・加速度情報を入れ込む
				var hoge:VectorDat = new VectorDat( av, vv, pv);
				//配列に押し込む
				vectorList.push( hoge );
			}
			//メインタイムラインのEnterFrameイベントをloop関数に関連付ける
			addEventListener( Event.ENTER_FRAME, loop );
			//stageがクリックされたらresetFunc関数が呼び出されるようにする。
			stage.addEventListener( MouseEvent.CLICK, resetFunc );
		}
		
		
		
		//毎フレーム呼び出される関数
		private function loop( e:Event ):void {
			//描画用のBitmapDataをcolorTransformで暗くする(重要)
			//これにより、過去の色が薄くなるのは、以前解説した花火の描画の時も一緒でしたね。
			//これがないとどうなるのかは、花火のとき同様、コメントアウトしてみてください。
			bmpDat.colorTransform( rect, cTra );
			
			//vectorListのコピーを作成
			var list:Array = vectorList;
			
			//listの長さをlenに代入しておく
			//なんかこうすると、for文の処理が早いらしい?
			var len:uint = list.length;
			
			//このクラスの根幹処理なので超重要!
			//for文でlistの最初のエレメントから走査
			for (var i:uint = 0; i < len; i++) {
				//listのi番目のエレメントをdotsに代入
				var dots:VectorDat = list[i];
				
				//PerlinNoiseが描かれているvectorDatの色を抜き出す。
				//dotsの場所(dots.pv.x, dots.pv.y)にあるピクセルの色を抜き出し(getPixel)ます。
				var col:Number = vectorDat.getPixel( dots.pv.x, dots.pv.y );
				
				//上で抜き出した色から、さらにRGそれぞれの色を抜き出します。
				//後の処理で、RはX軸の加速度に、GはY軸の加速度に変換されます。
				//Bがないのは、別にZ軸とかその他の処理がいらない(RとGだけで十分)からですね。
				//これで、何故PerilinNoiseがRGしか使われてないか理由が判明しました。
				
				//var r:uint = col >> 16;	これでもOK
				var r:uint = col >> 16 & 0xff;
				var g:uint = col >> 8 & 0xff;
				//var b:uint = col & 0xff;
				
				/* ビット演算がわかってる人はスルーで。読む人は、紙に書きながら一緒にやってみるとわかりやすいかもしれません。
				 * (というか自分も勉強しながら解説してるんで、偉そうなこといえない)
				 * 上の式は普段あまり見ないですが、ビット演算と呼ばれるものです。主に色を弄るときに使われたりします。多分。
				 * 例えば、
				 * 
				 * var color:Number = 0x99C6FF;
				 * trace(color.toString(2));
				 * 
				 * とやってみると、100110011100011011111111とでます。
				 * これは、0x99C6FFの2進数バージョンです。つまり、
				 * 		最初の8ビット(10011001)がRで0x99
				 *		真ん中の8ビット(11000110)がGで0xC6
				 *		最後の8ビット(11111111)がBで0xFF
				 * ということになります。
				 * で、これからどうやってそれぞれの色を抜き出すかというと、例えばRを例に説明すると
				 * >>16 とやることで、16ビット分右にシフトされます。すると、10011001(最初の8ビット)となります。
				 * これで最初の8bit、つまり赤色が取り出せたということになります。
				 * 右側にあふれた数値はどうなるかというと破棄されます。それだけではないらしいのですが、よく分かりません(ずれた分左側を符号で埋める埋めないとかなんとか)。
				 * 
				 * さて、Gは「col >> 8 & 0xff」となってますが、col >> 8はいいとして、&はなんでしょう。Rでも使われてますね。
				 * これは何かというと論理積というらしいです。左辺と右辺のビット単位で掛け算するってことですね。
				 * 言葉だけだと分からないので、まずはGを例としてやってみましょう。
				 * まず、col >> 8で8ビット右にシフトすると、1001100111000110(RGの色)となりますね。これに0xff(11111111)で論理積を求めるということは、
				 * 
				 *   1001100111000110
				 * &         11111111
				 * -------------------
				 *           11000110
				 * 
				 * ということになります。(式がずれていたら数値を右寄せで整列してください)
				 * 一番右を見てみると、0*1は0だから、答えは0ですね。右から2番目は1*1なので1です。
				 * これが論理積(AND)・・・らしいです。逆に論理和(OR)というのもありますが、これはどちらかが1なら1ってやつですね。
				 * if等の条件式のandとorを連想すると分かりやすいかと思います。
				 * 
				 * Rの部分(前の8ビット)は掛け算されることすらないので、0と考えてください。ということは、答えが1になるはずもないですね。
				 * 感覚としては、フィルターをかける感じと考えていただければいいと思います。とにかく、これでGが出ました。
				 * ということは、Bはどうすれば導き出せるでしょう?答えは書いてありますが、自分で考えてみましょうー。
				 */
				
				//赤色をX軸の加速度に変換
				//r-128は負の方向にも平等に移動させるため。
				//赤色が129以上だったら正の方向に、127以下だったら負の方向にそれだけ移動するということですね。
				//.0005は多分目算での速度調整用数値だと思います。
				dots.av.x += ( r - 128 ) * .0005;
				//緑はY軸ですね。
				dots.av.y += ( g - 128 ) * .0005;
				//あとはそれぞれの速度に加速度をプラスして、位置を調整します。
				dots.vv.x += dots.av.x;
				dots.vv.y += dots.av.y;
				dots.pv.x += dots.vv.x;
				dots.pv.y += dots.vv.y;
				
				var _posX:Number = dots.pv.x;
				var _posY:Number = dots.pv.y;
				
				//摩擦というか空気抵抗というか、速度が増え続けないように少し速度と加速度を落とす感じですね。
				dots.av.x *= .96;
				dots.av.y *= .96;
				dots.vv.x *= .92;
				dots.vv.y *= .92;
				
				//ステージ外に移動した場合の処理です。若干分かりづらいですが、3項演算子の入れ子になってます。
				/* 3項演算子はif文の簡略化されたやつという感じです。定義的には怪しいかもしれませんが。
				 * if文にすると・・・
				 * 
				 * if ( _posX > 465 ) {
				 * 	dots.pv.x = 0;	//465を超えたら0(反対側)に移動する
				 * } else {
				 * 		if ( _posX < 0 ) {
				 * 			dots.pv.x = 465	//0より小さくなったら465(反対側)に移動する
				 * 		else {
				 * 			0;	//これよく分からないですけど、処理しない(false)ってこと・・・?
				 * 		}
				 * }
				 */
				( _posX > 465 )?dots.pv.x = 0:
				( _posX < 0 )?dots.pv.x = 465:0;
				//Y軸も同様に。
				( _posY > 465 )?dots.pv.y = 0:
				( _posY < 0 )?dots.pv.y = 465:0;
				
				//1*1の四角形をdots.pv.x, dots.pv.yの位置に0xFFFFFFの色で描く
				bmpDat.fillRect( new Rectangle( dots.pv.x, dots.pv.y, 1, 1), 0xFFFFFF );
			}
		}

		
		//クリックで呼び出される関数。
		//これと同じ処理が上でもありましたね。なので、解説はしません。
		//・・・が。同じ処理っていうことは、記述的には無駄かもしれませんね。どうすればその無駄を省けるのか考えてみましょう。
		//ヒント(というか答え):最初のinitialize関数のときにもこれを呼び出せばいいじゃん?でもe:MouseEventが邪魔だなぁ・・・。
		//				この引数がなくても関数が使えるようにできないかなぁ・・・。あ、デフォルトの値を入れておけばいいんじゃね?
		private function resetFunc(e:MouseEvent):void {
			randomSeed= Math.floor( Math.random() * 0xFFFF );
			vectorDat.perlinNoise( 230, 230, 4,randomSeed, false, true, 1|2|0|0 );
			vectorList= new Array();
			
			for (var i:uint = 0; i < nums; i++) {
				
				var px:Number = Math.random()*465;
				var py:Number = Math.random()*465;
				
				var av:Point = new Point( 0, 0 );
				var vv:Point = new Point( 0, 0 );
				var pv:Point = new Point( px, py );
				
				var hoge:VectorDat = new VectorDat( av, vv, pv);
				
				vectorList.push( hoge );
			}
		}
	}
}

//パーティクル用のクラスです。花火のときと似てる感じですね。これも説明は不要ですかね。
import flash.geom.Point;
class VectorDat {

	public var vv:Point;
	public var av:Point;
	public var pv:Point;

	function VectorDat( _av:Point, _vv:Point, _pv:Point ) {
		vv = _vv;
		av = _av;
		pv = _pv;
	}
}