hika님의 블로그에 파티클에 관련된 글이 하나 올라 왔습니다. 학습과제와 함께… 원래는 눈팅만 하고 도망가려 하였으나 hika님의 도발에 넘어가서 잠깐 시간을 내어 제 나름대로 재구성 해봤습니다. 짧은 시간에 기능만 구현하려다 보니 손발이 오그라드는 코드가 나와버렸습니다. 특히 중력 계산하는 부분은 그냥 산수 입니다;;;;
원 소스에 있는 선택 값과 구간 값 표현하는 표현식 정말 아이디어 인거 같습니다. 저런 표현식을 그냥 블로깅 하면서 만들어 내시다니 차마 따라 갈 수 없는 내공입니다. orz
그럼 먼저 소스 부터 보겠습니다.
package{ import com.bit101.components.Label; import com.bit101.components.Slider; import com.greensock.TweenMax; import flash.events.Event; import flash.display.BlendMode; import flash.display.Sprite; import flash.events.MouseEvent; import flash.geom.Point; [SWF(backgroundColor="#EEEEEE", frameRate="110", width="500", height="400")] public class Main extends Sprite { public static var GRAVITY:Number = 100; public static var WIND:Number = 0; public static var POWER:Number = 0; public var duration:Number; public var rate:Number; public var particle:Class; public var lifeTime:Number; public var repeat:int; public var emitter:Point = new Point; public var power:Point = new Point; public function Main(){ initComps(); duration = 1; rate = 30; particle = Drop; lifeTime = 5; stage.addEventListener( MouseEvent.CLICK , run ); } public static function randFloat( $start:Number, $end:Number ):Number{ return Math.random()*$end + $start; } private function run( $e:* ):void{ if ( repeat === 0 && stage.mouseY > 60 ) { repeat = rate*duration; emitter.x = stage.mouseX; emitter.y = stage.mouseY; make(); } } private function make():void { var life:Number = randFloat(lifeTime-1.5, lifeTime); var power:Point = new Point(randFloat(-(POWER/2), POWER), randFloat(POWER, POWER)); var instance:* = new particle(life, power); instance.x = emitter.x; instance.y = emitter.y; addChild(instance); if( --repeat ){ TweenMax.to( {x:0}, duration/rate, {x:1, onComplete:make} ); } } private function initComps():void { _gravitySlider = new Slider(Slider.HORIZONTAL, this, 30, 30, handleGravity); _gravitySlider.minimum = 0; _gravitySlider.maximum = 200; _gravitySlider.value = 100; _gravitySlider.mouseEnabled = false; _gravityLabel = new Label(this, 28, 13, "Gravity : "+_gravitySlider.value); new Label(this, 27, 36, "0"); new Label(this, 118, 36, "200"); _windSlider = new Slider(Slider.HORIZONTAL, this, 150, 30, handleWind); _windSlider.minimum = -100; _windSlider.maximum = 100; _windSlider.value = 0; _windLabel = new Label(this, 148, 13, "Wind : "+_windSlider.value); new Label(this, 140, 36, "-100"); new Label(this, 243, 36, "100"); _powerSlider = new Slider(Slider.HORIZONTAL, this, 270, 30, handlePower); _powerSlider.minimum = 0; _powerSlider.maximum = 200; _powerSlider.value = 100; _powerLabel = new Label(this, 268, 13, "Power : "+_powerSlider.value); new Label(this, 266, 36, "0"); new Label(this, 360, 36, "200"); GRAVITY = _gravitySlider.value; WIND = _windSlider.value; POWER = _powerSlider.value; } private function handlePower(e:Event):void { _powerLabel.text = "Power : " + _powerSlider.value; POWER = _powerSlider.value; } private function handleGravity(e:Event):void { _gravityLabel.text = "Gravity : " + _gravitySlider.value; GRAVITY = _gravitySlider.value; } private function handleWind(e:Event):void { _windLabel.text = "Wind : " + _windSlider.value; WIND = _windSlider.value; } private var _gravitySlider:Slider; private var _gravityLabel:Label; private var _windSlider:Slider; private var _windLabel:Label; private var _powerSlider:Slider; private var _powerLabel:Label; } } import flash.display.DisplayObject; import flash.display.Shape; import flash.events.Event; import flash.geom.Point; class Drop extends Shape { public function Drop($life:Number, $power:Point) { super(); if(!_this) _this _life = $life; _power = $power; _elapsed = 0; graphics.beginFill( 0x4f9cf2, Main.randFloat(.5, 1) ); graphics.drawCircle( 0, 0, 3 ); addEventListener(Event.ADDED_TO_STAGE, handleAddStage); } private function handleAddStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, handleAddStage); _rate = 1 / stage.frameRate; addEventListener(Event.ENTER_FRAME, hanleThis); } private function hanleThis(e:Event):void { x += (-_power.x + (Main.WIND * _elapsed)) * _rate; y += (-_power.y + (Main.GRAVITY * _elapsed)) * _rate; _elapsed += _rate; if (_elapsed/_life>0.9) { alpha = 1 + (.9 - _elapsed / _life)*10; } // Destroy if (_elapsed > _life) { var _this:DisplayObject = e.currentTarget as DisplayObject; if (parent.contains(_this)) _this.removeEventListener(Event.ENTER_FRAME, hanleThis); parent.removeChild(_this); _this = null; } } private var _this:Drop; private var _life:Number; private var _elapsed:Number; private var _power:Point; private var _rate:Number; }
원 소스에서는 TweenMax 를 이용해서 구현했는데 저는 조금 다르게 걍 enterFrame 으로 구현했습니다. 저렇게 생성되는 instance 에서 enterFramer 을 도는 건 아무래도 성능상의 문제가 좀 있을 것 같기는 합니다. 그래도 객체 스스로 살고 죽고 하니까 만들어만 주면 따로 신경 쓰지 않아도 되서 즐겨 쓰고 있는 방법 입니다. 중력과 바람은 월드에 단 하나만 있으면 되니까 걍 static 으로 고정 했습니다. 결과는 다음 과 같습니다.
중력계산 하는 걸 제대로 수정하고 싶지만 모르니까 패스~ 일단 TweenMax 를 사용 하지 않고 간단한 중력계를 넣었다는 거에 만족 합니다. ㅎㅎㅎ 다만 누군가 파티클을 만들 고 싶으시다면 말성임 없이 StardustParticle 을 사용하라고 추천해 드리고 싶습니다.
오 진짜로 숙제를 하실줄은 몰랐다는!
ㅎㅎ 원래는 중력을 어떻게 적용 해야 할까 연구해보려고 시작했는데 결국 중력 값은 제대로 적용 시키지 못했네요. 수학과 물리는 참 어려운듯… 지금 저 파티클 들이 중력이 있음에도 무중력 처럼 느껴지는 이유는 파티클의 질량이 계산에 포함 되지 않아서 입니다.