Archive for the ‘ActionScript3’ Category.

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도 고정값이 아닌 마우스의 좌표 등을 맞춰보세요.
  • 물리엔진과 합체하시거나 간단한 중력계 트위닝을 걸어보세요.
  • 트윈맥스를 쓰지 말아보세요.

다 해보신 분은 손들어주세요. 결과물도 공유해주세요.

간만에 실무적인 튜토리얼이라 학습과제도 내봤습니다 ^^;

수 많은 디스플레이 객체를 관리하기 2

이전 포스팅(수 많은 디스플레이 객체를 관리하기 1)에서 생각해 낸 인스턴스에게 태그를 붙이는 아이디어를 보다 구체적이고 깊이 있게 생각해 보겠습니다.
일단 인스턴스와 태그와의 관계를 정립했으니 태그 자체에 대한 고민으로 옮겨갈 필요가 있습니다.

Continue reading ‘수 많은 디스플레이 객체를 관리하기 2’ »

수 많은 디스플레이 객체를 관리하기 1

조직화할 그래픽 객체가 대규모로 존재하는 경우엔 적당한 식별할 자료구조물이 없다는 생각을 자주 합니다. 객체란 행위중심으로 설계하기 때문에 상태는 보조적인 역할만 수행합니다. 따라서 데이터 덩어리를 효과적으로 정리하고 접근성을 향상시키는 구조물을 만드는 경우에 OOP패러다임은 큰 쓸모가 없습니다.
오히려 힌트를 받은 만한 곳은 데이터베이스로 개별 데이터가 아닌 집합을 지원하는 점을 이용할 수 있습니다. 이러한 아이디어를 차용하여 대규모로 등장하는 그래픽스 데이터에 대한 조직화에 대해서 간단히 얘기해보겠습니다.

Continue reading ‘수 많은 디스플레이 객체를 관리하기 1’ »

파이프라인의 구축

파이프라인이란 매우 간단한 개념입니다. 어떤 일이 처리될 때 독립된 단계별로 처리한다는 의미인데 이 내용에 대해 가장 많이 등장하는 예는 세차장입니다.

만약 손세차장을 운영한다고 해보죠. 땅이 충분히 넓고 인원이 많다면 길이로 길게 세차장을 만들어서 공정을 분업화하는 방법을 사용할 수 있습니다.

  1. 따뜻한 물을 끼엊는 파트
  2. 비누칠을 하는 파트
  3. 솔질하는 파트
  4. 헹구는 파트
  5. 마른 수건으로 닦는 파트

위의 파이프라인을 구축한다는 것은 단순히 분업했다는 것이 아니라 여러 가지 의미를 갖게 됩니다.

Continue reading ‘파이프라인의 구축’ »

BitmapData 확대축소 문제해결

현재 흔하게 쓰이는 용어인 bitmap은 사실 bit + map 의 합성어 입니다. 그 의미를 곱씹어보면 0, 1 로 된 bit가 구성한 2차원 배열이란 뜻입니다. 따라서 원래 흰색 검정색만 표현되는 이미지여야 합니다. 풀컬러 이미지에 대해서는 pixelMap이란 표현이 정확하겠죠. 따라서 이러한 개념을 openGL에서는 구분하여 쓰고 있습니다. bitmap은 흑백이미지고, 그 이상의 색이 필요하면 pixelMap을 생성해야 하는 것이죠. 이 용어를 혼탁하게 만든 장본인은 누구일까요? 예상하시는 대로 M$되겠습니다.
잠깐 헛소리 중이었습니다 ^^;

Continue reading ‘BitmapData 확대축소 문제해결’ »