particle system
파티클 시스템을 제작하려면 emitter의 개념이 필요합니다. emitter란 일종의 factory메서드를 제공하는 클래스 입니다. 하지만 factory는 보통 하나의 객체를 직접 반환하는데 비해 emitter는 일정한 조건이 충족되는 동안 계속 객체를 생성합니다.
문제는 이 일정한 조건을 나타내는 부분인데 정적 요소로 수량을 조건으로 사용할 뿐만 아니라 동적 요소로 시간을 사용합니다. 따라서 시간에 따른 생성관리를 하기 위해서는 트위너와 결합하게 되는 것이 일반적입니다.
만약 파티클 시스템을 바닥부터 만들려면 트위너부터 만들어야 하기 때문에 상당한 큰 작업입니다만, 트윈맥스 등의 기저층을 활용할 생각이라면 매우 간단히 제작할 수 있습니다.
emitter의 인터페이스
파티클 시스템은 결국 파티클을 만들어내는 것이므로 어떠한 조건으로 파티클을 생성할까를 생각해봐야 합니다. 쉽게 떠오르는 조건만 간단히 기술해 보죠.
- 일정 시간 동안 생성한다.
- 일정 개 수를 생성한다.
- 일정 위치에 생성한다.
- 특정 클래스의 인스턴스를 생성한다.
- 생성된 파티클을 초기화한다.
- 생성된 파티클의 움직임을 정의한다.
이 정도 조건이라면 상당히 쓸만합니다. 코드로 풀어보죠.
class Cstar extends Shape{
public function Cstar(){
this.graphics...//별을 그린다.
}
}
var emitter:Emitter = new Emitter;
emitter.duration = 10; //10초간 생성한다.
emitter.rate = 30; //1초에 30개씩 생성한다.
emitter.x = stageWidth/2;
emitter.y = stageHeight/2; //파티클이 생성될 기준 위치
emitter.particle = Cstar; //파티클로 생성될 클래스
//파티클 초기화 크기 10%~200%, 회전 0~360도
emitter.from = {scale:{'0.1':2}, rotation:{'0':360}};
//파티클 목표점 정의 x=0 또는 x=stageWidth, y=0~stageHeight
emitter.to = {x:[0,stageWidth], y:{'0':stageHeight}};
//파티클이 움직일 시간 3초
emitter.lifeTime = 3
emitter.run();
화면에 무엇이 나올지 대략 상상이 가실 겁니다. 중앙점에서 별이 양 사이드로 쏟아져나가는 형태가 되는 것이죠.
선택 값과 구간 값
단지 위에 좀 특수하게 기술한 값이 있습니다. 구간 값과 선택 값인데 표현하는 방법은 여러 가지겠지만 전 저런 방법도 가끔 사용합니다.
선택 값의 경우에는 배열로 표현하는 거죠. 예를 들어 x가 0, 100, 200 중에 하나가 랜덤하게 지정되길 바란다면 x:[0,100,200] 이라고 기술합니다.
구간 값은 범위를 표현해야 하고 선택 값의 형태와 구별되어야 하니 배열을 쓸 수는 없습니다. 그래서 오브젝트로 표현합니다. 예를 들어 x가 0~300의 범위를 가져야 한다면 x:{’0′:300} 이라고 표현하는 거죠(참고로 걍 글 쓰다가 생각난 표현식입니다^^ 실제로는 더 복잡한 값의 표현을 원하는 경우가 많아 제 실제 구현에서는 텍스트파싱을 하게 됩니다)
구현하기
이제 트윈맥스를 이용해 위의 emitter를 실제로 구현해보죠. 간략히 Main클래스에 통합하여 구현하겠습니다.
참 한 가지 문제가 있습니다. 트윈맥스의 onRepeat가 제대로 작동하지 않습니다. 어쩔 수 없이 재귀 호출을 통해 처리해야 합니다.
혹시 onRepeat의 문제를 당해보시거나 해결하신 분은 노하우 좀..공유해주세요( 사실 전 자작 트위너를 사용하지만 인류번영을 위해~ )
package{
import com.greensock.TweenMax;
import flash.display.BlendMode;
import flash.display.Sprite;
import flash.events.MouseEvent;
[SWF(backgroundColor="#000000", frameRate="110", width="500", height="400")]
public class T10emitter extends Sprite{
public var duration:Number;
public var rate:Number;
public var _x:Number;
public var _y:Number;
public var particle:Class;
public var from:Object;
public var to:Object;
public var lifeTime:Number;
public var repeat:int;
public function T10emitter(){
duration = 1;
rate = 30;
_x = stage.stageWidth/2;
_y = stage.stageHeight/2;
particle = circle;
from = {scaleX:{'0.1':2},scaleY:{'0.1':2}, rotation:{'0':360}};
to = {x:[0,stage.stageWidth], y:{'0':stage.stageHeight}};
lifeTime = 3;
stage.addEventListener( MouseEvent.CLICK , run );
}
public function run( $e:* ):void{
//시작위치는 emitter의 x,y로 고정
if( repeat === 0 ){
repeat = rate*duration;
from.x = stage.mouseX;
from.y = stage.mouseY;
make();
}
}
private function make():void{
var instance:* = new particle;
instance.x = instance.y = -100;
addChild(instance);
TweenMax.fromTo( instance, lifeTime, setParam( from ), setParam( to ) );
if( --repeat ){
TweenMax.to( {x:0}, duration/rate, {x:1, onComplete:make} );
}
}
private function setParam( $refer:Object ):Object{
var result:Object, key:*, subKey:*;
result = {};
for( key in $refer ){
//배열인 경우
if( $refer[key] is Array ){
//배열요소 중에 하나를 랜덤하게 할당한다.
result[key] = $refer[key][randInt(0, $refer[key].length - 1 )];
//오브젝트인 경우
}else if( $refer[key].toString() === '[object Object]' ){
//범위 구간 내에서 랜덤하게 선택한다.
for( subKey in $refer[key] ){
result[key] = randFloat( parseFloat( subKey ), $refer[key][subKey] );
}
//보통 값이라면 그냥 할당
}else{
result[key] = $refer[key];
}
}
return result;
}
private function randInt( $start:int, $end:int ):int{
return Math.random()*($end+1) + $start;
}
private function randFloat( $start:Number, $end:Number ):Number{
return Math.random()*$end + $start;
}
}
}
import flash.display.Shape;
class circle extends Shape{
public function circle(){
super();
graphics.beginFill( 0xffffff * Math.random());
graphics.drawCircle( 0, 0, 3 );
}
}
결과는 다음과 같이 됩니다. 클릭하세요.
결론
가장 기초적인 emitter구현에 성공하셨으므로 다음은 무한한 확장의 세계로 가실 차례입니다.
- 한 번에 하나만 생성하는 게 심심하니 rate때마다 랜덤한 갯수를 생성하도록 run을 바꾸세요.
- 직선적인 움직임을 벗어나 베지어트윈이랑 섞으세요.
- 다양한 이징옵션과 섞어보세요.
- 알파나 필터와 섞어보세요.
- emitter의 x,y도 고정값이 아닌 마우스의 좌표 등을 맞춰보세요.
- 물리엔진과 합체하시거나 간단한 중력계 트위닝을 걸어보세요.
- 트윈맥스를 쓰지 말아보세요.
다 해보신 분은 손들어주세요. 결과물도 공유해주세요.
간만에 실무적인 튜토리얼이라 학습과제도 내봤습니다 ^^;