Particle System

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 을 사용하라고 추천해 드리고 싶습니다.

2 thoughts on “Particle System”

    1. ㅎㅎ 원래는 중력을 어떻게 적용 해야 할까 연구해보려고 시작했는데 결국 중력 값은 제대로 적용 시키지 못했네요. 수학과 물리는 참 어려운듯… 지금 저 파티클 들이 중력이 있음에도 무중력 처럼 느껴지는 이유는 파티클의 질량이 계산에 포함 되지 않아서 입니다.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.