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 을 사용하라고 추천해 드리고 싶습니다.
오 진짜로 숙제를 하실줄은 몰랐다는!
ㅎㅎ 원래는 중력을 어떻게 적용 해야 할까 연구해보려고 시작했는데 결국 중력 값은 제대로 적용 시키지 못했네요. 수학과 물리는 참 어려운듯… 지금 저 파티클 들이 중력이 있음에도 무중력 처럼 느껴지는 이유는 파티클의 질량이 계산에 포함 되지 않아서 입니다.