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

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

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

Developer Book review 10.08.30

IMG_8287

1. 심리를 꿰뚫는 UX 디자인

좋은 기획자란 디자인 장치를 적절히 요구할 수 있는 능력이 요구됩니다. UX를 떠나 경험요소를 떠나 더욱 넓은 카테고리로 보면 고객만족 이라는 거대한 주제를 인식할 수 있습니다.

따라서 UX디자인 또한 고객만족과 관련된 모든 학문이 동원됩니다. 대표적으로 고객을 연구하는 마케팅분야에서 소비자심리학, 인지과학, 언어론, 경제학, 조직학 등의 직접적으로 인접한 학문만 해도 서너 가지가 넘습니다. 현실적으로 이런 모든 학문을 섭렵하기는 어렵기 때문에 직접적으로 관련이 있는 부분을 단편적으로 공부하는 것이 대부분입니다.

하지만 이 책은 그러한 학문들로부터 유도되는 실무상의 기본 이론을 재밌게 동시에 체계적으로 짧은 분량으로 다루고 있습니다. 따라서 켄트백의 구현패턴이 프로그래밍철학의 뼈대를 세워주는 것처럼 웹에서 고객을 어떻게 바라보고 어떻게 만족시킬 것인가에 대한 기본적인 개념을 세워주는 역할을 합니다(물론 UX측면에 특화 되어 있습니다만 ^^)

UX관련 공부하기 전에 입문서로 매우 좋을 뿐만 아니라 전반적인 웹서비스를 이해하는데 있어서도 좋은 교재가 됩니다. 단 이런 짧은 교재가 효과를 보려면 반드시 책 전체를 암기해야 할 필요가 있습니다. 그렇지 않으면 사람은 착해야 해 라는 도덕교과서를 읽었을 때 같은 감상만 남는다는군요 ^^

2. 리치 인터페이스 디자인

이 책은 사실 읽는 책이 아니라 레퍼런스 같은 책입니다. 이미 UX에 대해 심리적인 구조물을 어떻게 왜 사용할지를 아는 사람이 해당 구조물은 보다 구체적으로 언제 어떻게 어떤 점을 주의하여 전략적으로 배치할 것인가에 대한 참고서입니다. 따라서 한 번 쭉 읽고 목차나 요약 정도가 기억에 남게 되면 이후엔 해당 요소를 사용할 때 한 번씩 참고할 수 있습니다.

예를 들어 플레이 리스트에 현재 재생 중인 음악을 넣는 인터페이스를 생각해보죠.

  • 이 때 드래그앤드롭을 쓸까요, 클릭을 쓸까요, 토글을 쓸까요?
  • 왜 그렇게 고르셨나요? 어떤게 어떤 이유로 더 좋은가요?

이 책은 그러한 다양한 UX요소에 대해 정답을 알려주지 않지만 참고할 만한 많은 정보를 제공합니다. 하지만 제가 1번 책과 같이 구매한 이유는 기초적인 UX 나아가 고객에 대한 개념적인 기초가 없다면 그다지 기술적인 UI의 선택이 별로 의미가 없기 때문입니다.

3. 오브젝티브-C로 배우는 아이폰 게임 개발

한 동안 아이폰개발을 쳐박아두면서 언젠가 원서를 사야지 하고 생각하고 있던 녀석인데 역서가 나왔습니다. 낼름 집어왔습니다. 사실 이 책들은 전부 새로 오픈한 광화문 교보에서 사온 것인데 정말 도깨비 시장이 따로 없었습니다.

무리하게 통로를 줄이고 매대를 늘리는 바람에 매우 비좁고 사람에 치이면서 정신도 없었습니다. 게다가! 결정적으로 컴터 섹션이 줄어서 책이 더 없습니다. 기대를 많이 했었는데 자리 잡을 한 두달 정도는 안가게 될 듯합니다.

책에 대해서 말하자면 아주 재밌는 책입니다(저에게 안재밌는 책이 있겠습니까만은 ^^) 일단 게임개발서라고 표방하는 만큼 전부 openGL을 사용합니다. 또한 단지 게임을 부분부분 만들어보는 것이 아니라 제대로 게임엔진을 구축해가는 과정을 보여주고 사운드, 그래픽, 인터렉션, 물리엔진 등 게임엔진이 갖춰야 할 다양한 부분을 빠짐없이 다루고 있습니다. 전체적으로 입문자 수준으로 맞췄기 때문에 내용이 얕은 감이 많지만 그래도 나름 쓸만한 책입니다.

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

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

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

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

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

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

파이프라인의 구축

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

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

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

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

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