게임에서 키보드 제어는 가장 기본이면서 중요하다. 요즘 Flash 관련 게임을 제작해보겠다는 열의를 가지고 이것저것 테스트 해보고 있는데 게임 분야는 워낙 방대하고 알아야할 것도 많아서 계속 도전하고 싶은 충동을 강하게 만든다.

일단 가장 기본적인 슈팅게임을 만들어보겠다는 목표를 세웠다.

원더플(http://wonderfl.net)에 올라오는 다양한 완성된 게임들을 보면서 나름대로 코드를 분석하고 정리하면서 하나씩 완성해 나가고자 한다.

키보드 제어에 대해서는 Hika님의 글이 매우 유용하다.

마우스/키보드의 인터렉션 최적화 2/2

나는 키보드 제어에 대한 기본 지식을 위 글을 통해서 공부했고 뭔가 슈팅게임을 위한 적당한 클래스를 만들어봤야겠다는 생각을 했다. 좀 더 범용적인 클래스가 좋겠다고 했는데.. 마침 원더플에 올라온 KeyMapper 클래스가 눈길을 끌었다.


// Key mapper
//----------------------------------------------------------------------------------------------------
class KeyMapper {
	public var flag:int=0;
	private var _mask:Vector.=new Vector.(256, true);

	function KeyMapper(stage:Stage):void {
		stage.addEventListener("keyDown", function(e:KeyboardEvent):void { flag|=_mask[e.keyCode]; });
		stage.addEventListener("keyUp", function(e:KeyboardEvent):void { flag&=~(_mask[e.keyCode]); });
	}

	public function map(bit:int, ... args):KeyMapper {
		for (var i:int=0; i < args.length; i++) {
			_mask[args[i]]=1 << bit;
		}
		return this;
	}
}

매우 단순하고 아름답기 까지 하다. ㅎㅎ
사용법은 꽤 간단하다. 아래는 사용할 키를 등록하는 방법이다.

// keyboard mapper
_key=new KeyMapper(stage);
_key.map(0, 37, 65).map(1, 38, 87).map(2,39,68); //좌 : Left(37), A(65); 위:Up(38),W(87); 우:Right(39),D(68)

_key.map() 메소드의 첫번째 인자값은 해당 키가 눌려졌을때 참조할 bit위치이다. 2번째 부터는 이 bit위치가 변경되기 위한 키코드를 넣을 수 있다. 가령 0번 bit에 Left키 또는 A키를 눌렀을때 bit가 on될 수 있도록 한다.

사용된 키코드는 KeyboardEvent가 발생할때 이벤트의 keyCode속성에 나오는 코드이다. 이 값은 범용적으로 약속된 값으로 다음 글에서 참조하길 바란다.

키코드값 표

KeyMapper에 등록된 키들은 EnterFrame 이벤트 발생되는 시점에서 다음과 같이 사용하면 된다.

var inkey:int = _key.flag;
//회전
ar=(((inkey & 4)>>2) - (inkey & 1))*0.5; //회전가속
vr += ar; //회전속도
r += vr; //회전값
ar *= .96; //계속 증가 못하도록 방지
vr *= .92; //계속 증가 못하도록 방지
( r < 0 ) ? r += 360 : ( r > 360 ) ? r-=360 : 0; //0~360으로 Normalization

//이동
a = -((inkey & 2)>>1)*1.2; //가속
ax = a * Math.sin((360-r)*D2R); //가속 x성분
ay = a * Math.cos((360-r)*D2R); //가속 y성분 
vx += ax; //속도 x성분
vy += ay; //속도 y성분
x += vx; //위치 x성분
y += vy; //위치 y성분 
ax *= .96; //계속 증가 못하도록 방지
ay *= .96;
vx *= .90;
vy *= .90;



즉 inkey & 4(10진수)는 inkey & 100(2진수)과 같다. 즉 3번째 bit의 값이 on이냐 off이냐에 따라서 3번째으로 지정된(여기에서는 우측이동키 또는 D)키가 눌려졌는지 알 수 있다. 이 값에 shift연산자로 >>2를 하면 눌려졌으면 1, 안눌려졌으면 0을 참조할 수 있게 된다.

이처럼 키보드를 참조하기 위해 if문이 아닌 bit연산으로 참조함으로 이 부분에 있어서 더 빠른 참조가 가능해진다고 본다.

단지 현재 이 KeyMapper 클래스는 키동작의 순서를 담지 않기 때문에 액션대전게임등과 같은 것을 만드려면 그와 관련된 Key동작을 mapping할 수 있는 것이 필요할 것이다.

필자는 이것으로 한대의 비행기를 회전, 이동하는 Flash 애플리케이션을 간단하게 만들어봤다.


보러가기 : http://wonderfl.net/code/53e6c9b9ed9a776dd45b529304937d13ea9398ac

참고로 비행기는 아래 이미지를 참고했으며 출처는 다음 링크에 있다.
http://www.brighthub.com/internet/web-development/articles/11010.aspx



위 이미지를 회전시켜 BitmapData를 만드는 방법은 다음글을 참고한다.
Flash 속도 개선을 위한 실험 - 10만개 입자 유체 시뮬레이션 연장전!


추가사항

hika님께서 댓글로 다음처럼 지적해주셨습니다.

결사반대!
그러니까 80년대에 사용하던 비트or연산 합체는 제발 지양해주세요.
그 원조인 c업계에서도 철폐를 위해 노력하고 있습니다.
코드는 점점 암호화 되고 그걸 다시 가시화하기 위해 엄청난 const를 잡게 됩니다. 나락으로 빠지는 길이죠.
빠른 벡터를 컨테이너로 사용하세요 ^^

var keyDown:Vector.<Boolean> = new Vector.<Boolean>();

stage.addEventListener("keyDown", function(e:KeyboardEvent):void { keyDown[e.keyCode] = true; });
stage.addEventListener("keyUp", function(e:KeyboardEvent):void { keyDown[e.keyCode] = false; });

if( keyDown[Keyboard.UP] ){

if( keyDown[Keyboard.SHIFT] ){
   if( keyDown[Keyboard.SPACE] ){

이러한 코드는 가독성만 좋은게 아닙니다.
http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7d01.html
이 문서를 보세요.

키와 키 코드의 매핑은 장치 및 운영 체제에 따라 다릅니다. 따라서 키 매핑을 사용하여 액션을 트리거해서는 안 됩니다. 대신에 Keyboard 클래스에서 제공하는 미리 정의된 상수 값을 사용하여 적당한 keyCode 속성을 참조해야 합니다. 예를 들어, Shift 키의 키 매핑 대신 Keyboard.SHIFT 상수를 사용하십시오

이러한 멘트가 나옵니다. 플래시플레이어는 운영체제 버전에 따라 키코드값을 적절하게 매핑해주기 때문에 개발한 게임이 맥에서 안도는 사태를 막아줍니다 ^^

여하튼 그 모든걸 떠나서 결국 38, 39를 외우는 것도 무리고 코드를 해석하기 위해 맨날 키코드표를 참조한다는것도 말도 안되고 그 결과 const를 도입해서 느려지는 식입니다.

저 방식은 원더플에 올라오는 수준이거나 데모를 만드는 수준에서만 키셋이 몇개 없을때 통용되는거지 실제 게임에서 유저가 키셋팅을 커스터마이즈하는 것도 흔하고 플래시 특성상 이기종에서 실행되는것도 흔하기 때문에 아니되옵니다 ^^


네.. 중요한건 Adobe문서에 잘 나와있었다는 거죠 ^^;
그래서 기존 KeyMapper를 버리고 새로운 형태로 만들어봤습니다.


class Key {
	static private var _down:Vector.=new Vector.(256, true);
	static private var _stage:Stage;

	static public function start($stage:Stage):void {
		stop();
		if (Capabilities.hasIME) {
			IME.enabled=false;
		}
		if ($stage === null) {
			throw new Error("인자값이 null이면 안됩니다.");
		}
		_stage=$stage;
		_stage.addEventListener("keyDown", KEY_DOWN);
		_stage.addEventListener("keyUp", KEY_UP);
	}

	static public function stop():void {
		if (Capabilities.hasIME) {
			IME.enabled=true;
		}
		if (_stage) {
			_stage.removeEventListener("keyDown", KEY_DOWN);
			_stage.removeEventListener("keyUp", KEY_UP);
		}
	}

	static private function KEY_DOWN($e:KeyboardEvent):void {
		_down[$e.keyCode]=1;
	}

	static private function KEY_UP($e:KeyboardEvent):void {
		_down[$e.keyCode]=0;
	}

	static public function isDown($keyCode:int):int {
		return _down[$keyCode];
	}
}


IME 부분은 한글로 입력되어 제대로된 키코드가 들어오는 것을 방지하기 위함입니다.(이 부분은 좀더 생각해볼 필요가 있을듯..) Vector를 Boolean말고 int로 한 것은 간단한 연산처리가 필요한 경우에 활용하기 위함입니다. 이렇게 해두면 호스트쪽은 아래처럼 참조하면 됩니다.
var a:Number; //inkey:int = _key.flag;
var right:int=Key.isDown(KEY_RIGHT) | Key.isDown(KEY_D);
var left:int=Key.isDown(KEY_LEFT) | Key.isDown(KEY_A);
var up:int=Key.isDown(KEY_UP) | Key.isDown(KEY_W);

// rotation
ar=Number(right - left) * 0.5; //ar=(((inkey & 4)>>2) - (inkey & 1))*0.5;
vr+=ar;
r+=vr;
ar*=.96;
vr*=.92;
(r < 0) ? r+=360 : (r > 360) ? r-=360 : 0;

// move
a=-Number(up) * 1.2; //a = -((inkey & 2)>>1)*1.2;
ax=a * Math.sin((360 - r) * D2R);
ay=a * Math.cos((360 - r) * D2R);
vx+=ax;
vy+=ay;
x+=vx;
y+=vy;
ax*=.96;
ay*=.96;
vx*=.90;
vy*=.90;


이렇게 함으로써 가독성 향상도 되고 운영체제에 따른 다른 키코드에 대한 대응도 되겠네요. 

물론 KEY_DOWN, KEY_UP등은 Keyboard.DOWN, Keyboard.UP값으로 정의했습니다. 

사실... Key.isDown($keyCode:int) 대신 Key.isDown(...args)로 하려고 했는데... 이게 문제인것이 |연산인지 &연산인지 호스트 코드를 작성하는 입장에서는 저 함수명으로는 알기 힘들어지고... 사실 이런 요구가 계속 넘쳐날것 같아서 결국 가장 간단하게 만들어놓고 필요하면 그때그때 대응하는 편이 훨씬 좋다는 판단을 했습니다.

아무튼 좋은거 배웠습니다. 감사합니다.

결과 : http://wonderfl.net/code/23e1ea553ffa7994af88933059c1a55b04e683eb


참고
원더플(wonderfl.net)
마우스/키보드의 인터렉션 최적화 2/2
키코드값 표
Flash 속도 개선을 위한 실험 - 10만개 입자 유체 시뮬레이션 연장전!
원더플(Wonderfl)을 이용해 ActionScript 3.0을 공부하자. - 자동 테스트 환경 구축 소개

글쓴이 : 지돌스타(http://blog.jidolstar.com/672)

인터넷 상에 멋진 Flash 애플리케이션을 보면서 Flash 개발자라면 한번쯤은 어떻게 구현했을까 의문을 품어봤을 것이다. 필자 또한 그러했다. 하지만 남이 만든 소스를 보기 위해 SWF 파일을 디코드 하더라도 분석하기가 여간 까다로운게 아니다. 차라리 그럴바에 관련 내용을 담은 해외 서적을 하나 구입해서 공부하는게 더 빠를지도 모른다. 

이 문서는 자신의 Flash 제작 실력을 더욱 한층 높이기 위한 시도를 하고자 하는 분들을 위한 것이다. 중,고급 flash 개발 도약을 위해 도움이 되었으면 한다.

목차 
  • Wonderfl 소개 
  • Wonderfl의 주요기능 
  • Wonderfl 최고의 코드들 집합 Beautifl
  • Flash Builder 4에서 Wonderfl 코드 테스트 환경 만들기
  • Wonderfl 테스트 환경을 한번에 구축하기!
  • 정리하며
  • 참고사이트


Wonderfl 소개


Wonderfl(http://wonderfl.net)은 일본의 Kayac회사에서 제작하고 운영하고 있는 웹사이트로 ActionScript 3.0을 직접 인터넷 상에서 제작하고 컴파일할 수 있는 환경을 제공한다. Wonderfl은 Wonderful + flash가 합쳐진 단어로 생각된다. 매우 재미있고 기억하기 쉬운 단어 조합이다.

이 사이트는 단순히 컴파일하고 결과만 볼 수 있는 단계를 넘어 각종 공개된 라이브러리와 연동이 가능하며, 개발자들간에 코드 공유를 통해 더 좋은 코드로 개선할 수 있는 환경을 제공한다. 이로써 Flash 개발자간에 코드레벨 SNS를 실현했다고 생각한다.
 
개인적으로 Flash 개발자라면 이 사이트와 매우 가깝게 지내야한다고 생각한다. 실제로 일본의 매우 유명한 Flash 개발자들의 귀중한 노하우가 많이 공개되어 있기도 하다.

공개된 라이브러리를 활용하기 위한 환경을 구축하는 일을 매우 귀찮고 방법 또한 난해한 경우가 다분하다. 이런 경우 Wonderfl의 검색기능을 통해 필요한 기술을 쉽게 습득해서 그런 문제를 해결할 수도 있다.


Wonderfl 주요 기능

필자는 Wonderfl은 Flash 개발자간 코드공유 SNS라고 정의하고 싶다. Wonderfl의 몇가지 기능을 소개하면서 그 이유를 알아보도록 하자.

1. 코드 작성
Wonderfl의 가장 기본적인 기능 중에 하나로 코드 작성후 실시간으로 컴파일하고 결과를 볼 수 있는 기능이다. 이 기능은 Flex 컴파일러인 mxmlc을 활용해서 만든 것으로 파악한다. 그렇기 때문에 일반 ActionScript 3.0 코드외에도 MXML 태그로 작성해도 컴파일이 가능하다. 코드에 대한 라이센스도 함께 지정할 수 있고 작성이 완료된 코드는 블로그나 다른 커뮤니티에 퍼갈 수 있도록 되어 있다.


2. "Fork"기능을 이용한 다른 사람의 코드 수정
Fork기능은 이미 만들어진 코드를 퍼가서 자기 입맛대로 수정하는 것을 의미한다. wonderfl에 올라온 모든 코드는 이 작업이 가능하다. Fork의 강력함은 아래 소개하는 필자의 2개의 글을 참고하면 좋겠다.

10만개 입자를 이용한 유체 시뮬레이션 실험
Flash 속도 개선을 위한 실험 - 10만개 입자 유체 시뮬레이션 연장전!




3. 좋아하는 코드 찜하기
Fork 기능은 매우 강력하지만 또한 부담스러울 수 있다. 수정하지도 않을 코드를 Fork하는 것은 왠지 끌리지 않는다. 이럴때 사용할 수 있는 기능이 favorite 기능이다. 멋진 코드를 발견하면 "add to favorites"를 클릭하자. 그러면 다음에 설명할 "나의 페이지"에서 지금까지 favorite로 지정한 코드 리스트를 언제든지 참고할 수 있게 된다.


4. Following, Follower 기능
트위터와 같이 Following, Follower 기능이 존재한다. 코드 공유를 넘어 유용한 코드를 생산하는 사람을 Follow로 등록하면 나의 페이지에서 Following 리스트로 확인할 수 있고, 또 그 사람이 만들어내는 코드를 언제든지 쉽게 확인할 수 있게 된다. 그야말로 개발자 코드레벨 SNS 사이트인 것이다.

5. 나의 페이지
나의 페이지는 자신이 만든 코드, Fork나 favorite로 지정된 코드, Follow로 지정한 사람들의 동향을 확인할 수 있는 기능을 가지고 있다. 이 페이지는 "wonderfl.net/user/아이디"로 접근할 수 있다. 필자는 http://wonderfl.net/user/jidolstar 이다.



6. 질문&답변 기능
코드중에 이해하기 힘든 코드나 설명이 필요한 코드가 있다면 질문할 수 있다. 방법은 Fork를 하고 tag에 question을 달면 된다. 일본어, 영어로 해야하기 때문에 왠지 부담이 되겠지만 활용하면 분명 좋은 기능이다.





Wonderfl 최고의 코드들 집합 Beautifl
Beautifl(http://beautifl.net)은 Wonderfl에 올라온 멋진 코드들만 선별해서 종류별로 소개해준 사이트이다. 이 사이트는 Wonderfl에 올라온 코드중에 추천받은 코드만 엄선하여 보기 좋게 카테고리를 만들어 분류해주고 있다. 잘 이용하면 큰 도움이 되는 사이트이다.

Beautifl.net의 메인화면



Flash Builder 4에서 Wonderfl 코드 테스트 환경 만들기
Wonderfl에서 매우 좋은 코드 툴을 제공하고 있지만 약간의 수정외에는 Flash Builder 만큼 자유롭게 개발하기는 힘들다. Wonderfl에서 조금 놀다보면 이제 그곳에 있는 코드를 가져와 내것으로 만들어보고 싶게 된다.  

Wonderfl에는 Flash Player에서 제공하는 Native 클래스들(flash.* 패키지로 구성된 것들)뿐 아니라 외부 다른 유용한 라이브러리를 함께 활용할 수 있는 환경이기 때문에 그와 비슷한 환경을 만들어주는 것이 필요하게 된다. 필자처럼 Wonderfl에 공개된 코드를 매번 가져다가 테스트 해보는 사람은 필요할때 마다 라이브러리를 가져다 쓰는 것은 매우 귀찮은 작업이다. 그러므로 필요한 라이브러리를 한번에 가져와 언제든지 테스트 해볼 수 있는 환경을 구축할 필요성이 생긴다.

여기서는 라이브러리를 자신의 워크스페이스에 포함하는 방법과 그 라이브러리를 이용해 테스트를 하는 방법을 간단히 소개한다.

1. 라이브러리 가져오기
Wonderfl에서 사용하는 라이브러리 목록은 사이트 우측상단에 Wonderfl > libraries 메뉴로 들어가면 아래와 같은 화면을 통해 사용한 라이브러리의 종류와 버전을 확인할 수 있다.


각 라이브러리의 좌측에 download.swc를 직접 다운로드 받아 자신의 프로젝트에 포함시킬 수도 있다. 하지만 여러분은 단순히 라이브러리를 이용하는 차원이 아니라 직접 디버깅하며 코드를 분석할 필요가 있을 수 있다. 이러한 경우에는 직접 라이브러리 프로젝트를 만들어 소스를 항상 참조하는 환경을 만들어야 한다. 이러한 환경을 만들때 참고해야할 사항은 다음과 같다.

첫째. Wonderfl에 공개된 라이브러리는 다양한 배포 경로를 가진다.
공개된 라이브러리는 다양한 배포 경로를 가진다. 가령, Google Code에 최종 버전을 압축해서 배포하는 사람도 있지만 그냥 SVN에만 올려놓는 사람도 있다. 또 자신의 웹사이트(TweenMax의 경우)에서 직접 배포하는 경우도 있고 Google이 아닌 다른 커뮤니티(betweenAS3, thread의 경우 libspark.org)에서 배포하는 경우도 있다. 어떤 경우는 소스를 공개하지 않고 SWC만 공개하는 경우도 있다.(Alternative3D의 경우) 우리는 다양한 배포 경로에 맞게 알아서 참고해 사용해야한다.

둘째. Wonderfl에 사용된 라이브러리의 버전을 확인한다.
공개된 라이브러리는 왠만하면 Wonderfl에서 사용하고 있는 버전을 이용하는 것이 좋다. 그렇지 않으면 Wonderfl 코드를 사용할 때 버전차이로 컴파일 에러가 발생할 수 있다.  


셋째. 라이브러리간 의존성을 확인한다.
가령, jiglibflash 라이브러리의 경우 기본적으로 Papervision3D, Sandy3D, Alternative3D, away3d, five3d 라이브러리가 있는 경우에만 컴파일을 할 수 있다. 만약 jiglibflash에서 away3d, five3d를 제외한 다른 라이브러리만 사용하는 경우에는 해당코드를 찾아 컴파일 대상에서 제외해야한다. 아래 화면은 jiglibflash의 속성에서 필요없다고 판단한 클래스를 제외시키는 방법을 보여준다.



위와 같은 3가지 참고사항에 따라 Flash Builder의 워크스페이스에 라이브러리 프로젝트를 만들면 다음과 같은 Package Explorer의 모습을 기대할 수 있다.



2. Wonderfl 애플리케이션 제작 
위 과정을 통해 라이브러리를 자신의 워크스페이스에 받아왔으면 이제 테스트를 위한 애플리케이션 제작 프로젝트를 만들 차례이다. 

첫째. Flash Builder의 File>New를 통해 ActionScript 3.0 프로젝트를 만든다. 필자는 프로젝트 이름을 wonderfl로 했다.
둘째. 만들어진 프로젝트에 libs 폴더를 만들고 소스가 공개되지 않은 SWC 파일을 복사해둔다.

셋째. 라이브러리 경로를 잡아준다. 지금까지 만들어놓은 라이브러리를 전부 사용하기 위해 먼저 "Add Project..."버튼을 눌러 라이브러리를 모두 등록한다. 또 "Add SWC Folder..." 버튼을 눌러 libs를 입력해 libs 폴더에 SWC를 사용하는 라이브러리로 등록한다.

넷째, .actionScriptProperites의 <excludedEntries>부분을 모두 삭제한다.
<excludedEntries>는 FlexSDK를 이용해 mxmlc가 컴파일하는 과정에서 필요없는 라이브러리를 빼는 역할을 한다. 하지만 Wonderfl에 공개된 코드들은 FlexSDK의 모든 라이브러리를 거의 100% 사용하므로 이 부분을 제거할 필요가 있는 것이다. 하지만 .actionScriptProperites 파일은 숨김파일이므로 Package Explorer 창에 바로 보이지 않는다. 이 부분에 대해서는 http://blog.jidolstar.com/665 를 참고하고 <excludedEntries>를 제거하길 바란다.

다섯째, HTML wrapper 파일은 삭제해준다.
HTML wrapper 파일을 웹브라우저에서 실행하는 것을 가정하에 Flash 애플리케이션이 자동으로 HTML에 Embed처리 되도록 생성해주는 일종의 템플릿 코드이다. wonderfl의 코드들이 단독 Flash Player 에서 제대로 동작하는 경우가 있다. HTML wrapper 설정을 해지함으로써 제대로 동작하지 않는 것을 방지할 수 있는데, 굳이 이 설정을 한 상태로 하고 테스트 하다가 stageWidth, stageHeight관련 문제가 발생하면 http://blog.jidolstar.com/656를 참고하길 바란다.

 
여섯째, Wonderfl에서 마음에 드는 소스 코드를 복사한다.
일곱째, default package에 ActionScript 파일을 만들고 복사한 Wonderfl 코드를 붙인다. 이 때 주의할 것은 파일명과 클래스 명은 동일해야한다. 이때 이름규칙을 정해주면 좋다. bitmap실험은 bitmap_로, 게임은 game_으로, papervision3d면 pv3d_로 시작하도록 만들어놓으면 관리하기 용이해진다.

여덟째, 만들어진 ActionScript 코드를 실행하기 위해 Default Application으로 설정한다. (아래화면 참고)
이것은 .actionScriptProperties 파일의 <applications>에 등록하는 과정중 하나이다. 직접 .actionScriptProperties를 편집해도 되겠다.

아홉째. 실행하고자 하는 ActionScript 파일을 열고 Shift+Alt+X,W를 눌러 실행한다.
열째. 실행코드에 Wonderfl의 출처를 남겨두는 것이 좋다!
아래처럼 Wonderfl에서 가져온 코드의 출처를 남겨두면 나중에 다시 참고할 수 있게 된다.

마지막으로, 일본어 주석은 일본어 번역기를 이용한다.
필자의 경험상 네이버 일본어 번역기가 원본코드를 깨뜨리지 않고 알맞게 번역해주었다. 활용하면 도움이 된다.


Wonderfl 테스트 환경을 한번에 구축하기!
지금까지 라이브러리와 개발환경을 구축하는 방법을 소개했다. 하지만 이러한 개발환경을 만든다는 것은 너무 버겁고 귀찮은 작업이다. 필자는 이러한 문제를 한방에 해결할 수 있는 솔루션을 여러분께 선물하고자 한다.



필자는 Wonderfl 코드의 테스트 환경을 쉽게 만들어주기 위해 Naver 개발자 센터의 오픈프로젝트로 "원더플 개발환경구축 프로젝트"를 만들었다. 이 페이지는 Google Code와 유사한 오픈 프로젝트이다. SVN을 지원해주기 때문에 이 기능을 이용해서 위에서 설명한 라이브러리들을 단 한번의 Checkout으로 개발 환경을 만들 수 있다.

이것을 수행하기 위해 먼저 다음과 같은 툴이 필요하다.



위 과정을 다했으면 기본 개발 환경이 구축된 것이다. 다음으로 SVN에서 라이브러리를 다음과정을 통해 모두 checkout 한다. 

 
  • Flash Builder 4를 실행한후 새로운 워크스페이스를 만든다.
  • 메뉴에서 File>Import를 통해 창이 열리면 SVN>Checkout Projects from SVN을 선택한다.
  • Create a new repository location을 선택후 Next 버튼 누른다.
  • https://dev.naver.com/svn/wonderfl/trunk를 입력한다. 아이디는 네이버 아이디, 비밀번호는 anonsvn이다. 아이디, 비밀번호 모두 anonsvn 이다.
  • 열려있는 모든 프로젝트를 전부 선택한 뒤 Finish 버튼을 누른다. 자동으로 SVN에서 프로젝트에 필요한 라이브러리와 자료를 워크스페이스에 포함시키게 된다. 5~10분 소요됨
위 과정을 모두 완료했다면 Package Explorer에 다음과 같이 라이브러리 프로젝트들이 생성될 것이다.


또한 init_template도 있을 것이다. 이것은 테스트 프로젝트를 자동으로 만들기 위한 파일을 모아둔 것이다. 이중에 init.xml은 ANT구동 파일로서 그 역할을 담당하게 된다.(참고로 이런 방법론은 찬익님과 Hika님의 글에서 아이디어를 얻었다. 두분께 감사한다.)

테스트 프로젝트는 다음과 같은 과정으로 만든다. (복잡해 보일지 모르겠지만 한번 해보면 이보다 쉬운 방법은 없다고 생각할 것이다. ^^)


  • 워크스페이스에 ActionScript 3.0 프로젝트 생성한다.
  • init_template내에 init.xml을 생성한 프로젝트의 root에 복사한다.
  • init.xml을 열어 "프로젝트 이름을 넣는다" 안에 프로젝트 명을 넣는다.
  • Ant View에 init.xml을 드래그 해서 붙인다. 3번 항목에 붙인 프로젝트 이름이 ANT View에 표시될 것이다. 
    만약 Ant View가 없다면 Windows>Show View>Other를 선택해 ANT를 찾아 선택하여 열어준다.
    만약 ANT가 없다면 Flash Builder에 ANT 플러그인이 설치되지 않은 것이므로 먼저 플러그인부터 설치해야한다. 참고 : http://blog.flashplatform.kr/213
  • Ant View에 붙은 해당 프로젝트 이름 옆에 (+) 버튼을 눌러 init가 나오면 두번 클릭해 실행한다. 
    이때 Console창에 마지막에 BUILD SUCCESSFUL이 나오면 ANT 실행을 성공한 것이다.
  • 생성한 프로젝트를 선택한 다음 F5를 눌러 새로고침한다.  
    다음과 같은 변화가 일어난다. 
    - Main.as가 만들어지고 기본 Application으로 등록된다.
    - libs 폴더에 SWC 파일들이 복사된다.
    - remote 폴더가 생긴다. 이 폴더는 외부자원를 로드하는 예제를 작성할때 외부자원을 이곳에 놓고 쓰면 되겠다.
    - HTML Wrapper 폴더가 삭제된다. 
    - Wonderfl에서 사용하는 기본 라이브러리가 자동으로 등록된다. 


아래 화면은 위 과정을 통해 구축된 애플리케이션의 Package Explorer 화면이다. 이제부터 default package에 필요한 수만큼 Wonderfl에서 가져온 actionscript 코드를 테스트 할 수 있게 되었다.


더불어, 위 과정을 통해 개인적으로 만든 테스트 프로젝트들 모음을 아래 링크를 통해 볼 수 있다.
http://dev.naver.com/scm/viewvc.php/branches/jidolstar/?root=wonderfl

SVN의 https://dev.naver.com/svn/wonderfl/branches/jidolstar 경로로 부터 직접 받아볼 수 있으니 참고 바란다.




정리하며
개인적으로 Wonderfl은 필자에게 매우 도움이 되는 사이트이다. 한국의 Flash 개발자들도 Wonderfl을 적극적으로 활용했으면 하는 바램을 가지는 마음에서 이 문서를 작성했다. 아무쪼록 도움이 되었길 바란다. 


참고사이트



글쓴이 : 지돌스타(http://blog.jidolstar.com/669)
이전에 "10만개 입자를 이용한 유체 시뮬레이션 실험"이라는 제목으로 글을 적었다. 이 글로 10만개의 1x1 픽셀의 입자를 되도록 빠르게 렌더링하기 위한 기술을 습득할 수 있었다.

이번에는 조금 더 실용적으로 접근한다. 이 실험은 입자 유체 시뮬레이션의 연장전으로 입자대신 화살표를 이용한다. 소개하고자 하는 것은 원래 일본의 Yasu 아이디를 가진 개발자가  Wonderfl에 단순한 학습자료 용도로 올린 소스였다. 이 소스는 계속 Fork(원본소스를 퍼가서 수정하는 작업)가 거듭됨에 따라 매우 흥미롭고 재미있는 실험으로 탈바꿈하기 시작했다. 수십번의 Fork와 수백번의 favorite로 지정된 이 실험은 필자로 하여금 Flash 속도 개선에 대한 아이디어를 얻을 수 있다는 기대를 가지게 하기에 충분했다.


최종 실험 결과 화면



이 글은 Yasu님의 블로그에 소개된 글을 다시 한번 검토해보고 분석하는 정도이다.
(참고로 Yasu님은 Wonderfl과 블로그에서 clockmaker 아이디로 활동중이며 꽤 재미있는 컨텐츠를 많이 생산하고 있다.)

앞으로 소개할 내용은 아래 첨부파일내 ActionScript 3.0 코드를  참고하길 바란다.  Flash Builder 또는 Flash CS4에서 Flash Player 10기반으로  테스트하면 되겠다. 





BitmapData 배열을 활용한 속도 증가

Yasu님은 그의 블로그를 통해 Flash 속도 개선을 위한 매우 좋은 아이디어를 알려주었다.

[다음 Flash의 속도를 체험] BitmapData를 배열에 저장하면 2 ~ 3 배 속도 - 일본어 번역기를 통해 보세요.


이 실험은 2가지 경우의 예를 들고 있다. (첨부파일의 bitmap_arrow_01.as, bitmap_arrow_02.as를 참고)

  1. 화살표를 정해진 수만큼 Sprite로 생성하고 회전/이동 처리 
  2. 화살표의 모양을 회전값에 따라 BitmapData로 만들어 배열에 저장한 뒤, 정해진 수만큼 화살표를 렌더링할 Bitmap을 만든다. 1번과 같이 Bitmap의 위치를 지정하되 화살표의 방향은 이미 만든 BitmapData를 이용하여 회전된 모양 처리

(Wonderfl이 아닌 첨부파일 소스에서)
1번 예제는 화살표 500개로 FPS(frame/seconds)가 50정도의 속도가 나온다. 2번예제는 화살표 3000개로 FPS 50정도 나온다. 같은 FPS이지만 화살표를 더 렌더링할 수 있는 2번 예제의 경우가 훨씬 빠르다는 것을 알 수 있다. (물론 FPS는 개인 PC사양에 따라 다르게 나온다.)

일반적으로 많은 Flash 입문자가 처음 접하는 예제는 1번 일것이다. 하지만 화살표 모양의 Vector 데이터가 500개나 되기 때문에 회전, 이동때 마다 많은 수의 Vector정보를 렌더링하기 위해 계산이 많아질 수 밖에 없다. 2번 예제의 경우에는 이 문제를 해결하기 위해 화살표 회전값에 따라 미리 BitmapData를 만들어 놓고 실제 렌더링시에는 회전각도에 따라 선별적으로 미리 만든 해당 화살표 BitmapData를 가져와 사용할 수 있도록 만들었다. 일단 이렇게 하면 렌더링을 위한 계산량이 급격하게 떨어지므로 FPS 증가를 도모할 수 있게 된다.

이것만 보더라도 1번 예제처럼 만들고 Flash는 너무 느려요 라고 말하시는 분들이 있다면 반성해야한다. 어떤 언어로 만들던지 1번처럼 만들면 당연히 느려진다. 이것은 필요하다면 속도 개선을 위한 노력이 필요하다는 것을 말해주는 단적인 아주 좋은 예제이다. ^^


Fork! Fork!

Wonderfl 사이트의 강점 중에 하나가 바로 Fork 기능이다. 이를 이용해 다른 사람이 만들어 놓은 ActionScript 코드를 더욱 개선하거나 발전시킨 코드로 만들 수 있고 공유할 수 있다. 실제로 이 기능을 통해 Yasu님이 올려놓은 코드는 바로 다른 개발자들의 입맛에 맛게 다음과 같은 암묵적인 약속에 의해 변경되기 시작했다. 

  1. 속도에 따라 화살표 색이 달라진다.(빠를수록 빨강, 느릴수록 파랑)
  2. 속도가 빠른 화살표가 가장 화면 상단에 배치된다. 
  3. 위 조건으로 했을 때 최대 속도를 내도록 코드를 갱신한다.

즉, 위 조건은 보는 이로 하여금 더욱 예쁘고 멋진 효과를 보여주도록 하는 구체적인 목표인 것이다. 

이 시발점은 Wonderfl 아이디 keno42 님으로부터 시작한다.

속도개선 전, 색깔을 입힌 화살표 모습


- 화살표의 속도에 따라 색과 레이어를 달리 지정하도록 수정 (첨부파일의 bitmap_arrow_04.as 참고)

확실히 밋밋했던 화살표가 색이 들어가니 보기가 좋아졌다. 하지만 이에 따라 FPS가 급격하게 떨어지게 되었다. 가장 큰 이유는 2번 조건때문에 화살표의 레이어 위치를 수시로 변동시켜 주는 아래와 같은 코드가 추가되었기 때문이다.

arrow.parent.removeChild(arrow);
worldAlphaChildren[speed].addChild(arrow);	

이 때문에 FPS가 급격히 줄어들어 앞선 실험에서 사용된 화살표 3000개를 1000개로 줄일 수 밖에 없었다. 이렇게 해서야 FPS 57정도 나왔다. 


(참고)Fork된 코드는 diff기능으로 비교하자.


Wonderfl의 강력한 기능중에 하나는 Fork된 원본코드와 Fork를 통해 수정된 코드를 비교할 수 있다는 것이다. 위 Wonderl의 캡쳐 화면에서 볼 수 있듯이 Fork한 코드는 forked from과 원래 제작자 및 코드의 제목이 나와 있고 그 옆에 diff(104)가 표시되어 있다. 이 말은 이 소스가 원본 소스와 104개 줄이 추가/삭제/수정 되었다는 것을 의미한다. 이것을 클릭하면 다음과 같은 화면이 나온다. 


이것을 사용하면 어느부분이 수정되었는지 바로 알 수 있기 때문에 매우 유용하다.


속도개선 1 (bitmap_arrow_05.as 참고)

첫번째로 생각한 것은 마우스 이벤트 전파부분이였다. 빠른 속도를 가진 화살표가 위로 올라오게 하는 조건을 만들기 위해 앞선 코드에서는 단계별 레이어를 만들었다. 이 레이어는  Sprite로 만들어졌다. Sprite는 DisplayObjectContainer를 확장한 클래스로 자식을 가질 수 있는 시각객체를 렌더링하는데 기본이 되는 클래스중 하나이다. 시각객체들은 자식과 부모관계(addChild()에 의해)를 가지게 되면 이벤트 전파 메커니즘에 따라 이벤트가 전파된다. 그중에 마우스 이벤트가 대표적이다. 하지만 이 이벤트 메커니즘은 Flash 속도 저하에 큰 요소가 되기도 한다. 그러므로 이벤트 전파가 필요 없는 곳에는 사용하지 않도록 강제로 설정해줄 필요가 있다. 그 역할을 하는 것은 Sprite속성중에 mouseChildren, mouseEnabled 이다. 화살표가 올라갈 부모로서의 Sprite 레이어들은 어떠한 마우스 이벤트를 받을 필요가 없기 때문에 이들 속성을 모두 false로 지정하도록 한다. 이 설정으로 약간의 FPS개선이 있었다. 

두번째로 arrow.parent.removeChild(arrow); 부분을 삭제하는 것이였다. 시각객체는 2개 이상의 부모가 존재할 수 없다. 그러므로 otherParent.addChild(arrow); 하는 것만으로 arrow.parent.removeChild(arrow)가 이미 실행된 것이다. 이는 비싼 실행 비용을 지불하므로 제거하면 확실히 속도 개선이 된다. 실제로 FPS가 70이상으로 개선되며 거의 20이상 증가하게 되었다. 


속도개선 2 (bitmap_arrow_06.as 참고)
아직도 비싼 실행 비용을 지불하는 것이 있다. 바로 addChild()이다. 속도에 따라 레이어를 변경하더라도 굳이 변경할 필요가 없는 경우도 있다. 이러한 경우에는 선별적으로 변경되도록 해야한다. 아래 코드처럼 단순한 조건문 하나 넣어 쓸데없이 addChild를 실행하지 않게 하는 것만으로 속도 개선이 된다. 

if (arrow.parent != world.getChildAt(speed)) {
	Sprite(world.getChildAt(speed)).addChild(arrow);
}
이 작업으로 무려 FPS가 90 이상으로 상승했다. 속도개선 1 보다 약 20정도가 증가했다. 


속도개선 3 (bitmap_arrow_07.as 참고) 
속도개선 2에서 getChildAt()하는 것은 그리 빠른 방법이 아니다. 그래서 Array를 이용해 아래와 같이 바꾸게 된다.
if( arrow.parent != childrenArr[speed] )
	childrenArr[speed].addChild(arrow);	
원래 실험자는 개선이 있었다고 하나 필자는 그리 큰 개선은 못보았다.

속도개선 4 (bitmap_arrow_08.as 참고)
화살표는 정사각형 영역이 아닌 직사각형 영역에 그려진다. 무슨말인가? 회전된 화살표가 그려지는 영역은 항상 작아졌다가 커졌다가 한다. 이는 화살표의 영역을 담은 BitmapData가 실제 화살표 크기보다 클 수 있다는 것을 의미한다. 실제 화살표 크기보다 큰 BitmapData에서 쓸데 없는 부분을 제거하는 작업을 트리밍(trimming)이라고 한다. 이 작업으로 실제로 렌더링하는 화살표의 적당한 크기의 영역만을 가진 BitmapData를 만들기 때문에 물리적인 렌더링 시간 향상에 도움을 줄 수 있다.

while (i--) { //각도에 따라 화살표 비트맵 생성 
	matrix=new Matrix();
	matrix.translate(-11, -11);
	matrix.rotate((360 / ROT_STEPS * i) * Math.PI / 180);
	matrix.translate(11, 11);
	
	temp = new BitmapData(22,22,true,0x0);
	temp.draw(dummyHolder, matrix);

	//트리밍 처리(중심점의 조정은 하지 않는다)
	rect = temp.getColorBoundsRect(0xff000000, 0x00000000); //알파채널이 0이 아닌 사각형 이미지 경계 
	rotArr[i + k]=new BitmapData(rect.width, rect.height, true, 0x0);
	rotArr[i + k].copyPixels(temp, rect, new Point(0,0));
}

위 코드에서 보는데로 BitmapData의 getColorBoundsRect() 메소드를 이용해 트리밍 처리를 하고 있다. 이 작업으로 FPS를 거의 100까지 올릴 수 있었다.


속도개선 5 (bitmap_arrow_09.as 참고)
지금까지의 방식은 화살표 하나를 표현하는 도구로 Bitmap을 사용했다. 이것은 시각객체(DisplayObject)의 하나이다. 이 방식은 화살표 1000개를 그릴려면 시각객체 1000개가 필요하다는 말과 같다. 속도개선 1~4까지 하면서 사실 거의 최대한으로 속도개선했다. 이제 속도개선에 기대할 수 있는 마지막 단계는 바로 화살표를 렌더링하는 방법 조차도 BitmapData 를 이용하는 것이다. 이때는 1000개의 시각객체가 아니라 1000개의 위치/방향 정보를 담는 객체가 필요하므로 일단 상대적인 메모리 부담이 줄어든다. 그리고 기존 방식과 거의 동일하나 그려지는 Canvas가 이제는 Sprite를 이용한 레이어가 아니라 BitmapData 하나가 된다. 그러므로 화살표는 BitmapData의 copyPixels()메소드를 이용해 그리게 된다. 또한 화살표의 속도에 따른 위치를 정렬하기 위해서 Array.sortOn()함수를 이용한다. 

결과적으로 FPS가 120까지 상승했으며 초반에 60미만이였던 것에 비해서 거의 2배이상 빨라졌다. 그뿐인가? ColorTransform적용으로 화살표 궤적도 보여줄 수 있게 되었다. 

속도개선 최종화면



실행 : http://wonderfl.net/code/834201fba6c3562ca6fd7a0f1f229138b263b446 


조금더 예쁘게!!!!
지금까지 속도개선은 모두 Wonderfl의 Fork기능을 통해 각각 다른 사람들에 의해 구현된 것들이다. 최초에 화살표를 이용한 예제를 만든 Yasu님은 자신의 블로그에 지금까지 필자가 다루웠던 내용을 요약하면서 마지막으로 한번더 Fork를해 더 예쁘게 보여주기 위한 코드로 수정했다. 

마지막으로 더 멋진 화면을 만들기 위한 작업 결과




분명히 이전 코드와 속도상에 차이는 없으면서 더욱 아름다운(?) 결과물을 도출해냈다. 

그리고 1000개의 화살표가 아닌 3000개로 바꾸어도 FPS가 40이상 나왔다. 


정리하며
1픽셀짜리 데이터를 다룰때는 10만개나 다루었다가 그와는 상대적으로 너무 적은 3000개 화살표 렌더링하는 것으로 전환되면서 너무 숫자가 급감하는 것처럼 보일 수 있다. 하지만 실제로 렌더링하는 데이터 요소는 10만픽셀 이상으로 많은 데이터를 다루는 것이다.(조금만 생각하면 감이 올거다.)  렌더링 자원 숫자를 줄이는 것도 중요하지만, 그 자원이 어떤 조건의 것인가와 어떻게 렌더링 할 것인가도 역시 중요하다. 결국, 화면 렌더링을 어떻게 하느냐에 따라 Flash 애플리케이션의 속도와 밀접한 관계가 있는 것이다. 실무에서 제약된 환경의 디바이스나 서비스의 경우라면 이러한 노력은 필수이다. 

이 글에 소개된 아이디어는 정답은 아니다. 더 분석하고 더 최적화할 여지가 있다. 

Wonderfl은 아주 고급의 스킬을 다루지는 않지만 적어도 중고급 실력에 도달하기 위한 예제를 찾는데는 이만한 곳이 없다고 생각한다. 필자는 이곳에 올라온 코드들을 보면서 수없이 감격하고 있다. 

마지막으로 라이브 코딩을  SNS 영역으로까지 승격한 Wonderfl을 이용해 다양한 실험들을 하는 일본의 개발 문화를 보면서 항상 감탄한다. 아무쪼록 이러한 일본의 개발 문화가 한국에도 정착되길 희망한다. 


글쓴이 : 지돌스타(http://blog.jidolstar.com/671)
2009년 봄, 일본에서 ActionScript 개발자들간에 재미있는 실험이 있었다. 그것은 "Flash속도를 극대화하기" 실험중 하나로 수만개의 입자를 가지고 가상의 유체 시뮬레이션을 보여주는 예제를 만드는 것이였다.

수만개의 입자를 이용한 유체 시뮬레이션 실행


위 화면은 최초실험대상이 된 1만개 입자 유체 시뮬레이션 코드의 실행화면이다.

아래 소스는 위에서 소개된 소스를 10만개 입자로 수정하고 최대 frameRate를 110으로 조정한 것이다. 또한 마우스 클릭으로 초기화 하는 대신 500ms단위로 유체의 이동패턴과 전체색이 조금씩 변하도록 수정했다.

package {
	import flash.display.*;
	import flash.events.*;
	import flash.geom.*;
	import flash.utils.*;
	
	import net.hires.debug.Stats;
	
	[SWF(width="465", height="465", backgroundColor="0x000000", frameRate="110")];
	/**
	 * BitmapData를 이용한 파티클 렌더링 속도 테스트 
	 * @see http://clockmaker.jp/blog/2009/04/particle/
	 */ 
	public class bitmap_liquid100000 extends Sprite {
		
		private const nums:uint=100000;
		private var bmpDat:BitmapData;
		private var vectorDat:BitmapData;
		private var randomSeed:uint;
		private var bmp:Bitmap;
		private var vectorList:Array;
		private var rect:Rectangle;
		private var cTra:ColorTransform;
		private var vR:Number;
		private var vG:Number;		
		private var timer:Timer;
		
		public function bitmap_liquid100000() {
			initialize();
		}
		
		private function initialize():void {
			//stage 관련의 설정
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			stage.frameRate=110;
			
			//파티클이 렌더링 되는 메인 Bitmap. 
			bmpDat=new BitmapData(465, 465, false, 0x000000);
			bmp=new Bitmap(bmpDat);
			addChild(bmp);
			
			//파티클의 가속도를 계산하기 위한 도움 BitmapData로서 perlinNoise가 적용된다. 
			vectorDat=new BitmapData(465, 465, false, 0x000000);
			randomSeed=Math.floor(Math.random() * 0xFFFF);
			vectorDat.perlinNoise(230, 230, 4, randomSeed, false, true, 1 | 2 | 0 | 0);
			//addChild(new Bitmap(vectorDat)); //만약 이 perlinNoise가 적용된 Bitmap을 보고 싶다면 주석을 풀자 
			
			//화면크기 
			rect=new Rectangle(0, 0, 465, 465);
			
			//파티클 궤적을 그리기 위함 
			cTra=new ColorTransform(.8, .8, .9, 1.0);
			vR = 0;
			vG = 0;
			
			//파티클을 넣기 위한 List
			vectorList=new Array();
			for (var i:uint=0; i < nums; i++) {
				//파티클 위치
				var px:Number=Math.random() * 465;
				var py:Number=Math.random() * 465;
				var pv:Point=new Point(px, py);
				//파티클 가속도
				var av:Point=new Point(0, 0);
				//파티클 속도 
				var vv:Point=new Point(0, 0);
				//파티클 위치,가속도,속도 정보를 List에 저장 
				var hoge:VectorDat=new VectorDat(av, vv, pv);
				vectorList.push(hoge);
			}
			
			//지속적인 파티클 렌더링을 위한 loop 함수 호출 
			addEventListener(Event.ENTER_FRAME, loop);
			
			//500ms마다 
			timer = new Timer(500, 0);
			timer.addEventListener(TimerEvent.TIMER, resetFunc);
			timer.start();
			
			//통계 
			addChild(new Stats);
		}
		
		private function loop(e:Event):void {
			//렌더링용 BitmapData를 colorTransform로 어둡게 하여 기존에 그려진 파티클의 궤적을 보이도록 함 
			bmpDat.colorTransform(rect, cTra);
			
			//파티클의 위치를 재계산하여 렌더링한다.
			var list:Array=vectorList;
			var len:uint=list.length;
			for (var i:uint=0; i < len; i++) {
				var dots:VectorDat=list[i];
				var col:Number=vectorDat.getPixel(dots.pv.x, dots.pv.y);
				var r:uint=col >> 16 & 0xff;
				var g:uint=col >> 8 & 0xff;
				dots.av.x+=(r - 128) * .0005; //적색을 x축 가속도로 사용
				dots.av.y+=(g - 128) * .0005; //녹색을 y축 가속도로 사용
				dots.vv.x+=dots.av.x;
				dots.vv.y+=dots.av.y;
				dots.pv.x+=dots.vv.x;
				dots.pv.y+=dots.vv.y;
				
				var _posX:Number=dots.pv.x;
				var _posY:Number=dots.pv.y;
				
				dots.av.x*=.96;
				dots.av.y*=.96;
				dots.vv.x*=.92;
				dots.vv.y*=.92;
				
				//stage 밖으로 이동했을 경우 처리. 3항 연산자 처리함 
				(_posX > 465) ? dots.pv.x=0 : (_posX < 0) ? dots.pv.x=465 : 0;
				(_posY > 465) ? dots.pv.y=0 : (_posY < 0) ? dots.pv.y=465 : 0;
				
				//1*1 pixel을 bitmapData에 렌더링 
				bmpDat.fillRect(new Rectangle(dots.pv.x, dots.pv.y, 1, 1), 0xFFFFFF);
			}
		}
		
		private var seed:Number = Math.floor( Math.random() * 0xFFFF );
		private var offset:Array = [new Point(), new Point()];
		private function resetFunc(e:Event) :void{
			//파티클의 가속도를 계산하기 위한 도움 BitmapData로서 perlinNoise를 변경 
			vectorDat.perlinNoise( 230, 230, 3, seed, false, true, 1|2|0|0, false, offset );
			offset[0].x += 20;
			offset[1].y += 20;
			
			//파티클 궤적을 표시하기 위한 부분을 변경  (조금씩 색변동이 일어난다)
			var dots:VectorDat = vectorList[0];
			vR += .001 * (dots.pv.x-232)/465;
			vG += .001 * (dots.pv.y-232)/465;
			( vR > .01 ) ? vR = .01:
				( vR < -.01 ) ? vR = -.01:0;
			( vG > .01 ) ? vG = .01:
				( vG < -.01 ) ? vG = -.01:0;
			
			cTra.redMultiplier += vR;
			cTra.blueMultiplier += vG;
			( cTra.redMultiplier > .9 ) ? cTra.redMultiplier = .9:
				( cTra.redMultiplier < .5 ) ? cTra.redMultiplier = .5:cTra.redMultiplier;
			( cTra.blueMultiplier > .9 ) ? cTra.blueMultiplier = .9:
				( cTra.blueMultiplier < .5 ) ? cTra.blueMultiplier = .5:cTra.blueMultiplier;         
		}
	}
}

import flash.geom.Point;

class VectorDat {
	public var vv:Point;
	public var av:Point;
	public var pv:Point;
	
	function VectorDat(_av:Point, _vv:Point, _pv:Point) {
		vv=_vv;
		av=_av;
		pv=_pv;
	}
}

처음 이 소스를 본다면 뭔가 굉장히 복잡할 것이라고 생각할지 모른다. 지금껏 이런 것을 구현하기 위해 ActionScript 3.0의 DisplayObject객체 기반으로 입자를 구현하는 것을 먼저 생각한 분이라면 위 소스가 더 어렵게 느껴질 것이다. 참고로 만약 10만개의 입자를 전부 DisplayObject 객체로 구현하면 Flash Player는 제대로 작동도 못하고 중지될 것이다. DisplayObject와 그것을 확장한 화면 표시 객체는 다소 무겁다. Flash Player가 이런 무거운 객체를 10만개나 frameRate가 24 수준으로 그리는 것은 일반 보급 컴퓨터에서는 불가능하다.

위 소스를 잘보면 알겠지만 입자를 표현하기 위해 BitmapData(bmpDat변수 참고)를 이용한다. BitmapData 하나에 모든 입자 정보가 표현되도록 하는 것이 첫번째 아이디어이다. 이렇게 함으로써 10만개 입자 렌더링이 가능해진다.

처음 실행하면 무작위로 지정된 위치에 입자들이 배치되었다가 무작위 궤적을 따라 이동하는 것을 볼 수 있다. 이러한 이동이 뭔가 엄청나게 고난이도 물리엔진  알고리즘이 들어간 것 같지만 실상은 전혀 아니다. 입자의 위치를 이용해 가속도와 속도를 계산하는데 사용된 것은 다름 아닌 BitmapData(vectorDat변수 참고)이다. 이 BitmapData는 실제화면에는 눈에 보이지 않는다.  대신 가속도와 속도를 계산하기 위해 BitmapData에 PerlinNoise를 주어 입자의 위치정보에 해당하는 색정보를 얻어 색의 RGB값중 R값은 x축 가속도값으로 G값은 y축 가속도 값으로 참조하도록 구현되어 있다. PerlinNoise가 적용된 BitmapData는 아래와 같다. 실제로 이 화면을 보고 싶다면 위 코드의  "//addChild(new Bitmap(vectorDat))" 부분을 찾아 주석처리를 삭제하고 실행해 보면 된다.  

필자는 개인적으로 PerlinNoise 대신이 뭔가 물리학적 정보가 들어간다면 훨씬더 실용성 있는 재미난 교육용 자재로 만들 수 있지 않을까 생각했다.

입자의 가속도 계산을 위한 PerlinNoise가 적용된 BitmapData의 모습. 500ms단위로 계속 변한다.


BitmapData에 입자들의 위치를 갱신하는 것까지는 알겠는데... 입자들이 특정한 궤적을 남기도록 하는 효과는 도데체 어떻게 하는 것일가? 따로 궤적을 다루는 Array나 List가 있는 것일까? 아니다. ColorTransform 클래스에 비밀이 숨겨져 있다. loop() 함수를 보면 bmpDat.colorTransform(rect, cTra); 부분이 있다 .rect은 colorTransform을 적용할 범위이고 cTra가 적용할 ColorTransform객체이다. 이 객체에는 cTra.redMultiplier, cTra.greenMultiplier, cTra.blueMultiplier 속성등이 있다. 이 값은 기존 BitmapData에 RGB값을 해당값을 정해진 값으로 곱해주는 역할을 하는데 가령 cTra.redMultiplier가 0.8이면 loop를 돌때마가 Red값이 0.8배로 감소하게 된다. 결국 입자의 흰색(0xffffff)로 BitmapData에 찍히면 cTra.redMultiplier에 의해 계속 어두워지다가 결국 검은색이 된다. 이러한 원리로 궤적이 남게 되는거다.

하지만 위 코드는 문제가 있다. 실제로 실행해보면 필자의 컴퓨터에서는 frameRate가 10도 안나왔다. 최대치 110을 주었음에도 이 정도 밖에 안나왔다는 것에 대해 개발자로서 계산 및 렌더링 최적화에 관심을 자연스럽게 가지게 된다.

일본 개발자들은 Wonderfl의 "Fork"기능을 이용해 서로 소스를 공유하며 위 코드의 문제를 계속 개선해 나갔다. 

아래 압축파일은 필자가 여러 단계를 거쳐서 실험했던 코드를 압축한 것이다.



개선사항 1. BitmapData의 lock()과 unlock() 함수 추가 (bitmap_liquid100000_01.as)
BitmapData가 Bitmap을 통해 화면에 이미 렌더링 자원으로 활용되고 있다면 BitmapData가 수정될 때 마다 Bitmap에도 갱신한다.(즉, addChild(new Bitmap(bitmapData))일때를 의미한다.)  구체적으로 설명하자면 입자가 10만개 이므로 입자의 위치가 바뀔때마다 10만번의 BitmapData를 수정하면 그때마다 Bitmap에도 영향을 준다. 만약 BitmapData을 수정하고 난 다음에 마지막에 Bitmap에 적용될 수 있도록 하면 속도개선에 도움을 줄 것이다. 이것을 가능하게 하는 함수가 BitmapData의 lock(), unlock()함수이다. 위 코드에서 loop()함수에 맨처음과 맨끝부분에 각각 bmpDat.lock(), bmpDat.unlock()을 추가하자. 이 작업으로 전체적으로 2~3 frameRate 개선을 볼 수 있었다. frameRate가 이 실험에서는 크게 개선되지 않았지만 만약 BitmapData에 이보다 더욱 복잡한 수정이 있거나 더 크기가 크면 클수록 그 차이가 더 할 것이다.
private function loop(e:Event):void {
	bmpDat.lock();
	(생략)
	bmpDat.unlock();
}



개선사항 2. fillRect을 setPixel로 변경 (bitmap_liquid100000_02.as)
위 코드에서 가장 잘못된 부분중에 하나가 1px짜리 입자를 렌더링하기 위해 fillRect()을 이용했다는 것이다. 이 함수 대신 setPixel()로 수정한다. 이 작업으로 25~30 frameRate 개선을 했다. 여기까지만 하더라도 상당한 진전이다.

개선사항 3. point 대신 number로 (bitmap_liquid100000_03.as)
위 코드에서 비효율적인 코드부분이 있다. 바로 입자의 정보를 담은 VectorDat부분이다.

import flash.geom.Point;

class VectorDat {
	public var vv:Point; //속도
	public var av:Point; //가속도
	public var pv:Point; //위치 
	
	function VectorDat(_av:Point, _vv:Point, _pv:Point) {
		vv=_vv;
		av=_av;
		pv=_pv;
	}
}

위 클래스는 입자의 위치,속도,가속도 정보를 Point를 이용해 만들었다. 이렇게 하게 되면 10만개나 되는 입자의 위치를 vectorDat.pv.x, vectorDat.pv.y 형태로 접근해야 한다. 이것 대신 vectorDat.px, vectorDat.py로 하면 훨씬 빠른 접근이 가능해질 것이다. 반복되는 로직에 깊은 DOM을 접근을 사용하면 그만큼 느려진다는 것을 항상 염두할 필요가 있다. 다음 코드는 위 클래스를 개선한 것인다.

class VectorDat {
	public var vx:Number = 0; //x축 속도
	public var vy:Number = 0; //y축 속도
	public var ax:Number = 0; //x축 가속도
	public var ay:Number = 0; //y축 가속도
	public var px:Number; //x축 위치 
	public var py:Number; //y축 위치 
	
	function VectorDat( px:Number, py:Number ) {
		this.px = px;
		this.py = py;
	}
}

위 클래스에 맞게 전체 소스를 수정한 뒤 실행하면 frameRate가 10이상 개선되는 것을 확인할 수 있었다. 꽤 큰 진전이다.


개선사항 4. 가속도 계산을 위한 bitmapData 크기 조정 (bitmap_liquid100000_06.as)

BitmapData의 getPixel은 BitmapData의 크기가 크면 클수록 그 효율이 떨어진다. 위 코드에서 입자의 가속도를 계산하기 위해 perlinNoise를 적용한 BitmapData(vectorDat)는 렌더링하기 위한 BitmapData(bmpDat)와 크기가 동일하다. 이 크기를 465x465 대신 28x28로 줄이고 loop()함수내에 col=vectorDat.getPixel(dots.px, dots.py)대신 col=vectorDat.getPixel(dots.px>>4, dots.py>>4)로 수정해서 테스트하면 frameRate가 위 개선사항을 모두 적용한 것보다 약 10이상 증가한다. 



지금까지 개선사항 4가지를 통해 최초 코드의 frameRate를 12에서 60~70까지 끌어올릴 수 있었다. 아래는 실행화면이다.

실행 화면


결과 보러 가기 : http://wonderfl.net/code/2e15897ec742a2e2c09255bcba35c2b20a026356


다른 실험들...

필자는 VectorDat를 담는 List의 형태를 Array에서 Vector로 바꿔서 실험을 해봤다. 이 부분에 있어서는 거의 frameRate변화가 없었다. (bitmap_liquid100000_04.as참고)

또한 List형태를 Linked List로 바꿔봤는데.. 오히려 frameRate가 감소했다.  (bitmap_liquid100000_05.as참고)

마지막으로 BitmapData의 setPixel()보다 setVector()가 더 빠르다기에 그것으로 실험해 보았다. 하지만 이 방법은 위 실험에 쓰기에 적합하지 않았다. 왜냐하면 단순히 setPixel()과 setVector()만 놓고 보았을때 setVector()가 더 빠르지만 setVector()를 사용하기 위해서는 getVector()와  같은 부가적인 코드가 더 들어가게 되었고 그 코드들로 인해 오히려 frameRate가 더 줄어들었다. 그러므로 뭔가 도입할때는 그 상황에 맞게 도입해야한다. (bitmap_liquid100000_07.as참고)


추가사항 



웹눈님께서 이글을 보고 위 프로그램을 만들었는데 mouseX, mouseY 엑세스 비용문제를 해결하지 못해 속도 저하가 일어났었다.

while(pt.next) {
   var dx:Number = pt.x - mouseX;
   var dy:Number = pt.y - mouseY;
   (중간생략)
   pt = pt.next;
}


위처럼 만드셨는데...이렇게 하면 입자(파티클)이 10만개면 10번 mouseX, mouseY를 참조하게 된다. 이 속성을 엑세스 하는 것은 꽤 비싼 자원이 소비되므로 아래처럼 하면 속도개선 된다.

var mx:Number, my:Number;
mx = mouseX;
my = mouseY;
while(pt.next) {
   var dx:Number = pt.x - mx;
   var dy:Number = pt.y - my;
   (중간생략)
   pt = pt.next;
}


테스트 결과, 저렇게 바꾸는 것만으로 FPS(frame per seconds)가 무려 40에서 90까지 개선되었다.
별거 없는데도 엄청난 속도 개선이다! 


정리하며

일본에서 진행된 이 입자 유체 시뮬레이션 실험은 꽤 인기를 얻었으며 Wonderfl 사이트의 favorite와 fork기능을 통해 계속 퍼져나갔다. 필자도 예전부터 봐왔던 실험이였으나 BitmapData 활용을  이미지 에디터 정도의 프로젝트 외에는 거의 사용을 하지 않아서 깊숙히 파고들지 않았다. 

이런 단순한 실험이 일본의 여러 Flash 개발자들 사이에 인기를 끌고 함께 코드를 수정하는 모습을 보면서 그들의 개발분위기가 무척 부러웠다. 한국에도 이런 분위기가 정착되길 바란다. 

필자는 지금도 Wonderfl을 하루에도 수십번 방문한다.


참고글
Wonderfl - build Flash online
빠른 flash 연구회를 wonderfl에서
Flash 10, Massive amounts of 3D particles with Alchemy (source included).
Massive amounts of 3D particles without Alchemy and PixelBender
Flash 10, Massive amounts of 3D Particles (with haXe)
Using BitmapData.setVector for better performance


글쓴이 : 지돌스타(http://blog.jidolstar.com/670
 

오랜만에 Adobe Labs를 가봤는데 마침 Flash Builder 4, ColdFusion Builder, Flex 4 SDK가 베타 딱지를 떼고 정식으로 배포되기 시작했네요.

  • Adobe ColdFusion Builder
  • Adobe Flash Builder™ 4
  • Adobe Flex® 4 SDK

    위 링크를 통해 자세한 정보를 볼 수 있습니다.
  • 추가사항 1
    정영훈님께서 유용한 댓글을 달아주셨습니다. 이클립스에 Flash Builder를 Plug-in 형태로 설치할 때 멈추는 현상이 있는데 아래 내용으로 문제를 해결할 수 있습니다.

    설치할때 우선 압축을 풀게 되는데 그 경로를 지정할 수가 있습니다.
    그 압축을 푸는 경로에 하글이 섞여 있으면 설치가 안됩니다.

    xp에서는 바탕화면 에 압축을 풀게 딜포트 설정으로 나옵니다
    그 경로를 한글이 없는 경로로 수정하면 설치가 가능합니다.


     

    글쓴이 : 지돌스타(http://blog.jidolstar.com/667)


    Flash 애플리케이션을 제작할때 항상 고민되는 것은 메모리와 속도일 것이다. 이 문서는 이러한 고민을 했던 분들을 위해 본인의 경험을 반영하여 작성한 것이다. 주의할 것은 이 문서에서 다루는 Object Pool은 메모리, 속도 개선을 위한 하나의 방법일 뿐이지 전부는 아니다라는 것이다. Object Pool이 어떻게 당신의 애플리케이션의 속도와 메모리를 개선시켜줄 것인가 전달하는 것이 문서의 목표이다.

    목차

    1. 객체 관리에 대한 질문!
    2. new 연산자는 비싸다!
    3. Object Pool이란?
    4. Object Pool을 도입할 때 고려사항
    5. Object Pool을 사용해보자.
    6. Object Pool과 new 연산자 사용 비교
    7. 메모리 사용의 폭이 급격한 애플리케이션은 좋지 못하다.
    8. 재사용할 수 있는 클래스 제작
    9. 정리하며
    10. 참고글



    1. 객체 관리에 대한 질문!
    제작된 애플리케이션에 필요한 최소한의 메모리에 대해서 생각해 본적이 있는가? 하나의 애플리케이션에 들어가는 최소 자원(resource)은 얼마나 될까? 그 자원은 얼마나 자주 사용하고 버리는 것일까?

    슈팅 게임을 보면 동일한 적군전투기가 많이 나온다. 그 중에 적군전투기1이 있다고 하자. 적군전투기1이 한 화면에 5개가 나왔다. 아군전투기에 의해 죽든 화면에서 사라지든 어쨌든지 언젠간 화면에서 사라지고 쓸모없게 된다. 좀 있다가 또 적군전투기1이 3개가 나온다. 또 반복해서 사라진다. 당신이 이 슈팅 게임을 만든다면 적군전투기1 관리를 어떻게 할 것인가? 즉, 화면에 나타날때 new로 생성하고 사라지면 delete로 메모리에게 해지요청할 것인가? 아니면 어느 특정 영역에 화면에 나타날 전투기1을 미리 5개에서 10개 생성해놓고 필요할 때마다 재사용할 것인가?

    2. new 연산자는 비싸다!
    new는 클래스를 통해 객체를 생성하는 연산자이다. 이는 비교적 비용이 비싼편이다. 비용이 비싸다는 말을 구체적으로 설명하자면 new를 통해 객체를 생성하여 메모리에 올리고 다 사용했다고 판단될때 delete이나 null등을 이용해 객체참조를 제거하여 가비지 컬렉터에 맡기게 되며 결과적으로 가비지 컬렉터는 이 객체를 어느 특정시점에 제거하는 일련의 객체 생성과 삭제까지 동작이 꽤 복잡하여 메모리, CPU연산등의 자원소비가 크다라는 것을 의미한다. 특히나 클래스의 크기가 크고 복잡하다면 그 비용은 더욱 갑절로 들게 된다. new 연산 사용은 이처럼 비싼 비용을 지불해야 하기 때문에 할 수 있다면 최소한으로 사용하도록 하는 것이 속도와 메모리 관리에 도움을 준다.

    앞서 설명한 슈팅 게임을 보자면 적군전투기1은 수시로 보였다가 안보였다가 한다. 이 경우 new, delete를 반복한다면 경우에 따라서 엄청난(?) 비싼 비용을 지불하게 된다.

    다른 예를 들어보겠다. 본인은 얼마전에 스타플(http://starpl.com)이라는 사이트에서 제공하는 타임라인 애플리케이션을 Flash로 개발했다. 타임라인은 사용자의 기록, 앨범등을 시간순으로 보여주게 되며 좌우측으로 이동해서 내 인생의 기록을 보는듯한 느낌을 주도록 만들었다. 이때 화면에 보여지는 사용자의 기록들이 수시로 보여졌다가 사라진다. 이때 사용된 기록을 보여주는 시각객체(DisplayObject)를 new와 delete를 반복해서 생성 및 삭제한다면 너무 비싼 비용을 들이는 것과 같다.

    스타플의 타임라인

    참고글 : 스타플 타임라인 업그레이드 및 앨범 기능 추가 소식

    앞의 2가지 예에서 언급했듯이 자주 보여지고 사라지는 객체를 필요할 때마다 new연산자를 사용하는 것보다는 어느 특정 영역에 미리 생성해놓고 재사용을 반복하는 구조를 도입한다면 메모리와 CPU연산에 있어서 큰 비용절감을 기대할 수 있게 된다.

    3. Object Pool이란?
    Object Pool(객체 풀)은 객체를 미리 만들어놓고 담아놓는 메모리 영역이다. 개발자는 이 Object Pool에 미리 만들어진 객체를 사용하고 다 사용한 뒤에는 다시 반환시킴으로서 재사용(또는 대여)을 하도록 한다. Object Pool을 사용하면 처음 객체를 생성할때와 더 필요한 객체가 있는 경우를 제외하고 new 연산을 최소화 할 수 있다.

    4. Object Pool을 도입할 때 고려사항
    Object Pool은 미리 객체를 생성하고 지속적으로 재사용하기 위해 사용한다. 이때 미리 객체를 생성한다는 것은 초반 애플리케이션 동작 퍼포먼스에 영향을 미칠 수 있다.

    Object Pool을 사용했을때 다음과 같은 장점을 얻을 수 있다.

    1. new 연산자를 자주 사용해야하는 경우에 도입하면 애플리케이션 속도 향상을 기대할 수 있다.
    2. 가비지 컬렉터에 의존하지 않는 메모리 관리를 꾀할 수 있다.

    하지만 일반적으로 10개가 필요한데 미리 100개를 만들어 항상 10% 미만 정도의 재사용률을 보인다면 그것은 메모리 낭비이다. 그러므로 다음과 같은 조건에서 Object Pool을 사용하고 관리하는 것이 좋겠다.

    1. 비교적 큰 용량의 객체를 자주 사용되거나 반환해야하는 경우에 사용한다.
    2. 미리 생성할 객체를 적당하게 정하도록 한다.

    반대로 위 경우가 아니라면 쓸데없이 Object Pool을 사용하지 말자.


    5. Object Pool을 사용해보자.

    폴리고널 lab에서 간단한 Object Pool을 공개했다. 아래 링크에서 다운로드 받을 수 있다.

    Download: ObjectPool_v1.1.zip

    사용 방법은 매우 간단하다. ObjectPool 클래스는 아래 코드처럼 특정 클래스를 할당(allocate)하면 된다.

    var isDynamic:Boolean = true;

    var size:int = 100;

    var pool:ObjectPool = new ObjectPool(isDynamic);

    pool.allocate(MyClass, size);


    isDynamic 플래그는 Object Pool내의 객체가 비어있는가 체크하기 위해 사용한다. 즉, true이면 Pool은 자동적으로 정해진 크기(size)를 넘어서더라도 Pool의 크기를 확장하여 새로운 객체를 줄 수 있으나 false이면 정해진 크기만큼 객체를 이미 준 경우에는 Pool이 비게 되므로 Error를 발생시킨다. 

    크기(size)를 100으로 지정했기 때문에 미리 Object Pool에 100개의 MyClass의 객체를 생성해 두도록 한다.

    이제부터 MyClass는 아래와 같은 방식으로 사용하면 되겠다.

    myObjectArray[i] = new MyClass();

    // 코드 대신 아래를 사용한다.

    myObjectArray[i] = pool.instance;


    다 사용한 MyClass객체는 Object Pool에 반환해줘야 한다. 이때 가비지 컬렉터의 기능을 이용하는게 아니라 재사용(recycle)을 위해서 다음과 같이 코딩하면 되겠다.

    pool.instance = myObjectArray[i];

    사용방법은 이게 전부다.

    원한다면 자신의 애플리케이션에 맞게 이 공개된 ObjectPool을 개선시킬 수 있다. 가령, 100개의 Pool을 만들어놓지만 처음부터 100개를 생성해놓지는 않겠고 필요할 때마다 생성해주는 구조인 게으른(lazy) 생성을 기대할 수 있을 것이다. 또는 DisplayObject의 경우라면 사용한다는 것을 DisplayObject.parent 속성으로 판단할 수 있다. 위 처럼 pool.instance = '다 사용한 객체'와 같은 형태로 반환하는 것보다 자신의 부모가 존재하지 않는다면 재사용할 객체로 판단하도록 만들면 일종의 자동 Object Pool을 만들 수 있다. 자동 Object Pool은 다음 링크를 참고하면 좋겠다.

    자동 풀링(auto pooling)을 구현해보자.



    6. Object Pool과 new 연산자 사용 비교

    Object Pool을 사용해야하는 타당성은 퍼포먼스 실험을 통해 쉽게 알 수 있다.  이러한 점에서 폴리고널 랩에서 제공하는 실험은 매우 유용한 자료이다.

    실험 방법은 아래와 같다. (폴리고널 랩에서는 이 실험을 Window Vista, Flash Player 9.0.124에서 진행했다.) 그리고 Pool크기는 100으로 지정했다.

    첫번째로 아래 코드처럼 k수 만큼 Object Pool로 부터 객체를 get만 한다.

    for (var i:int = 0; i < k; i++) instances[i] = p.instance;


    두번째로 아래 코드처럼 k수 만큼 Object Pool로부터 객체를 get/set 한다.

    for (i = 0; i < k; i++) instances[i] = p.instance;

    for (i = 0; i < k; i++) p.instance = instances[i];


    세번째로 아래 코드처럼 k수 만큼 Object Pool대신 new 연산자를 사용한다.

    for (i = 0; i < k; i++) instances[i] = new MyClass();



    결과는 다음과 같다.


    네이티브 Object 클래스의 경우 Object Pool을 사용하는 것이 new 연산자를 사용한 것보다 5배 정도 빨랐다.


    Object 보다 더 크고 복잡한 flash.geom.Point의 경우는 약간더 빠른 결과를 보인다.


     flash.display.Sprite 객체를 가지고 하면 무려 80배나 속도 개선이 되었다. 이는 복잡하고 큰 클래스에 Object Pool을 도입하면 효과적일 수 있다고 판단할 수 있다.

    이 실험을 통해 적절한 방법으로 Object Pool을 사용하는 것은 애플리케이션의 퍼포먼스 증가에 지대한 영향을 줄 수 있다는 것을 깨달을 수 있다.


    7. 메모리 사용의 폭이 급격한 애플리케이션은 좋지 못하다.
    본인은 어떤 애플리케이션(Flash가 아니더라도)이든지 그 애플리케이션에 필요한 최소한의 메모리는 항상 존재한다고 생각한다. 무조건 메모리 사용을 줄여보겠다고 new, delete를 반복하는 것은 잘못된 것이다. new, delete를 반복하면 오히려 CPU 및 메모리 사용의 반복을 부축이며 애플리케이션의 전체적인 퍼포먼스를 저하시키는 경우가 발생할 수 있다. 실제로 new,delete를 반복하는 구조로 개발한 애플리케이션을 Flash Builder의 프로파일링 기능을 통해 메모리 사용을 살펴보면 큰폭으로 메모리 사용/해지가 일어난다는 것을 확인할 수 있다. 그러므로 몇몇 사람들이 Object Pool과 같은 개념과 같이 퍼포먼스 향상에 도움이 되는 개념을 전혀 사용하지 않고 Flash는 느리고 가비지 컬렉터 동작은 비정상적이다라고 말하는 것은 섣부른 판단이다라고 생각한다. 얼마든지 메모리를 효율적으로 사용하고 속도 개선을 할 수 있음에도 불구하고 잘못된 개발 방식때문에 전체적인 퍼포먼스가 나빠지는 것은 결국 개발자의 잘못인 것이다. 이는 Flash든 어떤 언어든 동일한 생각으로 접근해야 한다.

    Flash Builder의 프로파일링 기능을 이용해 Object Pool과 new 연산자간의 메모리 변화를 확인할 수 있는 간단한 실험을 해보겠다. (프로파일링 기능을 사용하는 방법은 주제에서 벗어나므로 제외하겠다.)

    먼저 Shape객체를 확장해서 크기와 색이 렌덤하게 그려지는 클래스를 만든다.

    //화면에 출력할 Shape

    class RandomShape extends Shape {

            static private var colorPool:Array = [0xff0000,0x00ffff,0x0000ff,0x00ff00,0x0f0f0f,0xf0f0f0,0xffff00,0xf00cf0,0x00fcaa,0xff0c9a];

            public function RandomShape():void {

            }

            public function draw():void {

                   var radius:Number = Math.random() * 40;

                   var color:uint = colorPool[ Math.floor(Math.random()*9) ];

                   graphics.clear();

                   graphics.beginFill(color,1.0);

                   graphics.drawCircle( 0, 0, radius );

                   graphics.endFill();

            }

    }


    위 클래스를 가지고 자동 Pool을 구현한 클래스를 만든다.(참고 : http://www.diebuster.com/?p=1000)

    //Shape 자동 객체 Pool

    class RandomShapeAutoPool {

            private var _pool:Vector.<RandomShape>;

            public function RandomShapeAutoPool() {

                   //적당히 수로 초기화한다.

                   _pool=new Vector.<RandomShape>();

            }

     

            public function getShape():RandomShape {

                   var key:*, result:RandomShape;

                   //먼저 기존의 pool에서 찾아본다.

                   for (key in _pool) {

                           //만약 해당 객체가 null이라면 new 통해 생성한다.

                           if (_pool[key] === null) {

                                  _pool[key]=new RandomShape;

                                  result=_pool[key];

                                  break;

                           //null 아니라면 parent 조사하여 사용가능한지 판단한다.

                           } else if (_pool[key].parent === null) {

                                  result=_pool[key];

                                  break;

                           }

                   }

                   //기존의 pool안에서 쓸만한 찾지 못했다면

                   if (result === null) {

                           result=new RandomShape;

                           _pool[_pool.length]=result;

                   }

                   //인스턴스를 반환한다.

                   return result;

            }

    }


    이제 테스트할 수 있는 호스트코드를 제작해보겠다.

    package {

            import flash.display.*;

            import flash.events.*;

            import flash.text.*;

            import flash.utils.*;

     

            [SWF(backgroundColor="#ffffff", frameRate="60", width="400", height="400")]

            public class ObjectPoolTest extends Sprite {

                   private var pool:RandomShapeAutoPool=new RandomShapeAutoPool;

                   private var poolFlag:Boolean = false;

                   private var textPoolFlag:TextField;

                   private var shapeCanvas:Sprite;

     

                   public function ObjectPoolTest() {

                           addEventListener(Event.ADDED_TO_STAGE, ADDED_TO_STAGE);

                   }

     

                   private function ADDED_TO_STAGE($e:Event):void {

                           removeEventListener(Event.ADDED_TO_STAGE, ADDED_TO_STAGE);

                           stage.scaleMode=StageScaleMode.NO_SCALE;

                           stage.align=StageAlign.TOP_LEFT;

                           //rf)http://blog.jidolstar.com/656

                           if (stage.stageWidth === 0 && stage.stageHeight === 0) {

                                  stage.addEventListener(Event.ENTER_FRAME, function($e:Event):void {

                                                 if (stage.stageWidth > 0 || stage.stageHeight > 0) {

                                                         stage.removeEventListener($e.type, arguments.callee);

                                                         init();

                                                 }

                                          });

                           } else {

                                  init();

                           }

                   }

     

                   private function init():void {

                           //shape 부모객체

                           shapeCanvas = new Sprite;

                           addChild( shapeCanvas );

                           //PoolFlag 상태표시

                           textPoolFlag = new TextField();

                           textPoolFlag.text = "poolFlag=" + poolFlag;

                           addChild(textPoolFlag);

                           //마우스 클릭 영역

                           graphics.beginFill(0x000000,0);

                           graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);

                           graphics.endFill();

                           addEventListener(MouseEvent.CLICK, MOUSE_CLICK);

                           //반복동작     

                           addEventListener(Event.ENTER_FRAME, ENTER_FRAME);

                   }

     

                   private function MOUSE_CLICK($e:MouseEvent):void {

                           //마우스 클릭때마다 poolFlag 상태를 전환

                           poolFlag = !poolFlag;

                           textPoolFlag.text = "poolFlag=" + poolFlag;

                   }

                  

                   private function ENTER_FRAME($e:Event):void {

                           var shape:RandomShape, i:int, numChildren:int;

                           //poolFlag 따라서 new연산자 또는 Object Pool에서 객체 참조

                           shape = poolFlag ? pool.getShape() : new RandomShape;

                           shape.draw();

                           shape.x = Math.random() * stage.stageWidth;

                           shape.y = 0;

                           shapeCanvas.addChild( shape );

                           //계속 아래로 떨어뜨림. 화면에서 벗어나면 removeChild시킴

                           numChildren = shapeCanvas.numChildren;

                           for( i=0; i<numChildren;i++) {

                                  shape = shapeCanvas.getChildAt(i) as RandomShape;

                                  shape.y += 5;

                                  if( shape.y > stage.stageHeight ) {

                                          shapeCanvas.removeChild( shape );

                                          i--;

                                          numChildren--;

                                  }

                           }

                   }

            }

    }


    동작은 매우 단순하다. poolFlag가 true이면 제작한 자동 Object Pool을 사용하겠다는 것이고 false이면 new 연산자를 사용해 RandomShape객체를 생성하겠다는 것이다. poolFlag 전환은 마우스 클릭으로 할 수 있다. 그리고 비가 내리는 것처럼 Shape 객체는 위에서 부터 아래로 떨어지며 맨 아래에 닿으면 removeChild 시킨다.

    테스트 화면


    실행 코드와 결과물은 http://wonderfl.net/code/805dc65d1f7800b7bee5dc3cd72ca21c0a87c2d6 에서도 볼 수 있다.

    이제 Flash Builder의 프로파일링 기능을 이용해 메모리 변화 및 객체 생성빈도를 살펴보자.

    먼저 new 연산자를 사용했을때 메모리 변화를 보자.

    new 연산자로 객체생성시 Memory Usage


    new 연산자를 사용해 객체를 생성하고 필요없을때 removeChild를 하게 되면 가비지 컬렉션 처리가 되기 때문에 일정한 시점에 메모리가 해지되는 것을 반복하는 것을 확인할 수 있다.

    new 연산자로 객체생성시 Live Object


    위 표는 축적된 객체수(Cumulative Instances), 현재 객체수(Instances), 축적된 메모리(Cumulative Memory), 현재 메모리(Memory)를 나타낸다. 변화 무쌍하며 실제로 보여지지 않는 객체도 가비지 컬렉션 처리가 일어날때까지 메모리에 남아 있다. 분명 비효율적으로 보인다.

    반대로 Object Pool을 이용했을때 메모리 변화이다.

    Object Pool로 객체관리시 Memory Usage

    new 연산자를 사용했을때와 비교가 안될 정도로 고요한 느낌이 든다. 거의 일직선의 메모리 사용율을 보인다.

    Object Pool로 객체관리시 Live Object


    축적되는 객체도 81개를 넘지 않는다. 그만큼 객체 생성과 삭제에 민감하지 않게 되고 가비지 컬렉터 도움을 받지 않고 객체관리가 되는 것을 확인할 수 있다.


    8. 재사용할 수 있는 클래스 제작

    Object Pool을 사용시 한가지 고려할 사항은 재사용할 수 있는 클래스를 만들어야 한다는 점이다. 왜냐하면 Object Pool은 단순히 재사용을 위한 공간만 제공하는 것이지 재사용을 하기 위한 어떤 기능을 클래스에게 줄 수 없기 때문이다. 그래서 재사용이 가능한 클래스를 설계하는 것은 하나의 기술적 이슈가 될 수 있다. 

    재사용할 수 있는 클래스를 어떻게 만드는지 간단한 예를 들어보겠다.

    final public class MyData {

            public var type:String;

            public var id:int;

            public var name:String;

    }


    위 클래스는 데이터를 담는 클래스이다. id, name은 실제 사용하는 데이타 값들이고 이 데이타의 종류는 type으로 구분한다. 이 데이타를 사용하는 인터페이스를 제작해보겠다.

    public interface IMyClass {

            function init($data:MyData):void;

            function clear():void;

            function get data():MyData;

    }


    이 인터페이스는 앞으로 만들 MyClass01, MyClass02... 등을 구현하기 위한 것이다. 초기화 하기 위해 init()함수가 있고 인자로 위에서 제작한 MyData 객체를 받는다. 또한 사용할 필요가 없을때 내부 청소를 위해 clear()함수를 추가했다. 게다가 가지고 있는 data를 참고하기 위해 get data()도 만들었다. 이제 이 인터페이스를 구현한 클래스는 아래처럼 만든다.

    final internal class MyClass01 extends Sprite implements IMyClass {

            private var _data:MyData;

            public function MyClass01() {

            }

            public function init($data:Mydata):void {

                   _data = $data;

                   //구현

            }

            public function clear():void {

                   _data = null;

            }

            public function get data():MyData {

                   return _data;

            }

    }

     

    final internal class MyClass02 extends Sprite implements IMyClass {

            private var _data:MyData;

            public function MyClass02() {

            }

            public function init($data:Mydata):void {

                   _data = $data;

                   //구현

            }

            public function clear():void {

                   _data = null;

            }

            public function get data():MyData {

                   return _data;

            }

    }

     

    final internal class MyClass03 extends Sprite implements IMyClass {

            private var _data:MyData;

            public function MyClass03() {

            }

            public function init($data:Mydata):void {

                   _data = $data;

                   //구현

            }

            public function clear():void {

                   _data = null;

            }

            public function get data():MyData {

                   return _data;

            }

    }



    코드가 길어보이지만 3개 클래스 모두 IMyClass를 구현했고 Sprite를 확장했다. 단지 클래스 이름만 다르며 실제 구현부는 알아서 구현하면 된다.

    개발자는 MyClass01, MyClass02, MyClass03은 매우 자주 new 연산자로부터 생성되는 클래스로 판단했고 그래서 Object Pool을 도입하겠다고 결정했다고 하자. 그럼 다음과 같이 시도해 볼 수 있다.

    import de.polygonal.core.ObjectPool;

    import flash.display.DisplayObject;

     

    public class MyPool {

            private static var poolList:Object;

           

            static public function init():void {

                   poolList = {

                           'type01':new ObjectPool(true),

                           'type02':new ObjectPool(true),

                           'type03':new ObjectPool(true),

                   };

                   poolList.type01.allocate(20, MyClass01);

                   poolList.type02.allocate(10, MyClass02);

                   poolList.type03.allocate(100, MyClass03);

            }

           

            static public function getObject($data:MyData):IMyClass {

                   if( $data === null ) {

                           throw new Error('인자값은 null이면 안됩니다.');

                   }

                   try {

                           var object:IMyClass = poolList[$data.type].object;

                           object.init($data);

                   } catch {

                           throw new Error('데이타의 type값이 잘못된 값입니다.');

                   }

                   return object;

            }

           

            static public function returnObject($object:IMyClass):void {

                   if( $object === null ) {

                           throw new Error('인자값은 null이면 안됩니다.');

                   }

                   $object.clear();

                   if( DisplayObject($object).parent ) {

                           DisplayObject($object).parent.removeChild( DisplayObject($object) );

                   }

                   poolList[$object.data.type].object = $object;

            }

    }



    위에서 제작된 MyPool은 static클래스이며 ObjectPool을 이용해 IMyClass 인터페이스를 구현한 클래스를 init()함수에서 적당하게 할당하는 것을 확인할 수 있다. 또한 getObject()를 통해 인자값 data를 참고하여 참조할 Object Pool을 찾아 IMyClass를 구현한 각각의 클래스의 객체를 받아올 수 있으며 returnObject를 통해 다 사용한 객체를 반환한다.

    실제로 실무에서 이런 형태로 개발했고 이는 매우 유용했다. MyPool 클래스를 더 개선해서 더 많은 수 Object Pool을 감당할 수 있도록 확장할 수 있다면 더욱 유용해질 것이라 생각한다.


    9. 정리하며

    Object Pool 개념은 Flash에만 국한되는 유용한 개념이 아니다. 이런 개념들에 대한 노하우를 계속 쌓아간다면 당신의 애플리케이션은 더욱 좋은 작품으로 탈바꿈할 수 있을 것이다.

    Flash Player가 느리고 메모리 관리가 안된다고 생각하지는 말자. Flash Player 태생자체가 그런것을 어찌하겠는가? 제작한 애플리케이션이 느리다면 그것은 결국 개발자가 잘못한 것임을 항상 인지하자. 중요한 것은 여전히 Flash Player는 유용하고 많이 사용되고 있으며 지금도 나날이 발전하여 Cross OS, Cross Browser를 넘어 Cross Device 세계로 뻗어가고 있다는 점이다.


    10. 참고글
    글쓴이 : 지돌스타(http://blog.jidolstar.com/666)

    2009년 한국천문연구원 아마추어 천문학 발전을 위한 위탁과제로 만든 대한민국 인터넷 천문대(http://realsky.org) 입니다.

    이 사업은 언제 어디서나 누구나 인터넷을 통해 쉽게 밤하늘을 관측하고 정보를 얻을 수 있는 것을 목표로 최초 제작되었습니다. 2009년 6월 부터 12월까지 프로젝트가 진행되었으며 이를 위해 미국 우주망원경연구소의 디지털하늘탐사(Digitized Sky Survey, DSS) 데이터를 이용해 전천의 밤하늘 이미지를 가공했고 이것을 보여주기 위해 Adobe Flash 기술을 이용했습니다.

    이미지의 편집 및 이용권한은 천문노트(http://astronote.org) 에게만 있습니다.

    클라이언트 엔진은 ActionScript 3.0이며 일부 Flex 4를 이용해 개발했습니다. 서버측 통신은 ZendAMF를 이용했습니다. 최초 개발한 만큼 아직 부족하나 앞으로 유용하고 다양한 형태로 개발할 계획입니다.

    http://realsky.org

    글쓴이 : 지돌스타(http://blog.jidolstar.com)

    나는 Flex나 기타 무거운 프레임워크를 사용하지 않고 ActionScript 3.0 API 수준으로 개발을 한지 이제 겨우 1년이 넘은 것 같다. Flex로 개발할 것과 ActionScript로 개발해야할 것에 대한 구분은 항상 명확하다. 자주 새로 빠르게 로드되어야 하는 애플리케이션 제작은 무조건 ActionScript 기반으로 제작한다. 하지만 가끔 사용하며 Flex 컴포넌트를 잘 활용해야하는 애플리케이션이라면 Flex로 개발한다.

    ActionScript 3.0기반으로 개발하고 완성하고 나서는 뭔가 꼬여있는듯한 느낌, 유지보수가 안될 것 같은 느낌을 항상 받아왔다. 또한 깨끗하지 못한 객체관리가 항상 걸렸고 너무 길어져서 지저분해지는 코드 또한 마음에 들지 않았다. 그리고 클래스간 역할 분담이 분명하지 않는다는 느낌을 받았다. 동작은 뭔가 석연치 않은 느낌을 계속 받았다. 어떤 개발의 스킬이나 전문적인 지식에 대해서 학습을 해보지 못해봤던 나로써는 설계때부터 항상 고민거리가 생긴다.

    나는 근 한달동안 블로그 활동 및 외부 활동을 거의 안하면서 개발에만 매진하면서 지냈다. 바로 스타플의 타임라인 개발때문이였다. 타임라인이 궁금하시다면 아래 링크로 접속해보면 된다.

    스타플 타임라인 업그레이드 및 앨범 기능 추가 소식


    개발하면서 위에서 제시한 문제점을 어느정도 해소시킬 수 있는 몇가지 개념들이 있었다. 여기에 간단하게 적어두도록 하겠다.


    1. 객체의 재사용 Object Pool
    Flash 개발하면서 항상 걸리는 문제중에 하나가 메모리이다. Flash Player가 정확한 가비지 컬렉터(GC)를 가진다고 하지만 지금도 그다지 명확한 신뢰를 받지 못하고 있다. 하지만 분명 GC는 동작한다. 중요한 것은 Flash Player의 GC를 너무 맹신하지는 말자는 것이다.

    new를 통한 객체 생성은 항상 지양하는 것이 가장 중요한 메모리 관리의 첫걸음이다. MyClass.Factory()와 같은 static 클래스를 통해 내부적으로 new를 사용하고 각종 처리는 factory가 담당하는 것이 각종 초기화 및 객체관리에 도움이 된다.

    더불어 매우 자주 사용하다가 없어지는 객체를 계속 new를 통해 생성하고 GC객체화 시키는 것은 메모리 관리에 도움이 안될뿐 더러 퍼포먼스에도 문제가 있다. 실제로 비교적 큰 디스플레이 객체인 Sprite를 new를 자주하면 전체적인 퍼포먼스에 좋지 않은 영향을 줄 수 있다. 이런 경우에 퍼포먼스와 메모리를 동시에 해결할 수 있는 방법은 바로 Object Pool을 이용하는 것이다.

    Using object pools : http://lab.polygonal.de/2008/06/18/using-object-pools/ 

    위의 글은 Object Pool의 강력함을 시각적으로 말해주고 있다. 특히나 Object나 Point와 같은 작은 클래스가 아닌 Sprite와 같은 큰 클래스의 경우 new 연산자 사용보다 Object Pool의 사용은 큰 이점을 주고 있다는 것을 알 수 있다.

    사용한 객체를 그냥 버리지 말자. 잘 보관했다가 다시 사용할 수 있게 만들고 사용하자... 특히나 시각객체(Sprite기반)이 자주 생성,소멸해야하는 경우라면 강력히 Object Pool을 사용하라고 주장한다.


    2. Linked List 와 Dictionary
    컴퓨터를 전공한 사람이라면(본인은 아님) 자료구조를 배우면서 Linked List는 반드시 이해해야할 필수 항목이다. 나는 이번 타임라인을 제작하면서 Linked List를 활용해 중간중간에 얻어진 시간순서 데이터를 엮어주는데 요긴하게 사용했다. 일반적인 Array형태의 데이터는 중간에 데이터 삽입하는 것은 효율적이지 못하지만 Linked List는 연결하고자 하는 지점의 연결부의를 끊고 그 중간에 삽입하여 앞뒤로 연결만 하면 되기 때문에 훨씬 효율적이다.

    Data Structures example : linked lists http://lab.polygonal.de/2007/08/13/data-structures-example-linked-lists/

    또한 특정 key의 자료검색에 Dictionary를 사용했다. Dictionary를 직접 사용하는 것보다는 한번 감싸서 클래스를 만들어 추가/삭제/편집/데이터접근등을 처리하기 쉽게 하면 정말 요긴해진다. 나는 Linked List와 더불어 Dictionary를 통해 key값을 통한 데이터 검색에 사용했다. 이것을 이용하면 아무래도 index 순으로 찾는 것보다 훨씬 빠른 검색을 할 수 있다.


    3. MVC
    개발자라면 Model-View-Controller에 대한 개념을 잘 알고 있을 것이다. 이러한 개념은 Flex 수준의 개발이라면 Flex가 어느정도 지원해주는 편이기 때문에 그나마 괜찮지만 ActionScript 수준에서 개발하려면 이 점을 고려하는 것이 좋다. 기본적으로 Model의 데이터 변화는 View에 영향을 미치고 View의 변화는 Controller에 영향을 준다. Controller는 View와 Model을 통제하며 서로의 상호작용에 조절한다. 그리고 View가 Model을 절대 변경하지 않는다. 이런 약속으로 MVC 구조를 설계하면 View와 View관계도 명확해지고 아직 로드되지 않는 View(모듈)에게도 도움이 된다.

    타임라인의 경우 최초 구동될때 달력과 보기설정은 아에 모듈로 만들어 버린다. 그리고 보여달라고 요청할때만 모듈을 로드해 시각화시킨다. 이때 모듈은 부모 애플리케이션에 Model에 있는 데이터를 사용한다. 이 데이터는 View(모듈)의 존재를 모르기 때문에 어떤 상황이 되든 사용할 수 있다. 또 달력과 보기설정은 같이 보여질 수 없다. 각각 show()와 hide()함수가 있는데 달력이 show하면 보기설정은 hide가 호출되어야하는 식이다. 그런데 View에서 이런것을 컨트롤해버리면 View간에 상호 간섭이 생긴다. 이런 경우 Controller가 중재하도록 하면 훨씬 쉽게 이런 문제를 해결할 수 있다.


    4. 인자객체를 이용하자.
    TextField를 이용해서 객체를 생성해본적이 있을 것이다. 1개 Text를 출력하기 위해서 얼마나 많은 설정을 해야하고 또한 수십줄이 넘는 코딩을 해야하는가? 타임라인의 달력 기능에는 한개 한개가 다 TextField 객체들이다. 물론 반복적으로 생성해주면 되지만 이 마저도 마우스오버,클릭,선택등과 같은 동작에 대응해야하기 때문에 단 몇줄 코드로는 이런 기능을 구현하기가 어렵다.

    나는 이러한 문제를 인자객체를 이용해 해결했다.
    이에 대해서는 더이상 말이 필요없다. 다음 글을 보자.
    인자객체 : http://diebuster.com/flash/?s=인자객체


    5. Helper 수준의 프레임워크
    이번 타임라인을 제작하면서 가장 도움이 되었다고 생각하는 사람은 hika님이다. 물론 같은 회사도 아니고 자주 만나본 것도 아니지만 hika님의 블로그에 정리해놓은 내용은 나에게 큰 자극제가 되었다. 그중에 hika님이 만들어놓은 ActionScript 3.0 프레임워크는 나에게 큰 도움이 되었다. 이는 흔히 말하는 무거운 프레임워크 아니다. 정확히 말하자면 Helper 수준이다. Helper 수준이라는 것은 Flex처럼 Button, Label, DataGrid와 같은 컴포넌트를 가지는 것이 아니라 Sprite, Shape, TextField, Loader등의 기본 ActionScript 3.0 기반의 클래스를 더욱 효율적으로 사용하게 하기 위한 것이라는 것이다.

    나는 이 프레임워크를 이용해서 시각객체관리, 레이어관리, Embed자원관리, 원격자원관리, 이벤트관리, 데이터관리등 전반적으로 큰 도움이 되었다. 나는 hika님이 만들어놓은 프레임워크를 분석하면서 큰 구조의 변경없이 나만의 프레임워크로 변경하고 이번 타임라인 개발에 적극적으로 사용했다. 만약 프레임워크의 도움이 아니였다면 역시나 어려운 코딩을 감당해야할지도 모르겠다.


    6. EnterFrame 수준의 동작 관리
    MouseMove 이벤트를 시각객체를 이동하는데 사용하지 말자. 대신 EnterFrame 이벤트시에 stage.mouseX, stage.mouseY등을 사용해서 시각객체를 이동시키자. 실제로 MouseMove 이벤트는 그 동작이 매우 느리다. 그러나 EnterFrame 이벤트는 최소한 stage.frameRate에 지정된 수준에서 발생하기 때문에 느린 마우스 동작에 걱정하지 않아도 된다.

    나는 타임라인을 마우스로 움직일 때 이 방법을 사용했다.

    마우스 이벤트 최적화 : http://diebuster.com/flash/category/algorithm/io


    7. Event 사용의 최소화
    나는 Event 사용을 최소화 하려고 많이 노력했다. 정말 필요한 곳에만 사용해도 개발에 무리가 없었다. 시각객체를 통한 이벤트 전파는 생각보다 매우 느린 편이기 때문에 되도록이면 사용을 지양해야한다. 이것도 정말 필요하고 요긴하다고 판단할때만 사용하는 것이 옳다. 사용한 이벤트는 반드시 remove시키는 것이 현명하다. useWeakReference는 정말 필요할때만 사용하는 것이 좋다. 되도록이면 명시적으로 remove 시키는 것이 메모리 관리에 도움이 된다.


    8. arguments.callee 활용
    arguments.callee는 함수 자신을 말한다. 이는 꽤 유용한데 특히나 이벤트 리스너 함수에서 자신을 호출한 이벤트를 삭제할때 사용될 수 있다.

    스스로 삭제되는 이벤트 리스너 : http://diebuster.com/flash/110


    9. BitmapData.clone() 사용 지양
    BitmapData 클래스는 비트맵 데이터를 저장하는 클래스이다. 이 데이터를 시각화 하기 위해 사용하는 클래스는 Bitmap이다. new Bitmap( bitmapData ) 시에 첫번째 인자가 BitmapData 객체이다.  같은 bitmapData가 자주 사용되어져야 하는 경우에 bitmapData.clone() 을 통해 계속 생성하지 말고 그냥 원본 bitmapData를 이용해 Bitmap객체를 생성해서 시각화 처리하자.

    나는 타임라인에 들어가는 각종 비트맵 객체에서 bitmapData를 뽑아내어 static으로 참조해 계속 사용하도록 했고 Bitmap 또한 Object Pool 관리를 통해 최소한의 객체 생성을 유도시켜 메모리 관리를 하도록 했다.


    10. mouseChildren, mouseEnabled, tabChildren과 같은 속성은 왠만하면 false로 지정하자.
    이러한 속성들이 필요없는 곳에 사용되면 오히려 퍼포먼스에 악영향을 준다. 정말 필요한곳에만 사용하는 것이 좋다. 이들 속성의 의미를 파악해두는 것이 중요하다.


    11. cacheAsBitmap은 이동에만 사용하자.
    cacheAsBitmap은 확대,축소,회전,Alpha값 변경되는 곳에는 사용하지 않는 편이 좋다.
    다음 글을 참고하자.

    왜 cacheAsBitmap은 나쁜가? http://www.bytearray.org/?p=290


    12. 동적언어의 장점을 충분히 살리자.
    동적언어와 정적언어의 차이점은 메모리를 사용하는 차이점에서 비롯된다. 실행하는 시점에 초기에 메모리를 확보해서 사용하는 언어는 정적이다라고 하며 거대한 메모리를 먼저 잡아놓고 실행하는 시점에 메모리를 자유롭게 사용하는 것은 동적언어이다. ActionScript 는 동적언어이기 때문에 중간중간에 실행도중 클래스 정의, 함수를 추가/수정/삭제가 가능하다. 이에서 나온 개념이 클로저라는 것인데 이 개념을 잘 이용하면 코딩이 훨씬 간편하고 편리해질 경우가 많다. Flex의 대부분 코딩 방법은 정적언어처럼 되어 있는데 사실 ActionScript는 동적언어이므로 이를 더 잘 사용해서 만들어져야 한다고 생각한다. 

    Closure에 대해 : http://diebuster.com/flash/82


    추가사항

    13. package 격리원칙
    클래스 격리에 대해서는 들어봤을 것이다. 클래스내 함수에 public, private를 적절하게 잘 사용해서 외부에 노출되는 메소드는 3~4개정도로 하고 내부 동작방식을 몰라도 클래스를 쉽게 이해할 수 있도록 하는데 필요한 개념이다. 이는 사용하는 관점도 그렇고 상속받는 관점에서도 마찬가지로 적용되도록 한다.

    패키지 격리라는 것은 internal을 적극활용한다는 의미이다. 아마도 많은 개발자들이 internal의 강력함을 잘 인지하지 못할것이다. 이에 대해서는 내가 굳이 설명할 필요 없다. 다음글을 보자.

    플래시 애플리케이션 개발하기 : http://diebuster.com/flash/86


    14. 적절한 코딩규칙
    개발하는데 코딩규칙은 무엇보다 중요하다. 가령 클래스 private 변수에는 _을 붙힌다던지 const값은 대문자로 표시한다던지 그런것 말이다. ActionScript 3.0에서 코딩규칙은 더욱 상세화 되면 좋다. 왜냐하면 워낙 동적인데다가 다른 언어에 비해서 명확한 컴파일 단계의 문법체크가 약하기 때문이다.

    hika님의 2009년도 코딩 규칙을 잠깐 보자. http://diebuster.com/flash/3 

    나는 처음에 클래스에 C, 정적클래스에 CS를 붙이는 의도에 대해서 잘 이해할 수 없었다. 하지만 이에 대한 강력함은 개발해보면 안다. 나는 클래스에 C, 정적클래스에 S, 인자객체는 A, internal 클래스는 앞에 _, 어떤 클래스의 Part라면 클래스명뒤에 _파트명 이런식으로 클래스 이름 규칙을 정했다. 실제로 이렇게 관리하면 클래스가 정확히 어떤 역할을 하는지 눈에 팍 들어온다. 함수를 인자로 받아야하는 경우 FN_intNumberString_Boolean:Function 과 같이 쓰면 이 인자로 들어올 함수는 반드시 function( a:int, b:Number, c:String):Boolean 형태로 만들어야 된다. 이런 규칙은 코딩시 코드힌트기능에도 표시되기 때문에 사용하면 정말 편리하다.

    반복구문에 대해 : http://diebuster.com/flash/?s=%EB%B0%98%EB%B3%B5%EA%B5%AC%EB%AC%B8&submit=

    결국 코딩규칙을 사용하는 명확한 의미는 한눈에 코드를 파악하고자 하는 의도에서 찾아야 한다.


    정리하며
    앞서 말했지만 타임라인 개발하는데 많은 도움을 주신 분을 꼽자면 hika님이다. 부족하지만 이 분의 도움으로 나는 스킬업을 할 수 있었다. 사실 hika님 입장에서는 이런 외부활동과 저술활동이 없어도 부족함이 없어보인다. 하지만 그의 공유정신 때문에 개발에 입문하는 많은 사람들에게 큰 도움이 되는 것은 사실이다. 이 글을 통해 감사하다고 전하고 싶다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/661)

    지난 2월 2일 AIR 2.0 beta 2가 배포된데 이어 오늘(한국날짜기준 2010.02.24) Adobe Flash Player 10.1 beta 3가 배포되었습니다. Adobe Labs에 가면 상세한 정보를 보실 수 있습니다. 


    릴리즈 노트의 Fixed Issues에 별표(*) 표시된 것이 이번 배포의 수정사항들입니다. 보면 기존에 있었던 Fix된 버그와 개선사항을 확인할 수 있습니다.

    주의사항은 이것은 말그대로 beta라는 점입니다. beta는 정식버전이 아니기 때문에 Flash Player 10.1 API를 이용해서 개발하셔도 실제로 서비스하는 것은 안된다는 것을 의미합니다. 테스트 용도로만 설치하셔서 사용하시기 바랍니다.

    글쓴이 : 지돌스타(http:/blog.jidolstar.com

    애플이 아이패드를 발표하면서 Flash Player 지원을 공식적으로 거부했습니다.
    그 이후로 엄청난 논란이 있는 가운데...
    Adobe CTO 케빈 린치는 다음 처럼 의견을 제시했습니다.

    Adobe 케빈 린치의 '컨텐츠 및 애플리케이션에 대한 오픈 액세스

    아무튼 이런 상황에서 아래와 같은 기사도 올라왔네요.
    한번 읽어보시고 각자의 의견을 들어보는 것도 좋을 것 같아요. ^^

    ---------------------------------------------------------------------

    [원문]
    http://online.wsj.com/article/SB20001424052748703546004575055184080144688.html


    [월 스트리트 저널(Wall Street Journal)]


    애플(Apple)의 마이크로소프트(Microsoft)화?


    2010.02.09 / 홀먼 W. 제킨스 주니어(Holman W. Jenkins, Jr.)


    애플은 증오적 라이벌 관계로 인해 제로섬 전략에 집착하는 회사로 전락할 위험에 놓여있습니다.

    현재를 언급하는 것이 아니라 애플의 시가총액이 상상할 수 없는 규모에 달하여 마이크로소프트의 시가총액을 뛰어넘는 해의 이야기가 될 수 있습니다. 당연히 축하 인사가 따르겠지만 위로의 말도 함께 따를 것입니다. 제품 개발에만 전념하는 회사의 경우 전략에 집착하는 회사가 될 위험의 소지가 있기 때문입니다. 그리고 여기서 “전략”이란 증오적 라이벌 관계로 인한 제로섬 전략을 의미합니다.

    안타깝게도 현재 우리는 신뢰를 찾기 힘든 타락한 세상에 살고 있습니다.

    아이패드(iPad)의 예를 들어보겠습니다. 아이패드는 세상에 공개되자마자 “구세주 태블릿”이라는 별명을 얻었습니다. 아이패드는 상상하지 못할 만큼 탁월한 제품이 아닌 단지 애플이 넷북 경쟁에 뛰어들기 위해 출시한 제품으로, 아이팟 터치(iPod Touch)를 확대해 놓은 버전에 불과합니다. 아이패드는 최상의 웹 브라우징 시스템으로 부각될 수 없을 수도 있습니다. 왜냐하면 애플이 웹 상에서 비디오를 전달하는 데 75% 가량 사용되고 있는 플래시(Flash) 지원을 거부했기 때문입니다. 그러나 아이패드(iPad)(‘지불’이라는 영어 PAID의 철자 순서를 바꾸어 만든 말)는 애플의 온라인 서비스를 통해 판매되고 있는 e북, 음악 및 비디오를 사용하기에는 적합한 디바이스로 보입니다. 단도직입적으로 말하자면 아이패드는 마치 아이튠즈(iTunes) 스토어를 후원하기 위해 최적화된 디바이스로 보입니다.


    그렇다면 왜 애플은 플래시를 제외시키기로 결정했는지 궁금하지 않을 수 없습니다. 애플과 애플의 후원업체들은 플래시가 짜증나는 웹 광고를 만드는데 사용되는 제품이라는 미학적이고 철학적인 이유를 내세우고 있습니다.


    애플이 플래시를 거부하는 이유는 바로 여기에 있습니다. 플래시를 사용하면 아이폰(iPhone) 및 아이패드 사용자는 아이튠즈를 통하지 않고 비디오 및 기타 엔터테인먼트를 사용할 수 있을 것이고 애플 앱 스토어(Apple App Store)에서만 현재 구입할 수 있는 다양한 기능을 무료로 얻을 수 있게 됩니다.


    네트 중립성 옹호자들이나 독점 금지법 집행자들이 스티브 잡스(Steve Jobs)를 연행해가는 오류를 범하지 않도록, 한 가지 덧붙이자면 애플은 플래시를 거부할 수 있는 적법한 권리를 가지고 있습니다. 그러나 한 가지 짚고 넘어가야 할 것은 애플은 엄청난 양의 웹 컨텐츠와 사용자를 분리시키려는 전략적 선택을 내세우고 있다는 점입니다. 플래시가 버그를 부른다는 주장 등에 대해 플래시 옹호자의 시점에서 잠시 벗어나 설명해 보겠습니다. 플래시는 다른 비디오 플레이어와의 시장 경쟁에서 우위를 점하고 있으며 10억 명에 달하는 PC 사용자가 정기적인 업데이트를 다운로드하고 있다는 사실은 엄청난 성공이라고 해도 과언이 아닐 것입니다. Hulu.com에서 TV 프로그램을 시청하거나 MLB.com에서 야구 경기를 보거나 Facebook을 통해 친구와 커뮤니케이션할 때에도 플래시가 필요합니다.


    현재로선 플래시를 소유하고 있는 어도비는 최소한 플래시 프로그래머가 애플의 앱 스토어를 통해 자신이 개발한 컨텐츠나 애플리케이션을 제공할 수 있도록 몇 가지 툴을 출시하고 있다고 말하고 있습니다. 이것도 애플의 축복이 있어야 가능할 것입니다. 그러나 애플은 미래의 웹 표준은 독점적인 플래시를 대체하게 될 것이라고 주장하고 있습니다. 이것은 지켜봐야 할 일입니다. 플래시는 현재 전세계 95%의 PC에 설치되어 있어 하루 아침에 웹 표준이 바뀔 가능성은 극히 희박합니다. 또한 파이어폭스(Firefox) 같은 브라우저 제작업체 모두가 애플이 말한 새로운 표준과 생각을 함께 하고 있는 것도 아닙니다.


    더 크게 우려되는 바는 여기에 있습니다. 애플이 이러한 무모한 목표로 인해 자사의 모바일 디바이스 사용자층을 확대하여 단지 더 많은 사용자가 아이튠즈만 이용하도록 사용자를 가두는 “네트워크 효과”의 매혹적인 유혹에 무릎을 꿇을 수 있다는 점입니다. 애플은 최근까지 제휴 관계를 유지했던 구글(Google)과 전면전에 돌입했습니다.


    지난달 말  애플 직원과의 미팅에서 스티브 잡스가  “지금까지 애플은 검색 시장에서 구글과의  경쟁을 피하기 위해 노력했지만 ‘아이폰  타도’ 를 위해 자사의 모바일 디바이스르  출시했다.” 면서 “사악해지지 말자(Don't be evil) " 라는 구글의 모토를 폄하한 발언이 일파 만파 퍼졌습니다.


    구글폰으로 인해 아이폰이 없어지지는 않을 것입니다. 시장은 수많은 모바일 디바이스를 수용할 수 있는 여건을 충분히 갖추고 있습니다. 그러나 실제 위협이 되는 것은 수천만 명의 소비자를 앱 스토어인 아이튠즈만 이용할 것을 주입시킬 수 있는 애플의 능력입니다. 구글이 아이패드가 공개되기 며칠 전 자사의 슬레이트 모양의 디바이스 모델을 발표한 것은 의미 있는 행보였습니다. 구글의 모바일 디바이스는 플래시를 지원하고 있습니다. 애플 사용자가 사용할 수 없는 비디오 및 기타 웹 기능을 사용자가 구매하거나 사용할 수 있도록 했습니다.


    애플이 아이폰에서 구글을 대체하기 위해 마이크로소프트의 검색 엔진인 빙(Bing)과 거래를 고려하고 있다는 소문이 돌고 있습니다. 또한 애플이 광고 사업에도 뛰어들어 구글의 서비스와 경쟁하기 위해 클라우드 서비스를 확대할 것이라는 소문도 들리고 있습니다. 어디서 많이 들어본 스토리 아닌가요?


    네트워크 효과는 권력과 부를 가져오는 방법이 될 수는 있지만 마이크로소프트의 사례에서 볼 수 있듯이 너무 많은 성과는 특권의 지위를 유지하기 위한 방어적이고 망상적 시도로 인해 물거품이 될 수 있습니다. 회사의 최고 심미가이자 완벽주의자가 더 이상 정상적인 의사 결정을 내리지 못하게 되면 과연 애플은 어떤 회사가 될 것인지 많은 전문가들은 의문을 가지고 바라보고 있습니다. 애플이 점점 더 많은 사용자들에게 아이튠즈 앱 스토어만 사용할 수 있는 질이 나쁜 디바이스를 출시하는 회사로 전락되지 않을까 우려되는 바입니다.
     

    stageWidthstageHeight 속성은 Stage에 정의된 속성으로 관련 디스플레이 객체가 디스플레이 리스트에 추가가 되면서 마지막에 Stage에 추가될 때 발생하는 Event.ADD_TO_STAGE 이벤트 발생 시점부터 stage.stageWidth, stage.stageHeight 형태로 참조할 수 있게 된다. 

    이 값은 ActionScript 3.0 프로젝트를 진행할 시에 정해진 Stage의 기본 폭과 높이를 알려주게 된다. 아래 블로그 글을 통해서 이 속성이 어떤 의미를 가지는지 쉽게 파악할 수 있을 것이다.

    AS3.0에서 stage.stageWidth 와 stage.width 의 차이

    아래 코드처럼 작성한다면 이 값들을 쉽게 참고가 가능하다. SWF 메타데이터로 100,100으로 지정했기 때문에 모든 경우가 100,100으로 나와야 하는 것이 정상이다.
    package {
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.text.TextField;
    
    	[SWF(width="100",height="100")]
    	public class MainApp extends Sprite {
    		public function MainApp() {
    			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
    		}
    
    		private function onAddedToStage($e:Event):void {
    			var textField:TextField;
    			textField = new TextField();
    			textField.text = stage.stageWidth + "," + stage.stageHeight;
    			addChild( textField );
    			
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			
    			textField = new TextField();
    			textField.text = stage.stageWidth + "," + stage.stageHeight;
    			textField.y = 30;
    			addChild( textField );
    		}
    	}
    }
    

    Flash Player 에서 직접 실행시키면 모두 100으로 나온다. 하지만 IE8에서는 처음에는 100이지만 다음에는 0이 나왔다. IE외에 다른 브라우져는 정상적으로 100이 나온다. 재미있는 것은 IE8에서 stage.scaleMode를 지정하면서 이 값이 리셋되어 버린다는 것이다. 이게 Mac의 FF3.0에서도 이런 현상이 있다고 한다.

    내가 만든 Flash 애플리케이션이 모든 운영체제(Cross OS)의 모든 브라우져(Cross Browser)에서 동작하도록 하기 위해서 다음과 같은 처리가 필요하게 되었다.
    package {
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.text.TextField;
    
    	[SWF(width="100",height="100")]
    	public class MainApp extends Sprite {
    		public function MainApp() {
    			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
    		}
    
    		private function onAddedToStage($e:Event):void {
    			$e.target.removeEventListener( $e.type, arguments.callee );
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			var count:int = 0;
    			if( stage.stageWidth === 0 && stage.stageHeight === 0 ) {
    				stage.addEventListener( Event.ENTER_FRAME, function ($e:Event):void {
    					if( stage.stageWidth > 0 || stage.stageHeight > 0 ) {
    						stage.removeEventListener( $e.type, arguments.callee );
    						_init();
    					}
    					trace( ++count ); //IE8에서 두번 들어왔다.
    				});
    			} else {
    				_init();	
    			}			
    		}
    		
    		protected function _init():void {
    			var textField:TextField;
    			textField = new TextField();
    			textField.text = stage.stageWidth + "," + stage.stageHeight;
    			addChild( textField );
    		}
    	}
    }
    


    Flex의 경우라면 아마도 이런 처리가 다 되어 있기 때문에 신경쓰지 않고 개발하면 될 것이다. 하지만 ActionScript 3.0으로 개발할 때에는 이런 문제를 간과하면 안될 것 같다.

    참고글
    Firefox 3 Mac Flash Bug stageWidth and stageHeight are 0
    stageWidth is zero in IE
    flash.display.Stage 클래스
    flash.display.DisplayObject 클래스


    글쓴이 : 지돌스타(http://blog.jidolstar.com/656)

    근래 스티브 잡스의 Adobe Flash에 대한 자극적인 발언으로 시끄러운 가운데 Adobe CTO인 케린 빈치가 의견을 게시했습니다. 아래는 그 내용의 번역본입니다.


    원본글 : http://blogs.adobe.com/conversations/2010/02/open_access_to_content_and_app.html
    게시자: Kevin Lynch, CTO

    최근 출시되고 있는 우수한 디바이스에 Flash Player가 탑재되어 있지 않다는 사실에 적잖이 놀랐을 것입니다.


    본래 Flash는 시장이 형성되지 않았던 약 15년 전에 펜(Pen) 컴퓨팅 태블릿을 위해 고안되었습니다. 초창기에는 낮은 대역폭의 벡터 그래픽을 지원했지만 지난 십여 년간 새로운 기능을 빠르게 추가해 나가면서, 웹의 틈새 시장을 공략했습니다. 현재는 새로운 활용 방안을 찾는 데 노력을 기울이고 있습니다. 일례로 웹상에서의 애니메이션, 스트리밍 오디오, 풍부한 인터랙션, 임의의 폰트, 양자간 오디오/비디오 커뮤니케이션, 로컬 저장소, 혁신적인 비디오 전달 등이 있습니다.
     

    HTML 기능이 추가된 Flash는 상당히 높은 채택율을 기록하고 있는 가운데, 상위 웹 사이트의 85% 이상에서 Flash 컨텐츠를 사용하고 있으며, 인터넷이 연결된 컴퓨터의 98% 이상에서 Flash가 실행되고 있습니다. Flash는 웹상의 대부분의 캐주얼 게임, 비디오 및 애니메이션에 사용되고 있으며 Nike, Hulu, BBC, Major League Baseball 등 유명 브랜드에서 Flash를 사용하여 10억 이상에 달하는 전세계 사용자에게 매력적인 경험을 전달하고 있습니다.


    Flash의 미래에 있어서 지금이 가장 중요한 시기입니다. 현재 PC 외에도 다양한 디바이스들이 하루가 다르게 출시되고 있으며, 많은 수의 디바이스가 인터넷 검색에 사용될 것입니다. 따라서 컨텐츠 및 애플리케이션 제작자와 사용자는 PC에서 Flash를 통해 얻어질 것으로 기대되는 경험, 즉 원활하고 일관되며 풍부한 경험을 디바이스에도 동일하게 전달하기 위해 많은 과제들을 해결해야 할 것입니다. Flash 엔지니어링 팀은 이를 실현하기 위해 다양한 디바이스에서 Flash Player를 철저히 분석해 왔습니다.


    이러한 노력의 결과로 Adobe는 시장 선도적인 제조업체를 대상으로 한 스마트폰용 Flash Player 10.1을 선보이려고 합니다. 이러한 제조업체에는 스마트폰 뿐만 아니라 태블릿, 넷북, 인터넷 TV 등 시장을 형성하고 있는 Google의 Android, RIM의 Blackberry, Nokia, Palm Pre, 기타 업체들이 있습니다. Flash를 통해 고객은 전체 웹을 검색할 수 있으므로 브라우저에서 Flash를 사용하고 있는 이러한 디바이스는 경쟁력을 갖추게 됩니다. 이는 사실상 오픈 스크린 프로젝트(Open Screen Project)를 통해 이루어지고 있으며, Adobe는 50여 이상의 파트너와 협력하면서 다양한 디바이스에서 이를 실현하기 위해 노력하고 있습니다. 예를 들어, 최근 선보인 구글 넥서스원(Nexus One)은 Flash Player 10.1을 통해 탁월한 브라우저 경험을 선사할 것입니다.


    그렇다면 Apple 디바이스에서 실행 중인 Flash는 어떨까요? Adobe는 Flash 기반으로 iPhone용 독립 실행형 애플리케이션을 구축할 수 있게 함으로써, Flash 기술은 이러한 디바이스에서의 사용을 넓혀 나가기 시작했습니다. 실제로 이러한 애플리케이션 중 일부는 FickleBlox, Chroma Circuit과 같은 Apple App Store(앱스토어)에서 이미 제공되고 있습니다. 이 동일한 솔루션은 iPad에서도 작동될 것입니다. Adobe는 이러한 디바이스의 브라우저에서 Flash를 지원하기 위한 준비를 마친 상태이며 이제 Apple에서 사용자를 위해 이를 허용하는 것만이 남아 있습니다. 그러나 Apple은 지금까지도 어도비의 이러한 협력 요청을 받아들이지 않고 있습니다.


    장기적으로 볼 때, Flash에 대한 요구 사항을 대신하게 될 것으로 HTML이 꼽히고 있는 데, 특히 최근 개발된 HTML 5 버전이 출시되면 그 움직임은 본격화될 것으로 전망하는 사람들이 많아지고 있습니다. 그러나 오늘날은 물론이거니와 앞으로도 한 기술이 다른 한 기술을 대체하게 되지는 않을 것으로 예상됩니다.


    Adobe는 HTML의 발전을 지원하고 있습니다. 앞으로 HTML이 진화를 거듭할수록 Adobe 소프트웨어에 더 많은 기능이 추가될 것으로 기대하고 있습니다. HTML이 Flash의 기능을 안정적으로 수행할 수 있다면, Adobe는 많은 시간과 수고를 덜 수 있지만, 사실상 그렇게 될 가능성은 거의 없습니다. 비디오 부문의 경우 현재 웹상에 있는 비디오의 75% 이상에서 Flash가 사용되고 있는 데도 불구하고, 브라우저 간 포맷 통일이 이루어질 수 없으므로 사용자와 컨텐츠 제작자는 비호환성이라는 문제를 안게 되고 결국 HTML 비디오 구현은 어두운 뒤안길에 남겨질 것입니다.


    HTML의 진보에도 불구하고 Flash의 생산성과 표현 기능은 웹 커뮤니티에서 가희 독보적이라고 할 수 있습니다. Flash 팀은 지난 십여 년간 불가능하다고 여겼던 경험을 구현해 왔던 것처럼 앞으로도 더욱 혁신에 박차를 가할 것입니다. 1년도 채 안 되는 짧은 시간 동안 대다수의 웹 클라이언트를 업데이트할 수 있었던 Flash는 다양한 브라우저 전반에 걸쳐 HTML이 수행하는 것보다 훨씬 빠른 시간 내에 고객에게 이러한 혁신 기술을 선보일 수 있을 것으로 기대를 모으고 있습니다.


    Adobe는 시간, 장소, 매체에 구애 받지 않고 아이디어와 정보를 생성하고 전달할 수 있는 방식을 혁신하고 있으며 디자이너와 개발자가 자신의 독창적인 아이디어를 마음껏 펼칠 수 있는 방법을 고안해 내는 데 전력을 다하고 있습니다. 또한 가장 생산성이 우수한 툴과 컨텐츠 및 애플리케이션을 배포할 수 있는 광범위한 기능을 창의적인 관리 방법과 접목시키는 것 또한 Adobe의 미션이라고 할 수 있습니다. Adobe는 고객이 목표를 달성하고 기술 격차를 극복할 수 있는 기술 혁신을 이뤄내는 데 필요한 모든 기술과 포맷을 지원하고 있습니다. Flash와 HTML이 결합되면 최고의 기술이 탄생하게 될 것입니다. 그렇게 되면 누구나 웹상에서 이 기술을 사용하여 최상의 경험을 제공할 수 있게 될 것입니다.


    아이디어와 정보를 활용하는 것은 사용자가 선택한 컨텐츠와 애플리케이션을 보고 서로 인터랙션할 수 있는 개방된 에코시스템과 자유가 존재한다는 것을 의미합니다. 이 오픈 액세스 모델은 가장 효율적인 모델로 그간의 여러 시행착오를 거쳐 그 효과가 입증되었습니다. 이와 반대로 폐쇄 모델에서는 사용자가 개별 컨텐츠와 애플리케이션을 보거나 승인 및 거부할 수 있는 사항을 제조업체에서 결정하려고 했습니다. 웹은 디바이스에 상관없이 컨텐츠와 애플리케이션을 일관되게 액세스할 수 있는 개방된 환경으로 존재해야 한다는 데는 이견이 없을 것입니다.


    Adobe는 고객들이 최상의 업무 성과를 달성하고 운영 체제, 브라우저 및 다양한 디바이스 전반에 걸쳐 효과적이고 안정적으로 전세계 모든 사용자에게 다가갈 수 있도록 지속적으로 지원해 나갈 것입니다.




    원래 이런 문제가 발생하면 안되는건데... 현재 구글 크롬에서 Flash Player를 설치해도 설치되지 않은 것처럼 보여주는 버그가 있습니다. 제 컴퓨터만 그렇겠지 했는데... 회사 동료도 이런 문제가 있더군요. 허허허....

    회사 동료로부터 좋은 정보를 얻게 되어 이 문제를 해결하는 방법을 소개합니다. ^^

    http://blog.still.pe.kr/550

    Window 7에서 하실때 권한 문제같은거 발생할 수 있는데요.
    C:\Documents and Settings\[사용자 이름]\Local Settings\Application Data\Google\Chrome\Application
    까지만 들어가셔서 거기에 Plugins를 폴더를 만드시면 될 것 같습니다. ^^

    최근에 Flex 4.0 기반으로 Adobe AIR 애플리케이션을 개발하는 프로젝트를 맡았다. 현시점(2010.2.1) Flex 4.0 SDK에 함께 있는 AIR SDK 버전은 한단계 구버전인 1.5.2이기 때문에 가장 최신 버전인 1.5.3을 기존 Flex 4.0 SDK에 덮어씌운 상태에서 개발을 진행했었다. 문제없이 개발할 수 있다.

    그런데 애플리케이션 배포시 문제가 발생했다. AIR 애플리케이션을 설치하면 설치 디렉토리 밑에 META-INF/AIR/publisherid 파일이 존재해야 한다. 그런데 이 파일이 없는 것이다. publisherid는 인증서의 고유 ID를 담고 있다. 이 파일이 없는 상태에서 배포, 업데이트 모두 잘되지만 badge에서 이미 설치된 AIR 애플리케이션이 있는지 여부를 판단할 수 없었다. 사실 배포,업데이트가 잘 되기 때문에 이 상태로 배포해도 문제는 없지만 너무 꺼름칙했다. (AIR의 장점중 하나가 쉽게 설치된 AIR 애플리케이션과 Flash의 연동이 큰 장점 아니던가....)

    이 문제의 원인을 찾고자 별짓을 다하다가 SDK 문제라는 것에 귀결을 내리게 되었다. 문제는 Flex 4.0 SDK에 AIR 1.5.3 SDK를 덮은것에서 비롯되었다. 기존 Flex 4.0 SDK에서 AIR 1.5.2 기반으로 만들어 배포하면 Publisher ID 파일이 있지만 AIR 1.5.3으로 덮어씌우고 배포하면 이 파일이 없었던 것이다. 개발, 컴파일까지 모두 잘되는데 말이다. ㅎㅎ 

    결국 나는 Flex 4.0 기반으로 애플리케이션을 거의 완성한 상태라서 AIR 1.5.2 기반으로 배포할 수 밖에 없게 되었다. 편리하고 빠른 개발을 위해 Flex 4.0를 선택했지만 AIR 버전이 구버전으로 인한 문제점은 정말 예상치 못한 일이었다.

    개발하다 보면 별에 별일이 많은데... 정말 이렇게 크리티컬한 상황까지 보게 되니 정말 웃기기 까지 한다.

    질문?
    Flex 4.0에 AIR 1.5.3을 덮어씌워도 publisher id 파일이 누락되지 않도록 하는 방법은 뭘까요? 혹시 알게되면 댓글이나 트랙백 부탁합니다.

    추가 1
    Flex 3의 최신버전인 Flex 3.5의 경우에는 AIR 1.5.3 SDK가 함께 있는데... Flex 4는 왜???? ㅡㅡ;;

    추가 2
    Flex 4.0 SDK의 beta 2 버전은 4.0.0.10485 이다. 이 버전은 AIR 1.5.2 SDK이다. 하지만 최신 Nightly Builder 버전으로 다운로드 받으면 AIR 1.5.3이 기본이다. 그냥 이걸로 개발해야겠다.

    추가 3
    Adobe AIR 1.5.3 부터는 PublisherID가 기본적으로 제거됩니다. http://blog.jidolstar.com/655

    글쓴이 : 지돌스타(http://blog.jidolstar.com/652)




    지난 22일에 Adobe Labs에서 Adobe Stratus 2에 대한 소개가 있었습니다.

    http://labs.adobe.com/technologies/stratus/

    Stratus는 RTMFP(Real Time Message FlowProtocal)이라 불리우는 통신프로토콜을 이용해 Flash Player 10이 설치된 클라이언트 까지 P2P(Peer to Peer)가 가능하게한 기술입니다. TCP가 아닌 UDP기반으로 동작하고요.

    이번 Stratus 2는 Adobe Flash Player 10.1 beta, Adobe AIR 2 Beta에서 동작하도록 만들어졌습니다. 재미있게도 이번 버전에서는 애플리케이션 수준에서 멀티케스트와 소스 공급자의 로드를 줄여주는 구조를 지원하게 되었습니다. 맨 위의 그림에서 좌측 그림은 기존 Flash Player끼리 통신하기 위해 서버를 항상 거쳐야만 했었습니다. 서버입장에서는 완전 부담이죠. 그런데 중간 그림에서 볼 수 있듯이 Stratus가 나오면서 서버와는 한번의 접속으로 하고 Flash Player끼리 통신이 되었습니다. 이것도 소스 공급자인 Flash Player 입장에서는 로드가 심할 수 있습니다. 이를 Flash Player 단위에서 분산시키도록 한 것이 이번 배포의 핵심인 것 같네요.

    물론 Stratus 2는 아직 베타버전인 Flash Player 10.1과 AIR 2.0에서만 동작하므로 바로 실무에 사용할 수는 없습니다. 그러나 조만간 이 기술을 사용할 수 있게 되겠죠.

    Stratus 1.0에 대한 많은 실험이 국내에도 있었습니다. 저도 조금 테스트 해봤었고요.
    http://blog.jidolstar.com/498 

    다른 분들의 글들입니다.
    플래시 P2P RTMFP에 대해(예제 파일 첨부)
    [Flex] Stratus를 이용한 P2P방식의 Flash간에 1:1 다이다이 오목게임-_-
    Flash로 p2p를 만들어보자 - Stratus 라이브러리
    Flash로 채팅을 구현해보자.


    하지만 방화벽, 공유기를 통해 접근 문제가 해결되었는지는 미지수네요.(테스트 안해봤음)
    이 문제만 해결되면 정말 멋진 애플리케이션이 많이 나올거라 생각됩니다. ^^

    글쓴이: 지돌스타(http://blog.jidolstar.com/651)





    Adobe AIR에는 Webkit 브라우져 엔진이 내장되어 있어 IE나 FireFox와 같은 브라우져 기능을 만들 수 있다. 단순히 그런 기능만 제공하는 거라면 흥미가 떨어지겠지만 중요한 점은 로드되는 컨텐츠에 DOM형태로 접근할 수 있어 실제로 Element를 추가하거나 뺄 수 있고 함수까지 재정의도 가능하다. 이러한 작업을 Javascript에 대한 지식이 있다면 쉽게 할 수 있기 때문에 이것을 이용한 다양하고 재미있는 시도를 해볼 수 있다.

    개발환경은 Flash Builder 라고 가정하겠다. 물론 Flash IDE에서 해도 무방하다.


    AIR에서 DOM 접근의 예

    AIR에서 DOM에 접근해보는 재미있는 예를 들어보겠다.

    Javascript에는 alert() 함수가 존재한다. 알다시피 경고창 띄워주는 함수이다. 이 함수를 AIR에서 커스터마이징할 수 있다. 가령, 원래기능인 경고창을 띄워주지 않고 넘겨준 문자열 정보를 Flash Builder의 Console창에 출력하도록 하는 것이다. 이 기능을 구현해보자.

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
    <title>Test</title>
    <style type="text/css">
    	body { background-color: #aaaaaa; }
    </style>
    </head>
    <body>
    	<input type="button" value="alert( 'Hello' )" onclick="alert('Hello')"> 
    	<input type="button" value="alert( 'I', 'love', 'you' )" onclick="alert('I', 'love', 'you')">
    </body>
    </html>
    

    위 HTML코드는 Button을 누르면 alert() 함수로 경고창을 띄워준다.

    AIR에서 이 HTML 문서를 로드하는데 HTMLLoader 클래스로 할 수 있다. 이 클래스는 Webkit 엔진을 한번 감싸서 AIR개발자가 손쉽게 사용할 수 있도록 한다. HTMLoader의 load()를 이용해 HTML문서를 로드하고 alert를 아래 코드와 같이 재정의 할 수 있다.

    var _html:HTMLLoader = new HTMLLoader();
    _html.addEventListener( Event.HTML_DOM_INITIALIZE, __ON_HTML_DOM_INITIALIZE );
    _html.load( new URLRequest("test.html") );
    stage.addChild(htmlLoader);
    
    private function __ON_HTML_DOM_INITIALIZE($e:Event):void {
    	//javascript의 alert 함수를 actionscript에서 재정의했다.
    	_html.window.alert = function():void {
    		trace( arguments );
    	}			
    }
    

    참고 1. 위 코드는 완벽한 코드는 아니다. 중요부분만 떼어내었다.
    참고 2. _html.window.alert 내에 재정의 된 함수에 arguments가 뭐냐고 물어보신다면... 
    Adobe문서를 참고하자. 이런것이 가능한 것은 Actionscript와 Javascript가 모두 ECMAScript 규약을 따르고 있기 때문이다.

    Event.HTML_DOM_INITIALIZE 이벤트는 HTML DOM이 만들어졌음을 알려주는 이벤트로 이때부터 HTML의 DOM에 접근할 수 있다. 하지만 모든 DOM이 만들어지는 것을 의미하지 않으며 가장 기본적인 구조만 접근이 가능해진다. 완벽한 DOM구조에 접근하려면 Event.COMPLETE 이벤트를 핸들링해야한다. 

    위 ActionScript 코드의 __ON_HTML_DOM_INITIALIZE() 이벤트 핸들러에서 브레이크 포인트를 찍어서  디버깅 모드로 실행해보면 다음과 같은 DOM구조를 볼 수 있다.


    위처럼 내부적으로 정의된 __HTMLScriptObject 객체가 HTMLLoader의 window속성으로 참조되어 있는 것을 확인할 수 있고 alert도 여기에 포함되어 있는 것을 확인할 수 있을 것이다. 저기에 있는 HTML 내부 속성을 모두 개발자가 커스터마이징 할 수 있다는데 매우 흥미를 느끼지 않는가?

    Flash Builder에서 디버깅 모드로 실행해보면 다음 처럼 윈도우가 나오고 test.html이 HTMLLoader에 로드되는 것을 볼 수 있다. 



    위 버튼을 누르면 누를때마다 alert창이 뜨지않고 다음처럼  Flash Builder의 Console창에 출력된다.


    굳이 Event.COMPLETE가 아니라 Event.HTML_DOM_INITIALIZE 이벤트 발생시 alert를 재정의 하는 것은 Event.COMPLETE 이전에 Javascript가 얼마든지 실행될 수 있기 때문이다. 그러므로 어느때든지 alert가 원래기능을 수행하지 못하게 하는데 가장 적절한 시점은 바로 Event.HTML_DOM_INITIALIZE 이벤트가 발생할 때이다.


    사용자 정의 Trace() 만들자.

    AIR의 HTMLLoader를 통해 불려지는 HTML의 디버깅은 쉽지 않다. 왜냐하면 Flash Builder나 FireFox와 같은 디버깅 툴을 사용할 수 없기 때문이다. 이 때문에 복잡하게 만들어진 JavaScript 코드와 HTML이 섞여 있는 경우라면 어떤 경우에 문제가 발생하는지 찾는것 조차 어려워진다. 

    Flash Builder에서는 trace()문을 사용하면 디버깅에 크게 도움이 된다. 비록 브레이크 포인트를 찍으면서 값을 추적할 수 없지만 trace()문 하나만으로도 많은 부분 문제를 해결하는데 도움을 준다.

    그럼 JavaScript내에 강제적으로 trace()함수를 정의하고 개발자가 필요할때 HTML문서상에서 이 함수를 호출하면 AIR 애플리케이션에 trace으로 들어온 인자값을 출력해주면 어떨까? alert()를 커스터마이징 했었다는 것을 충분히 이해했다면 이 부분도 그리 어려운 것은 아니다.

    private function __ON_HTML_DOM_INITIALIZE($e:Event):void {
    	_html.window.trace = function():void {
    		trace( arguments );
    	}			
    }
    

    참고 : _html.window.trace는 로드되는 HTML DOM에 trace()함수를 정의한 것이고 함수내에 trace()는 ActionScript의 trace라는 것을 인식하자.


    이미 보여준 ActionScript 코드에서 alert 재정의 대신 trace를 정의하도록 만들었다. 이미 언급한 HTML문서에서 onclick="alert()" 구문대신 onclick="trace()" 로 바꿔보자.

    디버깅 모드로 실행하면 Flash Builder의 Console창에 버튼을 클릭할때마다 메시지가 보이는 것을 확인할 수 있을 것이다.

    이 방법은 꽤 유용하다. 웹개발자가 만든 HTML문서가 AIR의 Webkit에도 제대로 동작하지 않는 경우도 꽤 발생한다. 이런경우 어느 지점에서 문제가 되는지 알 수 없어 alert()만 의지해서 디버깅하는 것은 웹개발자에게 너무 가혹하다. AIR개발자는 웹개발자를 위해 이 정도의 기능을 최소한으로 제공해서 웹개발자의 어려움을 덜어주어야 한다고 생각한다.


    몇가지 문제점 극복하기

    하지만 아직까지 문제는 있다.

    1. IFrame으로 로드되는 컨텐츠는 AIR에서 정의한 trace를 직접사용할 수 없다.
    이런 점을 극복하기 위해서 iFrame에 로드되는 문서에서는 parent.trace() 처럼 사용하면 어느정도 해결이 된다.

    2. 기존에 사용하는 HTML문서가 이미 웹상에서 서비스되고 있을때 HTML내 실수로 trace()문을 그대로 남겨두게 되는 경우 문제가 발생한다. 왜냐하면 AIR에 로드되는 경우는 강제적으로 trace()함수를 정의하지만 일반 웹브라우져에서 로드되는 경우에는 trace()정의가 없으므로 trace()함수를 호출하면 에러를 던질 것이기 때문이다. 물론 이런 경우가 발생하지 않도록 웹개발자의 꼼꼼한 테스트가 필요하지만 그게 말처럼 쉬울까?

    이처럼 웹상에서 trace()가 쓰여도 웹개발자의 실수로 인해 trace()를 지우지 못해 발생하는 문제를 해결할 방법은 있다. 먼저 아래처럼 common.js 파일을 만든다.
    try {
    	trace; //trace문이 정의되었는가?
    } catch(e) {
    	trace = function() {}; //정의되어 있지 않으면 강제로 만들어준다.
    }
    

    그 다음 서비스되고 있는 모든 HTML에 common.js를 아래처럼 import한다.

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
    <title>Test</title>
    <script type="text/javascript" src='common.js'></script>
    <style type="text/css">
    	body { background-color: #aaaaaa; }
    </style>
    </head>
    <body>
    	<input type="button" value="trace( 'Hello' )" onclick="trace('Hello')"> 
    	<input type="button" value="trace( 'I', 'love', 'you' )" onclick="trace('I', 'love', 'you')">
    </body>
    </html>
    

    모든 HTML문서에 common.js가 있어야 한다는 점을 기억하자. 중요하게 봐야할 것은 common.js의 동작방식이다. 이 HTML문서를 AIR에서 로드되면 DOM이 생성되자마자 trace를 정의하므로 try..catch문에서 catch()문으로 가지 않는다. 하지만 일반 웹브라우져에서 로드하면 trace가 생성되지 않아 catch()문이 실행되게 된다. 그러니 웹개발자가 trace()문을 실수로 지우지 않아도 AIR에 로드되든 일반 웹브라우져에 로드되든 문제가 발생하지 않는다.


    실용적으로 만들어보자. 

    AIR에 로드되는 HTML문서에서 trace()문을 이용하는 방법을 알 수 있게 되었다. 이를 좀더 실용적으로 테스트 해볼 수 있도록 만들기 위해서는 trace()의 결과가 console창에만 나오는 것이 아니라 별도의 창을 띄워서 언제든지 trace()내용을 확인할 수 있도록 하는 것이 좋다. 이것은 AIR 개발자와 웹페이지 개발자가 다른 경우 AIR개발자가 웹페이지 개발자에게 해줄 수 있는 최소한의 배려이다.

    여기서는 최소한의 기능만 보여주려고 한다. 아래처럼 창 2개가 있다. 좌측창은 HTML를 로드한 지금까지 예제이고, 우측창은 trace문의 결과물을 보여주는 창이다.


    이것을 구현하기 위해 ActionScript 3.0으로 만들어보자. Flash Builder에서 개발한다면 Flex 프로젝트를 생성하되 Application type은 Desktop(AIR)로 하고 Main Application file 이름을 .mxml이 아닌 .as로 끝나는 확장자를 가지도록 만들어야한다. 나는 Main.as로 만들었다.

    Main.as
    package {
    	import flash.display.NativeWindow;
    	import flash.display.Sprite;
    	import flash.events.Event;
    
    	/**
    	 * 메인 애플리케이션 
    	 * @author jidolstar
    	 */
    	public class Main extends Sprite {
    		public function Main() {
    			//브라우져 띄우기 
    			var window:BrowserWindow = new BrowserWindow();
    			window.width = stage.stageWidth;
    			window.height = stage.stageHeight;
    			window.load( "test.html" );
    			window.activate();
    		}
    	}
    }
    


    BrowserWindow.as


    package {
    	import flash.display.NativeWindow;
    	import flash.desktop.NativeApplication;	
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.html.HTMLLoader;
    	import flash.net.URLRequest;
    
    	/**
    	 * Trace 테스트용 브라우저 윈도우
    	 * @author jidolstar
    	 */
    	public class BrowserWindow extends NativeWindow {
    		private var _html:HTMLLoader;
    		private var _trace:TraceWindow;
    
    		/**
    		 * 생성자 
    		 */
    		public function BrowserWindow() {
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			
    			//윈도우 옵션
    			var initOptions:NativeWindowInitOptions = new NativeWindowInitOptions();
    			initOptions.type = NativeWindowType.NORMAL;
    			super(initOptions);
    			
    			//HTML 로더 
    			_html = new HTMLLoader();
    			_html.width = stage.stageWidth;
    			_html.height = stage.stageHeight;
    			_html.addEventListener(Event.HTML_DOM_INITIALIZE, function ($e:Event):void {
    				//DOM에 trace 함수를 정의한다. 
    				_html.window.trace = function():void {
    					var str:String = "";
    					for( var i:int = 0; i < arguments.length; i++ ) {
    						str += arguments[i] + " ";
    					}
    					//결과물을 창에 출력해준다.
    					_trace.addString( str );
    				}
    			});
    			stage.addChild(_html);
    			
    			//Trace 결과물을 보여주는 창 
    			_trace = new TraceWindow();
    			_trace.width = 300;
    			_trace.height = 300;
    			_trace.activate();
    			
    			//창이 닫힐때 모든 윈도우를 닫는다.
    			addEventListener(Event.CLOSING, function($e:Event):void {
    				var openedWindows:Array = NativeApplication.nativeApplication.openedWindows;
    				for each( var window:NativeWindow in openedWindows ) {
    					window.close();
    				}
    			} );	
    			
    			title = "browser";
    		}
    		
    		public function load($url:String):void {
    			_html.load( new URLRequest( $url ) );
    		}
    		
    	}
    }
    


    TraceWindow.as

    package {
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;	
    	import flash.text.TextField;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	
    	/**
    	 * Trace를 출력해주는 윈도우
    	 * @author jidolstar
    	 */
    	public class TraceWindow extends NativeWindow {
    		private var _textField:TextField;
    		private var _traceText:String = "";
    
    		public function TraceWindow() {
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    
    			var initOptions:NativeWindowInitOptions = new NativeWindowInitOptions();
    			initOptions.type = NativeWindowType.NORMAL;
    			super(initOptions);
    
    			_textField = new TextField();
    			_textField.width = stage.stageWidth;
    			_textField.height = stage.stageHeight;
    			stage.addChild(_textField);
    			
    			title = "trace window";
    		}
    
    		public function addString($value:String):void {
    			_textField.appendText($value + "\n");
    		}
    	}
    }
    


    물론 이것은 ActionScript 3.0 코드이고 이미 언급한 HTML(test.html) 및 common.js 도 함께 소스에 포함해야한다.

    아래는 위 코드들을 압축한 소스이다.


    예제는 단순하지만 더 응용해서 컴포넌트화 시키면 좋을 것이다.


    정리하며
    Adobe AIR의 장점은 크로스 OS, 기존 Flash/Flex 개발자의 접근 향상 정도만 있는 것은 아니다. Adobe는 웹과 매우 친하면서도 데스크탑 영역으로까지 확장했다. 여기서 웹과 매우 친하다는 말에 주목할 필요있다. 웹브라우져에 올라온 Flash와 AIR가 별도의 설치 없이 서로 통신할 수 있고 웹브라우져에서 AIR를 실행할 수도 있다. 또한 오늘 언급한 것처럼 dom에 대한 접근이 매우 쉬운 것도 있다. 아직까지 AIR가 성능 및 기술적 한계가 분명 존재하지만 버전업을 거듭하면서 더욱 나아지고 있다. AIR의 장점을 명확히 인식한다면 AIR만의 매력을 느낄 수 있지 않을까 생각한다.

    아래 링크로부터 AIR에 관련된 정보를 얻을 수 있다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/650)


    제가 ACP(Adobe Community Professional)가 되었습니다.
    이로써 Adobe 공인 전문가 그룹에 등록 되었습니다.

    http://lizfrederick.blogspot.com/2010/01/new-acps-for-2010.html

    국적과 상관없이 약 300명 정도가 등록되었습니다. 분류로는 Acrobat, AfterEffect, AIR, Authorware, Captivate, ColdFusion, Creative Suite, Dreamweaver, Fireworks, Flash Media Server, Flash Mobile, Flash Platform, Flash Platform(RIA), Flash Professional, Flex, FrameMaker, Illustrator, InDesign, InDesign Server, Lightroom, LiveCycle ES, Photoshop, PhotoShop Elements, Premiere Pro, Production Premium, RoboHelp, Spry, Visual Communicator등이 있고 저는 Flash Platform(RIA) 분야로 등록되었네요.

    저는 모르고 있었는데 우야꼬가 전화로 축하해주더군요. ^^;
    메일을 보니까 한국 어도비로부터 축하 메일이 왔더군요.
    저 외에 한국에서 ACP로 선정된 분들입니다.

    강성규(땡굴이)
    이정웅(블루메탈)
    이준하(열이아빠)
    배준균

    ACP 후보 추천시 저에 대해서 작성된 내용입니다. (물론 영어로 번역되었겠죠...)

    Flash Platform 기술에 대해 큰 관심을 가지고 있으며 그 기술에 대해 한국어 블로그(http://blog.jidolstar.com)를 운영하고 있다. 한국 어도비 RIA 홈페이지에 분기별로 기술문서를 기고하고 있다. 또한 ACC로써 Flash Platform 기술 전도사로 활동하고 있다(http://adoberia.co.kr/acc/acc.html). 한국어 Flex문서가 없다. 그래서 그는 flexdocs.kr 이라는 사이트를 구축했다. 그는 회사에서 Flash 개발팀장으로 근무하고 있으며 한국 SNS인 Starpl에 Flash 기술을 적용하는데 일조했다(http://starpl.com).

    여러가지로 부족한데 선정해준 Adobe사에 감사하며 이름에 걸맞게 열심히 활동하고 실력도 쌓아가도록 노력하겠습니다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com)
    HTML 문서내에 SWF와 JavaScript는 ExternalInterfacecalladdCallback 메소드를 이용하면 통신할 수 있다. ExternalInterface.addCallback( "myMethod", myMethod);로 정의하면 JavaScript에서 SWF의 myMethod를 호출할 수 있게 된다. myMethod가 function myMethod( params:Object ); 형태로 만들었다면 JavaScript에서 만든 {a:1, b:2}와 같은 값이 전달될 수 있다. 당연한거다.

    AIR 1.5.3 환경에서 HTMLLoader 클래스를 이용해 위처럼 정의된 HTML 문서를 로드한다고 하자. 이 때는 Object값을 JavaScript에서 SWF내에 정의된 myMethod로 넘길 수 없다. String이나 Number 형태의 값이라면 상관없지만 Object는 안된다. 버그다. 결국 Object의 형태를 띈 String을 넘겨주고 그 String을 다시 SWF내에서 Object로 변환해주어야 한다. 이것을 구현하기 위해 json.js을 사용했지만 JSON객체를 찾을 수 없다며 에러를 던진다. 그래서 Prototype의 Object.toJSON을 이용했다. 물론 ActionScript쪽에서는 as3corelib에 있는 JSON 클래스를 활용하면 된다. String과 Object형태의 String을 어떻게 구분할지 묻는다면 try..catch 문을 활용하면 된다고 답변하고 싶다.

    이 때문에 엄청 삽질하게 생겼다. ExternalInterface.call() 메소드의 두번째 인자가 ...arguments 형태라는 것을 감안하고 만들었다면 이런 고생 안했을텐데... 

    글쓴이: 지돌스타(http://blog.jidolstar.com


    "Flash로 못할 것이 없다."라는 말을 들어보셨나요? 이번에 소개하는 것은 beautifl.net에 올라온 Flash 코드중 개인적으로 자극을 받은 것만 선정해본 것들입니다. wonderfl.net에 올라온 것들이기 때문에 코드도 공개되어 있어 언제든지 분석도 할 수 있습니다. Flash의 세계를 만끽해보세요.

    보러가기 : http://opencast.naver.com/FL188/16

    글쓴이 : 지돌스타(http://blog.jidolstar.com/646)

    나는 올해 멋진 밤하늘을 보여줄 수 있는 모바일 애플리케이션 및 Flash 애플리케이션을 만드는 것을 목표로 삼았다. 모바일이나 Flash와 같이 렌더링하는데 어려운 환경에서 어떻게 최적화 하는가가 큰 이슈가 되지 않을까 생각이 든다. 아래는 간단하게 만들어본 밤하늘의 모습이다. Flash Player에서 거의 40~50 FPS로 나오는것으로 봐서 이것 조차도 느리다.



    각종 천체목록과 연결되고 이미지 서비스도 된다. 천체에 대한 정보도 보여주고 태양계(행성,혜성,위성) 정보도 볼 수 있다. 각 지역에서의 밤하늘 정보를 보여주며 각종 천문현상도 볼 수 있다.

    아무튼 일단 너무 큰거 말고 작은것부터 차근차근 만들어 봐야겠다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/645)

    LCDS, BlazeDS, ZendAMF등을 이용해 AMF(ActionScript Message Format)으로 데이터를 주고받는 형태는 이미 많은 예제들과 문서들이 있다. AMF는 데이터 통신을 위한 일종의 약속된 규약이고 이 데이터를 주고 받고 하는 과정에서 서로의 언어에 맞게 직렬화(Serialization)하는 것은 각 언어에서 지원해주는 라이브러리를 사용하면 된다. ActionScript 3.0은 기본 API에서 지원해준다. 자바의 경우는 BlazeDS나 LCDS를 사용하면 된다. PHP의 경우에는 ZendAMF를 사용하면 된다. 이들을 이용하면 가령, 자바와 java.lang.Interger는 ActionScript의 int나 uint로... java.lang.Double은 ActionScript의 Number형과 직렬화된다. 이는 일종의 기본적으로 지원되는 직렬화이다.

    다음은 BlazeDS에서 기본적으로 지원되는 직렬화이다.
    Serializing between ActionScript and Java

    하지만 이런 기본 직렬화과정을 사용하지 않고 직렬화 자체를 커스터마이징(customizing)할 수 있다. 가령 클라이언트(예,Flash 애플리케이션)에서는 아이디, 이름, 속성, 가격의 정보가 중요하지만 서버(예,자바)에서는 재고(inventory)가 중요한 경우가 있다. 이런 경우에는 Flex와 Java쪽의 직렬화하는 클래스를 다음과 같이 디자인 할 수 있겠다.

    // Product.as
    package samples.externalizable {
    
    import flash.utils.IExternalizable;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    
    [RemoteClass(alias="samples.externalizable.Product")]
    public class Product implements IExternalizable {
        public function Product(name:String=null) {
            this.name = name;
        }
    
        public var id:int;
        public var name:String;
        public var properties:Object;
        public var price:Number;
    
        public function readExternal(input:IDataInput):void {
            name = input.readObject() as String;
            properties = input.readObject();
            price = input.readFloat();
        }
    
        public function writeExternal(output:IDataOutput):void {
            output.writeObject(name);
            output.writeObject(properties);
            output.writeFloat(price);
        }
    }
    }
    


    // Product.java
    package samples.externalizable;
    
    import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    import java.util.Map;
    
    /**
    * This Externalizable class requires that clients sending and 
    * receiving instances of this type adhere to the data format
    * required for serialization.
    */
    public class Product implements Externalizable {
        private String inventoryId;
        public String name;
        public Map properties;
        public float price;
    
        public Product()
        {
        }
    
            /**
            * Local identity used to track third party inventory. This property is
            * not sent to the client because it is server-specific.
            * The identity must start with an 'X'.
            */
            public String getInventoryId() {
                return inventoryId;
            }
    
            public void setInventoryId(String inventoryId) {
                if (inventoryId != null && inventoryId.startsWith("X"))
                {
                    this.inventoryId = inventoryId;
                }
                else
                {
                    throw new IllegalArgumentException("3rd party product
                    inventory identities must start with 'X'");
                }
            }
    
            /**
             * Deserializes the client state of an instance of ThirdPartyProxy
             * by reading in String for the name, a Map of properties
             * for the description, and 
             * a floating point integer (single precision) for the price. 
             */
            public void readExternal(ObjectInput in) throws IOException,
                ClassNotFoundException {
                // Read in the server properties from the client representation.
                name = (String)in.readObject();
                properties = (Map)in.readObject();
                price = in.readFloat();
                setInventoryId(lookupInventoryId(name, price));
            }
            /**
             * Serializes the server state of an instance of ThirdPartyProxy
             * by sending a String for the name, a Map of properties
             * String for the description, and a floating point
             * integer (single precision) for the price. Notice that the inventory 
             * identifier is not sent to external clients.
             */
            public void writeExternal(ObjectOutput out) throws IOException {
                // Write out the client properties from the server representation
                out.writeObject(name);
                out.writeObject(properties);
                out.writeFloat(price);
            }
            
            private static String lookupInventoryId(String name, float price) {
                String inventoryId = "X" + name + Math.rint(price);
                return inventoryId;
            }
    }
    

    위 코드는 ActionScript의 flash.utils.IExternalizable 인터페이스와 Java의 java.io.Externalizable 인터페이스를 이용해 기본직렬화를 무시하고 이들 인터페이스에 정의된 readExternal와 writeExternal 메소드를 호출하여 직렬화 자체를 커스터마이징 할 수 있다는 것을 의미한다. Externalizable 인터페이스를 구현한 클래스에 정의된 메소드가 기본 직렬화보다 우선순위가 높다는 것을 기억하면 되겠다.

    Flex의 ArrayCollection을 다시 한번 보기 바란다. 이 클래스는 flash.utils.IExternalizable를 구현했다.
    mx.collections.ArrayCollection

    꼭 이런 경우만은 아니다. 쓸데없는 데이터의 크기를 줄이기 위한 방법도 해당한다. 예를들어 ActionScript 코드를 보면 다음과 같다.


    class Example implements IExternalizable {
      
          public var one:Boolean;
          public var two:Boolean;
          public var three:Boolean;
          public var four:Boolean;
          public var five:Boolean;
          public var six:Boolean;
          public var seven:Boolean;
          public var eight:Boolean;
           public function writeExternal(output:IDataOutput) {
               var flag:int = 0;
               if (one) flag |= 1;
              if (two) flag |= 2;
              if (three) flag |= 4;
              if (four) flag |= 8;
              if (five) flag |= 16;
              if (six) flag |= 32;
              if (seven) flag |= 64;
              if (eight) flag |= 128;
               output.writeByte(flag);
          }
           public function readExternal(input:IDataInput) {
               var flag:int = input.readByte();
               one = (flag & 1) != 0;
              two = (flag & 2) != 0;
              three = (flag & 4) != 0;
              four = (flag & 8) != 0;
              five = (flag & 16) != 0;
              six = (flag & 32) != 0;
              seven = (flag & 64) != 0;
              eight = (flag & 128) != 0;
          }
     }
    

    데이터 통신을 위해 쓸데없이 Boolean객체를 주고 받을 필요없다. 위 코드처럼 직렬화를 커스터마이징한다면 송수신 데이터 자체의 크기도 줄일 수 있다. 이는 매우 유용하다.


    참고글
    flash.utils.IExternalizable
    커스텀 직렬화의 사용
    ActionScript 3.0 데이터 유형 및 직렬화(serialization)
    Serializing between ActionScript and Java
    AMF 3 스팩
    AS3 BitmapData AMF solution using IExternalizable
    Flex and PHP: remoting with Zend AMF

    글쓴이 : 지돌스타(http://blog.jidolstar.com/644)


    아래는 Papervision3D에 쓸 수 있는 Primitive인 GeodesicSphere 클래스를 만들어 테스트한 화면이다.


    위 프로그램의 소스는 아래 링크를 참고한다.
    http://wonderfl.net/code/e6f14ed092706ba9f539f64da252a92200724769

    소스코드상에서 GeodesicSphere 클래스 부분만 제외하고 전부 테스트 코드이다.

    Away3DLite환경에서 만든 GeodesicSphere도 만들었다.

    http://wonderfl.net/code/5dcd89ac60896ae55604aa018747bc0d861b2bff

    참고글 : http://blog.jidolstar.com/589

    글쓴이 : 지돌스타(http://blog.jidolstar.com/643)



    Flash 애플리케이션을 만들때  통합개발환경(IDE)인 Flash CS3, CS4 나 Flex Builder, Flash Builder, FDT와 같은 툴을 주로 사용할 것이다. 실무에서는 당연히 이런 툴을 선택할 수 밖에 없다. 그러나 이들은 사실 유료이고 무겁다.

    Flash MiniBuilder라는 프로그램은 간단하게 ActionScript 3.0 기반으로 개발할 수 있도록 Adobe AIR로 만들어진 가벼운 개발툴이다. 이것은 Victor Drâmbă라는 사람이 만들었으며 그의 블로그(http://www.victordramba.com)를 가보면 MiniBuilder에 대한 소식을 볼 수 있다.

    MiniBuilder는 Flex SDK 3와 Java JRE 설치를 먼저 해야 정상적으로 사용할 수 있으며 사용하는 방법은 다음 동영상에서 잘 나와있다. 약간 복잡해 보이지만 중간에 start.bat를 실행해야하는 번거로움은 AIR 2.0에서는 없어질 것이라 생각한다.
    http://www.youtube.com/watch?v=bjEc2eT_rCE&feature=player_embedded


    이 프로그램은 Flex SDK에서 제공하는 컴파일러를 이용해서 만들어진 ActionScript 3.0코드를 컴파일하게 된다. 워낙 가볍기 때문에 Flash Builder에서 느껴지는 묵직함은 찾아볼 수 없다. 일반적인 코딩도 별 어려움이 없고 또한 코드힌트기능 및 에러메시지 출력 기능도 있기 때문에 이 또한 유용하다. 아쉬운 점은 디버깅을 할 수 없고 한글 주석도 못단다는 점.

    MiniBuilder는 공개소스이다. GPL 라이센스를 가지며 Google Code(http://code.google.com/p/minibuilder/)에서 전반적인 설명과 소스코드를 볼 수 있다. 원한다면 Flex Builder 3나 Flash Builder 4에서 SVN으로 다운로드 받아 직접 소스를 테스트해볼 수도 있다. (본인도 SVN으로 다운받아 해봤다. 자바소스까지 포함되어 있으므로 Eclipse+Flex Builder 3 Plug-in 환경에서 작업할 것을 권한다. MXML 사용 안하고 전부 ActionScript 3.0으로 만들었다.)



    MiniBuilder는 AIR뿐 아니라 Flash버전으로도 제작되어 유명한 ActionScript 3.0 코드 공유사이트인 Wonderfl의 에디터로도 활용되었다. 다음 제작자의 글을 보면 그에 대한 소개가 나와있다.
    http://www.victordramba.com/?p=71



    벌써 이것이 만들어져 소개된지 몇개월 되었는데 이렇게 멋진 공개 프로그램이 한국에 소개된게 딱 하나밖에 못본 것 같아 간단하게 소개해본다.

    참고 링크
    Victor Drâmbă의 블로그 : http://www.victordramba.com
    Flash MiniBuilder Google Code : http://code.google.com/p/minibuilder
    설치 및 사용방법 : http://www.youtube.com/watch?v=bjEc2eT_rCE&feature=player_embedded


    글쓴이 : 지돌스타(http://blog.jidolstar.com/642)


    지난 2009년 12월 18일에 프로젝트 코드명 Squiggly(스퀴글리)가 Prerelease판으로 업데이트 되었다.
    http://labs.adobe.com/technologies/squiggly/

    Squiggly는 Flash Player 10, AIR 1.5이상에서 동작한다. 이 엔진은 ActionScript 3.0 기반이며 영어로 문장에서 사용된 단어를 영어사전의 그것과 비교하여 단어의 철자를 체크한다. 이번 업데이트는 TLF(Adobe Text Layout Framework)를 기반으로 하는 Flex 4 Spark 컴포넌트를 지원하도록 했다. Squiggly는 Flex Builder, Flash Builder, Flash CS4등 다양한 툴에서 사용할 수 있다. 단지 아쉬운 것은 영어단어만 체크한다는 점이다.


    다음 페이지는 Squiggly 데모이다.
    http://labs.adobe.com/technologies/squiggly/demo/

    개발자는 언제든지 예제소스와 ASDoc을 아래 링크에서 받아볼 수 있다.


    이것을 쓸지 모르겠지만 아무튼 알아두고 있어 나쁠 것도 없으니....

    참고
    http://adnaru.com/242

    글쓴이 : 지돌스타(http://blog.jidolstar.com/640)

     


    Flash, Flex, AIR, ActionScript 프로젝트를 진행하면서 모든 코드를 손수 만들어 사용하는 것은 그다지 추천할 만한 일은 아니다. 왜냐하면 아무리 실력있는 개발자라도 프로젝트에 필요한 코드모음을 전부 만드는 것은 너무 큰 노력이 들뿐만 아니라 코드의 유지보수에도 매우 많은 시간을 허비할 수 있기 때문이다.

    Flash 기반 코드 라이브러리는 이미 수년간에 걸쳐 공개되어 있다. 조금만 노력을 기울이면 3D, 트윈, 물리엔진, 수학, 보안, 데이터관리, MVC,   as3corelib, Papervision3d, Away3d, Flex 컴포넌트, 각종 유틸등 ActionScript/Flex/AIR/Flash에 관련된 수십,수백가지 우량(?) 라이브러리들을 쉽게 사용할 수 있다.


    위에 링크되어 있는 라이브러리만 해도 정말 현업에 바로 사용할 수 있는 것들이 정말 많다. 또한 단순히 공개차원을 넘어 지속적으로 업데이트가 되며 완성도가 높아지고 있다.

    그럼 다시 생각해보자. 내가 3D 게임을 만들기 위해 3D 엔진을 만들어야할 것인가? 내가 트윈(tween)을 구현하기 위해 트윈엔진을 만들어야 할것인가? 각종유틸이 필요해서 그걸 다 내가 만들것인가? 만약 검증되지 않고 아직 별도로 공개되어 있지 않은 것이라면 당연히 만들어야할 것이다. 하지만 기본적으로 큰 노력없이 쉽게 사용할 수 있고 자동으로 업데이트 및 검증까지 해주는 사람들이 만든 훌륭한 라이브러리가 있는데... 왜!!! 그걸 다 만들 필요가 있는가이다.

    재미있게도 위에서 소개한 라이브러리들은 대부분 Google 코드(http://code.google.com)에 공개되어 있다. 이는 무엇을 의미하는가? 업데이트할때마다 압축파일을 받아 라이브러리에 복사하는 수고가 필요없이 SVN으로 간단하게 업데이트 할 수 있다는 것을 말한다. 이것을 적극 활용하는 방법을 소개하는 것이 이 글을 쓰는 목적이다.

    필자는 Flash 개발자라면 누구라도(?) 잘 알고 있는 Flash 3D 엔진인 Papervision3D과 Away3D를 SVN을 이용해 가져와 프로젝트에서 활용하는 방법을 언급하고자 한다. 개발환경은 Flash Builder 4를 기준으로 하겠다. (거의 모든 기능을 Flex Builder 3에서도 할 수 있다.)


    1. Flex/Flash Builder에 SVN 플러그인 설치
    이제부터 설명할 공개 라이브러리를 사용하기 위해 Flash builder에 SVN 플러그인을 설치해야한다. 방법은 어렵지 않다. 다음 링크를 참고한다.

    Flex Builder 에서 Subversion(SVN) 사용하기


    2. Google Code에서 Papervision3D SVN 주소 확인하기
    Google 코드 사이트(http://code.google.com)로 들어가 papervision3d로 검색해서 Papervison3D 구글코드 페이지(http://code.google.com/p/papervision3d/)로 들어간다. Papervision3d의 라이센스와 각종 링크들을 보면 다른 정보들도 확인할 수 있다.

    물론 화면 우측 Featured downloads에 있는 zip 및 swc 파일을 받아 직접 프로젝트에 복사해 사용할 수 있으나(많은 블로그에 이 방법을 소개하고 있다.) 앞서 말한데로 새로 업데이트되는 최신소스를 바로바로 사용해보려면 이 방법은 너무 불편하다. 게다가 공개 라이브러리를 사용하는게 4~5개 이상 넘어가면 지속적인 업데이트가 힘들다.

    어떤 개발자는 너무 최신 버전이면 버그가 있을지도 모르지 않겠느냐? 라고 의심할 수 있을 것이다. 하지만 Papervision3D와 같이 이미 대중적(?)으로 인기를 모으고 있는 라이브러리라면 철저한 검증없이 함부로 최신화(commit) 하지 않는다.


    위 Source 탭으로 들어가 Checkout 메뉴를 보면 아래처럼 SVN checkout 주소가 있다. 이 주소는 저장소의 주소라 불리운다. 여러분은 이 주소로 Papervision3D 소스 코드를 받아올 수 있겠다.




    3. SVN을 이용해 Papervision3D 라이브러리 프로젝트 만들기
    이제 본격적으로 Papervision3d를 SVN으로 받아와 라이브러리 프로젝트를 만들어보자.

    Flash Builder를 실행한다. 실행되면 메뉴에서 File > Import 로 들어간다. 아래 처럼 창이 뜨면 SVN > Checkout Projects from SVN 을 선택한다음 Next 버튼을 누른다.



    아래 처럼 보이면 새로운 저장소 위치를 만들기 위해 Create a new repository location을 선택후 Next 버튼을 누른다. 이는 새로운 저장소라는 것은 SVN을 접속할 주소를 의미한다고 보면 되겠다. 여기서는 Google 코드에서 봤던 주소 http://papervision3d.googlecode.com/svn/trunk 가 되겠다.

    아래처럼 나오면 주소를 입력하고 Next 버튼을 누른다.



    아래 화면처럼 나오면 http://papervision3d.googlecode.com/svn/trunk 저장소 내에 있는 Papervision3D 소스 경로를 볼 수 있다. 우리는 ActionScript 3.0 기반에서 제작하기 때문에 as3/trunk를 선택한다. 이때 선택하는 기준은 그 안에 src 폴더의 상위 폴더이어야 한다. 이 방식은 다른 라이브러리를 참조해서 하더라도 저장소내에 구조가 조금씩 다르지만 같은 방법을 사용하므로 기억하자.


    위 창에서 Next 버튼을 누르면 다음 창으로 전환된다. 세부적으로 Check out 하는 방법을 설정할 수 있는데 우리는 최신 버전을 사용할 것이기 때문에 바로 Finish 버튼을 누른다.



    이제 Papervison3D 라이브러리를 가져올 SVN 주소 설정을 끝냈으므로 이 라이브러리가 담길 프로젝트를 생성해야한다. 위 절차를 모두 끝내면 자동적으로 다음과 같이 New Project 창이 뜬다. 이때 Flex Library Project를 선택하면 되겠다.


    다음 그림처럼 Project name을 Papervision3D(이름은 아무거나 써도 상관없다.)으로 설정하고 Flex SDK Version을 선택한 다음 Finish버튼을 누른다.


    참고로 Flex SDK Version을 선택시에 라이브러리가 AIR 코드가 포함되어 있는 경우라면 Include Adobe AIR libraries를 선택해야한다.(예: as3corelib) 또한 라이브러리가 구동되기 위한 SDK가 무엇인지도 알아야한다. 가령 Flash Player 10부터 3D API를 사용한 라이브러리라면 그것을 지원하는 SDK를 사용해야한다는 것을 의미한다. SDK는 Adobe Labs에서 최신버전을 받아 사용하면 된다.(방법을 모르시면 댓글을 ^^)

    아래처럼 Confirm Overwrite 메시지가 뜨면 그냥 OK 버튼을 누른다.



    이제 구글 코드 저장소로부터 아래처럼 Papervision 3D 코드를 받아온다.


    Package Explorer에 추가된 Papervion3D 라이브러리 프로젝트를 확인하자. 하지만 만약 에러메시지가 나온다면 다음과 같이 해보자. 해당 프로젝트를 선택하고 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴에서 Properties로 들어가보자. 아래와 같은 창이 나오면 왼쪽 메뉴에서 Flex Library Build Path를 누르고 우측 상단 탭에 Classes를 선택한다. 그리고 Select classes to include in the library를 선택한 다음 src 앞에 check박스를 체크한다. 그리고 OK 버튼을 누르면 다시 컴파일 되면서 컴파일 에러가 사라질 것이다.



    여러분은 이제 최신버전의 Papervision3D를 사용할 수 있게 되었다. 아래처럼 해당 프로젝트를 선택하고 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴에서 Team > Update to Head를 선택하는 것만으로 최신버전으로 업데이트 할 수 있게 된다. 이러한 방법은 SVN으로 공유한 다른 라이브러리들을 항상 최신으로 유지하는데 쉬운 방법을 제공하게 된다.


    한가지 언급하자면 위에서 소개하는 방법은 Papervision3D를 예로 들었기 때문에 다른 라이브러리의 공개 형태에 따라 수행하는 방법의 차이가 있을 수 있다는 점을 강조하고 싶다. 그러한 점은 개발자가 알아서 체크할 수 있어야 한다.


    4. 조금만 더 가다듬어 보기 (관심있는 사람만 읽으면 됨)
    3번까지 진행하는 것만으로도 개발하는데 전혀 문제없지만 아래 내용을 좀더 알고 가면 더욱 좋을 듯 싶다.

    지금까지 구글 코드로부터 SVN 저장소를 통해 받아온 Papervison3d 라이브러리는 설정파일을 제외한 ActionScript 3.0 코드가 대부분이다. 그래서 실질적으로 프로젝트에 필요한 설정구성요소인 .actionScriptProperties나 .flexLibProperties등의 파일들은 Papervision3D 개발자가 처음부터 저장소에 공유하지 않는다. 왜냐하면 .actionScriptProperties, .flexLibProperties들은 Flex Builder나 Flash Builder와 같이 특정 개발툴에서 필요한 파일이기 때문에 다른 IDE에서는 무의미 할 수 있기 때문이다.

    아래 처럼 Papervision3D의 프로젝트 이름에 SVN으로 공유되었으나 폴더그림에 * 표시가 되어 있다. 이 표시는 원래 SVN 저장소 내용과 프로젝트 내용이 다를때 나온다. 이 다른 내용이라는 것은 금방 언급했던 .actionScriptProperties, .flexLibProperties 등이다.


    Flash Builder에서는 프로젝트내의 .actionScriptProperties, .flexLibProperties 파일을 직접 볼 수 없도록 되어 있다. 이는 Filter 처리가 되어 있기 때문이다. 아래 그림처럼 선택해 Filter 처리된 파일들을 전부 check out하자.



    위 그림 처럼 Filter 처리된 파일을 check out하면 아래처럼 프로젝트 내에 .actionScriptProperties, .flexLibProperties 등을 볼 수 있게 된다. 이 파일의 아이콘에 ?로 표시된 것은 저장소에 이 파일이 존재하지 않음을 나타내는 것이다.

    * 표시나 ? 표시를 없애기 위해서 이러한 파일들이 SVN 저장소와 공유되지 않도록 무시할 수 있도록 하면 된다. Flash Builder에 설치된 SVN 플러그인은 이러한 기능을 지원해준다. 메뉴에서 window > Preference로 들어가 창이 뜨면 Team > Ignored Resources로 들어가면 아래 처럼 나온다. 여기에 다음 내용을 Add Pattern하자.

    .actionScriptProject
    .actionScriptProperties
    .flexProperties
    .flexProject
    .project
    bin
    .settings
    참고 : http://labs.adobe.com/wiki/index.php/Flex_Builder:resources:plugins:subversion:filtering




    이제 아래 그림처럼 * 표시나 ? 표시가 없어지고 저장소의 원본과 프로젝트의 소스가 일치함을 나타내는 표시로 바뀐다.




    5. Away3D를 SVN으로 받아오기

    Papervision3D 라이브러리를 이용해서 애플리케이션을 만드는 예제는 굳이 설명안해도 된다고 생각한다. 대신 Away3D를 활용하는 방법을 약간 언급하고자 한다.

    Away3D도 Papervision3D처럼 구글코드(http://code.google.com/p/away3d/)를 통해 SVN으로 소스를 공개하고 있다. 하지만 Away3D는 Papervision3D와 다르게 Flash Player 9, Flash Player 10 버전을 함께 제공하면서 Flash Player 10 3D 라이브러리를 이용한 3D 엔진인 Away3DLite도 제공한다. 또한 몇가지 유용한 예제도 함께 제공하고 있다.


    Away3D 저장소 주소(http://away3d.googlecode.com/svn/trunk)로 접속하면 아래처럼 나온다. fp9과 fp10은 각각 Flash Player 9, 10 버전을 의미한다. 아무래도 Flash Player 10 버전이 최신이면서 효율적으로 만들어졌으므로 fp10에 있는 공개 라이브러리와 예제소스를 참고할 것이다.

    fp10에 보면 Away3D, Away3DLite 소스가 있음을 확인할 수 있다. 또한 그에 대한 예제는 Examples에 들어 있다.


    아래는 해당 라이브러리와 예제의 저장소 주소이다.


    여러분은 Papervision3D를 SVN으로 프로젝트를 만든 것처럼 Away3D에서도 그래도 하면 된다. 단. Examples의 경우 중간에 프로젝트 생성시 ActionScript 프로젝트를 선택하기 바란다. 물론 Flash Player 10 기준이라는 점도 명심하자.

    예제 프로젝트에서는 해당 라이브러리 프로젝트의 경로를 포함시켜줘야한다. Away3D_Example은 Away3D 라이브러리를 Away3DLite_Examples는 Away3dLite 라이브러리를 포함해야한다. 방법은 간단하다. Away3D_Example와 Away3DLite_Examples내에 libs 폴더안에 각각의 라이브러리 프로젝트에서 만들어진 bin디렉토리에 있는 
    swc를 복사하면 된다. 하지만 이는 애플리케이션을 전부 만들고 배포하고 난 다음에 백업을 위해 선택할 방법이고 실제로 개발할때는 다음과 같이 한다.

    1. Away3D_Example 프로젝트를 선택한다.
    2. 메뉴에서 Project > Properties를 선택한다.
    3. 창이 뜨면 좌측 ActionScript Build Path(Flex의 경우 Flex Build Path)를 선택한다.
    4. 상단 Library path 탭을 선택한다.
    5. Add Project를 선택한 다음 Away3D 라이브러리를 선택한다.
    6. OK 버튼을 누른다.
    7. Away3D_Example의 .actionScriptProperties를 연다.
    아래 그림처럼  default package에 있는 소스 들은 전부 애플리케이션들이다. 이 코드들이 진정한 애플리케이션으로서 동작하려면 .actionScriptProperties내에 application으로 등록해야한다. 수작업이긴 해도 전부 등록하자. 또는 해당 as 파일을 선택후 마우스 오른쪽 버튼을 눌러 Set as Default Application 항목을 선택해도 된다. 이는 애플리케이션으로 등록하는 동시에 Default 애플리케이션으로 잡아준다. Default 애플리케이션을 지칭하는 것은 .actionScriptProperties 파일 내에 actionScriptProperties 태그의 mainApplicationPath 속성이 그 역할을 한다.



    위 처럼 설정하면 이제 예제 파일들을 아무 문제없이 동작시킬 수 있을 것이다.
    8. Away3DLite_Example도 위와 비슷한 방법으로 하면 되겠다.

    아래는 Away3DLite_Examples에 있는 Advanced_Mario1000.as 애플리케이션을 실행하는 화면이다.

    아마 어떤 애플리케이션은 as3dmod 라이브러리가 필요하다는 에러가 나올것이다. 이것도 비슷한 방법으로 Google Code에서 가져올 수 있다. 단, 몇가지 다른 설정을 해야하는데 여기서 설명하지 않고 각자의 몫으로 돌리겠다.


    정리하며

    개발자라면 Google Code와 친해져야하지 않을까? 또한 기존 라이브러리가 있는가 검색을 해보는 성의가 있어야하지 않을까? 그리고 그 라이브러리를 잘 활용하는 방법을 스스로 깨달으려고 노력해야하지 않을까?

    잘못된 사항이나 더 좋은 정보가 있다면 얼마든지 댓글이나 트랙백 부탁한다.


    참고
    bs-papervision : http://code.google.com/p/bs-papervision/ 
    as3dmod : http://code.google.com/p/as3dmod/
    sandy3d : http://www.flashsandy.org 
    alternativa3D : http://alternativaplatform.com/en/alternativa3d/

    글쓴이 : 지돌스타(http://blog.jidolstar.com/639)





    예전에 wonderfl.net에 대해서 소개한바 있다. 여기서 소개하는 Beautifl.netWonderfl.net에서 올라온 엄선된 아름다운(?) ActionScript/Flex 컨텐츠를 목록화 해서 쉽게 볼 수 있도록 해주는 서비스이다.

    Beautifl.net은 적절히 분류(3D, Effect, Physics, Game, Sound등)가 되어 있어 관심있는 영역의 Flash 컨텐츠도 감상할 수 있겠다. PV, Pick, New 순으로 정렬도 되기 때문에 이 또한 유용하다. 또한 좋은 컨텐츠가 있다면 누구든지 추천할 수 있으며 RSS feed로도 구독할 수 있다. 

    Flash로 이런 것도 만들 수 있구나를 넘어 실제 코드도 볼 수 있기 때문에 Flash 개발자들의 스킬업을 위해 이보다 좋은 것도 없을 것 같다.

    Wonderfl.netBeautifl.net을 통해 멋진 Flash 세계를 감상해보자.

    참고로 이 둘 사이트는 모두 일본에서 만들어졌다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/634)


    Flash 엔터프라이즈 개발 업체를 제외하고는 플래시 개발자가 2~3명인 업체가 대부분입니다. 게다가 Flex/AIR등에 처음 접하는 경우도 다분하죠. 결국 Flash 프로젝트의 실무를 어찌 진행할 것인가 고민이 되는 것이 현실입니다. 그래서 도움이 될만한 글들을 모아봤습니다.

    http://opencast.naver.com/FL188/15

    이 캐스트는 베스트 No.로 12월 10일에 추천되었습니다. ^^
    http://opencast.naver.com/home/bestNo.nhn?exposureDate=20091210 

    글쓴이 : 지돌스타(http://blog.jidolstar.com/633)

    어떤 분이 Flex 3.4환경에서 MenuBar에 색을 입히려하다가 잘 안되서 본인의 블로그에 질문을 해주셨다.


     <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    	<mx:Style>
    		MenuBar { 
    			font-weight:bold; 
    			color:#FFFFFF;
    			fill-colors:#0D0A92, #1B16F4, #37920A, #59EB12;
    			fill-alphas:1.0,1.0;
    		}
    		MenuBarItem {
    			theme-color:#0000FF;
    		}
    		Menu { 
    			alternatingItemColors:#ffffff,#eff4fa;
    			color:#000000;
    			top:4px;
    			bottom:2px;
    			horizontalGap:2px;	
    			roll-over-color:#00ff00;
    		}
    	</mx:Style>
    	<mx:MenuBar id="myMenuBar" labelField="@label" >
    		<mx:XMLList>
    			<menuitem label="MenuItem A">
    				<menuitem label="SubMenuItem A-1" enabled="false"/>
    				<menuitem label="SubMenuItem A-2"/>
    			</menuitem>
    			<menuitem label="MenuItem B"/>
    			<menuitem label="MenuItem C"/>
    			<menuitem label="MenuItem D">
    				<menuitem label="SubMenuItem D-1" 
    						  type="radio" groupName="one"/>
    				<menuitem label="SubMenuItem D-2" 
    						  type="radio" groupName="one"
    						  selected="true"/>
    				<menuitem label="SubMenuItem D-3" 
    						  type="radio" groupName="one"/>
    			</menuitem>
    		</mx:XMLList>
    	</mx:MenuBar>
    </mx:Application>
    



    질문은 MenuBarItem의 themeColor를 #0000FF로 지정했는데 저렇게 나오냐는 것이였다. 참고로 MenuBar는 저 파란색의 바를 말하는 것이고 MenuBarItem은 MenuItem A, MenuItem B...등으로 표시된 것이다. 이들은 모두 UIComponent를 상속했다. 마지막으로 Menu는 서버메뉴로써 List를 확장한 것이다.

    질문처럼 직관적으로 봐도 문제가 있어 보인다. 이렇게 나오는 이유를 알기 위해서는 스킨부분을 찾아봐야 한다.

    (아래내용이 약간 복잡할 수 있으나 천천히 따라오면 그리 복잡한 것도 아니다. 원래 이런걸 말로 설명해야 빠른 건데 글로 설명하려 하니 어렵다. ^^;)


    원인 찾기

    MenuBar의 아이템렌더러는 menuBarItemRenderer속성에 지정된다. 기본적으로 MenuBarItem 클래스가 된다. 쉽게 이야기해 위 그림에서 MenuItem A, MenuItem B... 등은 다 MenuBarItem으로 생성되어 그려진 것이다. 만약 MenuBarItem을 다른 것으로 대체하고 싶다면 IMenuBarItemRenderer 인터페이스를 구현해서 다른 MenuBarItem을 만들면 되겠다. 아래는 MenuBar의 menuBarItemRenderer 속성을 설명하고 있다.

    (문제의 원인을 파악하기 위해 가장 먼저 찾아야 할 것은 API 문서이다.)



    MenuBarItem의 스킨은 무엇일까? 아래 MenuBar에서 Style부분을 보자. MenubarItem의 스킨은 바로 ActivatorSkin 클래스라는 것을 알 수 있다. 결국 스킨도 내 마음대로 바꿀 수 있는 것이다.

    Flex 3.4에서 스킨은 크게 그래픽기반 스킨과 프로그램적 스킨(Programmatic Skin)이 있다. 그래픽기반 스킨은 이미지를 생각하면 된다. 하지만 프로그램적 스킨은 ActionScript 3.0 코드이다. MenuBar의 itemSkin 스타일 속성으로 mx.skins.halo.ActivatorSkin을 썼다는 것은 바로 프로그램적 스킨을 사용한다는 이야기이다.

    (문서를 봐도 해결의 실마리가 안보인다면 SDK를 뜯어보자!)

    그럼 이들이 어떻게 동작하는 것일까? MenuBar부터 시작해 MenuBarItem, ActivatorSkin으로 자연스럽게 찾아보면 된다. Flex 3.4 SDK 소스는 공개되어 있으므로 아래 그림처럼 Flash builder 4에서 쉽게 찾아볼 수 있다. Flex Builder 3를 사용한다면 이 기능은 사용할 수 없다.

    Flex SDK 클래스를 보려면 framework.swc를 펼쳐보면 된다.



    MenuBar 소스에서 mouseOverHandler, mouseUpHandler, mouseDownHandler 메소드들을 보자. 이들 메소드는 마우스 이벤트를 처리하는데 잘 보면 이런 코드들이 있다.

    item.menuBarItemState = "itemDownSkin";
    


    여기서 item은 IMenuBarItemRenderer 인터페이스의 참조이다. 그리고 MenuBarItem 클래스는 이 인터페이스를 구현한 것이다. 결국 MenuBarItem의 menuBarItemState 속성에 itemDownSkin이라고 지정한 것이다.

    그럼 MenuBarItem 소스의 menuBarItemState 부분을 보자.


        public function set menuBarItemState(value:String):void
        {
            _menuBarItemState = value;
            viewSkin(_menuBarItemState);
        }   
    
        private function viewSkin(state:String):void
        {  	
        	var newSkinClass:Class = Class(getStyle(state));
        	var newSkin:IFlexDisplayObject;
        	var stateName:String = "";
        	    	
        	if (!newSkinClass)
        	{
        		// Try the default skin
        		newSkinClass = Class(getStyle(skinName)); 		
        		    	
        		if (state == "itemDownSkin")
        			stateName = "down";
        		else if (state == "itemOverSkin")
        			stateName = "over";
        		else if (state == "itemUpSkin")
        			stateName = "up";
        		
        		// If we are using the default skin, then 
        		if (defaultSkinUsesStates)
        			state = skinName;
        		
         		if (!checkedDefaultSkin && newSkinClass)
        		{
    	    		newSkin = IFlexDisplayObject(new newSkinClass());
    	    		// Check if the skin class is a state client or a programmatic skin
    	    		if (!(newSkin is IProgrammaticSkin) && newSkin is IStateClient)
    	    		{
    	    			defaultSkinUsesStates = true;
    	    			state = skinName;
    	    		}
    	    		
    	    		if (newSkin)
    	    		{
    		    		checkedDefaultSkin = true;
    		    	}
        		}
        	}
        	
          	newSkin = IFlexDisplayObject(getChildByName(state));
    
            if (!newSkin)
            {
                if (newSkinClass)
                {
                    newSkin = new newSkinClass();
    
                    DisplayObject(newSkin).name = state;
    
                    if (newSkin is ISimpleStyleClient)
                        ISimpleStyleClient(newSkin).styleName = this;
    
                    addChildAt(DisplayObject(newSkin), 0);
                }
            }
    
            newSkin.setActualSize(unscaledWidth, unscaledHeight);
    
            if (currentSkin)
                currentSkin.visible = false;
    
            if (newSkin)
                newSkin.visible = true;
    
            currentSkin = newSkin;
            
            // Update the state of the skin if it accepts states and it implements the IStateClient interface.
    		if (defaultSkinUsesStates && currentSkin is IStateClient)
    		{
    			IStateClient(currentSkin).currentState = stateName;
    		}
        }
    

    MenuBarItem의 menuBarItemState에 상태변화 값을 넣어주면 MenuBarItem의 viewSkin() 메소드에서 각 상태값에 따라서 스킨을 만들어준다. viewSkin 메소드에서 newSkin = IFlexDisplayObject(new newSkinClass()); 은 새로운 스킨을 만드는 부분이 newSkinClass가 바로 ActivatorSkin 클래스가 된다. 그리고 newSkin = IFlexDisplayObject(getChildByName(state));  부분은 이전에 만든 스킨이 있는지 확인하는 부분이다. viewSkin() 메소드는 그래픽적 스킨, 프로그램적 스킨등에 모두 대응되며 게으른(lazy) 스킨 생성을 한다. 여기서 게으른 생성이라는 것은 가령, 애플리케이션이 실행 당시에 마우스 Over스킨을 생성하는 것이 아니라 마우스Over시에 스킨은 바로 그때 생성해준다는 것이다. viewSkin() 메소드가 결국 ActivatorSkin 클래스로 하여금 스킨을 만들어주는 것이다.

    ActivatorSkin 클래스의 일부분을 보자.

    	private static function calcDerivedStyles(themeColor:uint,  fillColor0:uint, fillColor1:uint):Object
    	{
    		var key:String = HaloColors.getCacheKey(themeColor,fillColor0, fillColor1);
    				
    		if (!cache[key])
    		{
    			var o:Object = cache[key] = {};
    			
    			// Cross-component styles.
    			HaloColors.addHaloColors(o, themeColor, fillColor0, fillColor1);
    		}
    		
    		return cache[key];
    	}
    
    	/**
    	 *  @private
    	 */
    	override protected function updateDisplayList(w:Number, h:Number):void
    	{
    		super.updateDisplayList(w, h);
    
    		if (!getStyle("translucent"))
    			drawHaloRect(w, h);
    		else
    			drawTranslucentHaloRect(w, h);
    	}
    
    	/**
    	 *  @private
    	 */
    	private function drawHaloRect(w:Number, h:Number):void
    	{
    		var fillAlphas:Array = getStyle("fillAlphas");
    		var fillColors:Array = getStyle("fillColors");
    		var highlightAlphas:Array = getStyle("highlightAlphas");				
    		var themeColor:uint = getStyle("themeColor");
    
    		var themeColorDrk1:Number =
    			ColorUtil.adjustBrightness2(themeColor, -25);
    
    		// Derivative styles.
    		var derStyles:Object = calcDerivedStyles(themeColor, fillColors[0], fillColors[1]);
    												 
    		graphics.clear();
    
    		switch (name)
    		{
    			case "itemUpSkin": // up/disabled
    			{
    				// invisible hit area
    				drawRoundRect(
    					x, y, w, h, 0,
    					0xFFFFFF, 0);
    				break;
    			}
    
    			case "itemDownSkin":
    			{
    				// face
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2, 0,
    					[ derStyles.fillColorPress1, derStyles.fillColorPress2], 1,
    					verticalGradientMatrix(0, 0, w, h )); 
    									
    				// highlight
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2 / 2, 0,
    					[ 0xFFFFFF, 0xFFFFFF ], highlightAlphas,
    					verticalGradientMatrix(0, 0, w - 2, h - 2));
    
    				break;
    			}
    
    			case "itemOverSkin":
    			{
    				var overFillColors:Array;
    				if (fillColors.length > 2)
    					overFillColors = [ fillColors[2], fillColors[3] ];
    				else
    					overFillColors = [ fillColors[0], fillColors[1] ];
    
    				var overFillAlphas:Array;
    				if (fillAlphas.length > 2)
    					overFillAlphas = [ fillAlphas[2], fillAlphas[3] ];
      				else
    					overFillAlphas = [ fillAlphas[0], fillAlphas[1] ];
    
    				// face
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2, 0,
    					overFillColors, overFillAlphas,
    					verticalGradientMatrix(0, 0, w, h )); 
    
    				// highlight
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2 / 2, 0,
    					[ 0xFFFFFF, 0xFFFFFF ], highlightAlphas,
    					verticalGradientMatrix(0, 0, w - 2, h - 2));
    				
    				break;
    			}
    		}
    
    		filters = [ new BlurFilter(2, 0) ];
    	}
    


    그림을 그릴때 drawHaloRect() 메소드를 호출해서 각 상태값에 따라서 그림을 그려준다. 우리가 처음 themeColor를 지정했음에도 불구하고 다른 색으로 보였던 부분을 알기 위해서는 drawHaloRect()에 switch분기점에서 "itemDownSkin"을 보면 된다. derStyles.fillColorPress1, derStyles.fillColorPress2 색을 사용하는 것을 볼 수 있으며 코드에서 derStyles는 calcDerivedStyles(themeColor, fillColors[0], fillColors[1]);에 의해 만들어진다. calcDerivedStyles() 메소드를 보면 결국 HaloColors.addHaloColors(o, themeColor, fillColor0, fillColor1); 으로 색이 결정됨을 볼 수있다. 여기서 HaloColors 클래스내에서 addHaloColors() 정적 메소드를 보면 다음과 같다.

    public static function addHaloColors(colors:Object,  themeColor:uint,  fillColor0:uint, fillColor1:uint):void
    {
    	var key:String = getCacheKey(themeColor, fillColor0, fillColor1); 
    	var o:Object = cache[key];
    	
    	if (!o)
    	{
    		o = cache[key] = {};
    		
    		// Cross-component styles
    		o.themeColLgt = ColorUtil.adjustBrightness(themeColor, 100);
    		o.themeColDrk1 = ColorUtil.adjustBrightness(themeColor, -75);
    		o.themeColDrk2 = ColorUtil.adjustBrightness(themeColor, -25);
    		o.fillColorBright1 = ColorUtil.adjustBrightness2(fillColor0, 15);
    		o.fillColorBright2 = ColorUtil.adjustBrightness2(fillColor1, 15);
    		o.fillColorPress1 = ColorUtil.adjustBrightness2(themeColor, 85);
    		o.fillColorPress2 = ColorUtil.adjustBrightness2(themeColor, 60);
    		o.bevelHighlight1 = ColorUtil.adjustBrightness2(fillColor0, 40);
    		o.bevelHighlight2 = ColorUtil.adjustBrightness2(fillColor1, 40);
    	}
    	
    	colors.themeColLgt = o.themeColLgt;
    	colors.themeColDrk1 = o.themeColDrk1;
    	colors.themeColDrk2 = o.themeColDrk2;
    	colors.fillColorBright1 = o.fillColorBright1;
    	colors.fillColorBright2 = o.fillColorBright2;
    	colors.fillColorPress1 = o.fillColorPress1;
    	colors.fillColorPress2 = o.fillColorPress2;
    	colors.bevelHighlight1 = o.bevelHighlight1;
    	colors.bevelHighlight2 = o.bevelHighlight2;
    }
    

    위에서 우리가 주목할 점은 o.fillColorPress1와 o.fillColorPress2를 설정할 때 ColorUtil.adjustBrightness2(themeColor, 85);와 같은 방법으로 새로 색을 설정하는 부분이다.

    결국 무엇인가? 지금까지 MenuBar, MenuBarItem, ActivatorSkin 클래스를 쭉 따라가며 themaColor 스타일값이 원하는대로 보여지지 않은 것은 바로 HaloColors 클래스의 addHaloColors() 부분에서 찾을 수 있는 것이다.


    해결하기

    위처럼 themeColor가 원하는대로 적용되지 않은 것 처럼 보인 이유를 분명히 알았다. Flex 3.4가 매우 잘 만들어졌다고 할지라도 이것도 결국 사람이 만든거라 완벽히 원하는대로 기능을 쓸 수 없는 경우가 생긴다. 그러면서 발생하는 문제를 해결하기 위해 단지 피상적으로 문서만 의지해서 해결하려고 하면 절대 답을 못찾을 수 있다. 표면상 문제를 해결할 수 없다면 문제의 부분을 찾기위해 SDK 소스를 위와 같이 찾아보는 스킬을 길러야 한다.
     
    어떤 문제인지 파악했으니 이제 해결해보자. 일단 기본적으로 CSS로 themeColor 와 같은 설정값으로 해결못하는 것을 알았으니 MenuBarItem에 새로운 ActivatorSkin 와 비슷한 MyActivatorSkin 을 만들고 더불어 HaloColors도 동일하게 MyHaloColors를 만들자. 이들은 기존 ActivatorSkin.as와 HaloColors.as 소스를 복사해서 만들면 된다. 물론 package부분 설정 및 include부분 설정으로 인한 에러는 제거해주자.

    MyHaloColors 클래스내에 o.fillColorPress1, o.fillColorPress2에서 ColorUtil.adjustBrightness2(themeColor, 85); 처럼 된 것을 ColorUtil.adjustBrightness2(themeColor, 0);으로 바꾸자.

    그런 다음 메인소스에 CSS에서 MenuBar 셀렉터에 itemSkin을 다음처럼 추가한다.

    MenuBar { 
    	font-weight:bold; 
    	color:#FFFFFF;
    	fill-colors:#0D0A92, #1B16F4, #37920A, #59EB12;
    	fill-alphas:1.0,1.0;
    	itemSkin: ClassReference("skins.MyActivatorSkin");
    }
    


    문제가 해결되었다.


    결론
    나는 개인적으로 Flex 프레임워크가 완벽하다고 보지 않는다. 그래도 전체적으로 잘 구조가 잘 잡혀있는 만큼 구조를 이해하고 잘 따라가보면 표면상으로만 해결하지 못한 부분을 해결할 수 있다. 너무 책과 문서에만 의존하지 말고 개발자라면 Flex 소스도 조금씩 분석해보길 바란다.

    참고로 몇가지 덧붙여 말하면...
    사실 본인은 Flex 3를 손놓은지 꽤 되었다. Flex 4도 지금 입문단계정도이다. 기본적으로 실무에서 Flex 프레임워크로 개발한다기 보다 애플리케이션의 퍼포먼스나 개발적 스타일의 문제로 ActionScript 3.0기반으로 개발한다. Flex 는 너무 유연하게 만들려고 노력해서 그런지 전체적으로 무겁다는 것을 지울 수 없어 간단히 또 빠르고 사용자에게 불편함이 없는 그런 개발이 필요할때는 Flex를 이용하지만 속도,메모리가 중요한 개발은 무조건 ActionScript 3.0으로 개발한다.

    Flex는 ActionScript 3.0으로 만들어진 프레임워크 일종이며 mxml이라는 것은 이 Flex 프레임워크를 손쉽게 쓰기 위해 도입된 xml이다. 이를 파싱해서 ActionScript 3.0으로 해석해 다시 swf로 만드는 컴파일러가 compc, mxmlc인 것이다. Flex에 대해서 이러한 점을 알고 공부하고 개발하신다면 여러가지로 도움이 될 것이다. 


    참고글

    [Flex] 자식 컴포넌트에 CSS를 적용시키는 통상적인 방법 소개
    [Flex] 스킨(Skin)을 이용하여 확장가능한 게이지 컴포넌트 제작


    글쓴이 : 지돌스타(http://blog.jidolstar.com/627)
    처음 Flash Builder 4 beta 2를 설치하면 설치된 디렉토리에 sdks 디렉토리가 있고 이 안에 3.4.1, 4.0.0 의 두개의 디렉토리가 있다. 이 디렉토리는 SDK 두개의 버전을 말한다. 이는 윈도우(MS Windows)던 맥(Mac)이든 동일하다. 윈도우는 C:/Program Files/Adobe/Flash Builder 4 beta 2/sdks가 되겠고 맥이라면 /Application/Adobe Flash Builder 4 beta 2/sdks가 될 것이다.

    하지만 AIR 1.5개발을 위한 sdk들이며 새로운 AIR 2.0 Beta를 테스트해보기 위해서는 sdks를 변경해야한다. 윈도우에서는 Adobe Labs에서 AIR 2.0 Beta SDK를 다운로드 받아 해당 sdks안에 3.4.1, 4.0.0 디렉토리에 각각 복사하고 AIR 2.0 Runtime을 설치하는 것만으로 개발환경이 구축된다. (이미 윈도우환경에서 AIR 2.0 Beta를 Flash Builder 4에 적용하는 방법을 소개했었다. : http://blog.jidolstar.com/619)

    하지만 맥에서의 복사는  윈도우 운영체제에서 복사와 다른 개념을 가진다. 윈도우에서는 같은 디렉토리 이름의 경우 이전의 내용과 덮어쓰려는 내용을 합쳐버린다. 하지만 맥은 같은 이름의 디렉토리는 제거하고 복사할 디렉토리로 바꾼다. 이를 대치한다고 한다. 그래서 맥 사용자는 AIR 2.0 개발환경을 구축하기 위해 조금 복잡한 과정을 거쳐야한다.

    약간 복잡하더라도 리눅스를 조금만 다뤄봤다면 쉽게 이 문제를 해결할 수 있다.

    • 파인더에서 Application(응용 프로그램) > 유틸리티 에서 터미널을 실행한다. 
    • 만약 이전에 root 계정을 만든 적이 있다면 바로 su를 입력해 root권한으로 접근한다.
      아니면 sudo passwd root 를 입력해서 현재 계정의 암호를 입력하고 root암호를 설정후 성공적으로 입력하면 su로 접속해 방금 입력한 root암호로 접속한다. 참고로 root로 접속하면 명령 프롬프트가 $에서 #로 바뀐다.
    • 다운로드 받은 AIR 2.0 SDKs를 기존 sdks에 복사하기 위해 cp -rf orig_dir/* dest_dir를 이용한다.
      만약 AIR 2.0 SDKs를 자신의 홈에 복사해 두었다면 다음과 같이 터미널 창에 입력하여 강제 복사한다.
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/3.4.1/
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/4.0.0/

    윈도우와 맥의 디렉토리 복사에 대한 개념이 달라 본인도 몇시간 고민한 결과 찾아낸 방법이다. 좋은 정보가 되었으면 한다.


    추가사항 
    기존에 AIR 1.5 기반의 SDKs를 그대로 두면서 AIR 2.0도 필요할 때 개발하고 싶은 사람도 있을 것이다. 
    그런 경우에는 위의 설명에서 약간만 다르게 하면 되겠다. 위에서 설명한 2번째 내용에서 root로 접속하는 것까지는 같다. 

    1. Flash builder 4의 sdks 디렉토리에 있는 3.4.1과 4.0.0의 복사본을 만든다. 
      mkdir 3.4.1_AIR2.0
      mkdir 4.0.0_AIR2.0
      cp -R 3.4.1/* 3.4.1_AIR2.0
      cp -R 4.0.0/* 4.0.0_AIR2.0
    2. AIR 2.0 SDKs를 3.4.1_AIR2.0과 4.0.0_AIR2.0 폴더에 복사한다.
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/3.4.1_AIR2.0/
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/4.0.0_AIR2.0/
    3. Flash Builder 를 실행한다.
    4. 메뉴에서 Eclipse > Preference에 들어간다. (참고로 Window였다면 Window > Preference이다.)
    5. 창이 뜨면 왼쪽 메뉴에서 Flash builder > Installed Flex SDKs를 선택한다.
    6. 우측에 Add버튼을 눌러 위에서 새로 만든 AIR 2.0을 위한 SDKs들을 선택한다.  
      Flex SDK Name은 각각 Flex 3.4 AIR 2.0, Flex 4.0 AIR 2.0 등으로 이름을 바꿔도 된다.
      본인의 경우 아래 경로가 되겠다.
      /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/3.4.1_AIR2.0/
      /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/4.0.0_AIR2.0/
      본인은 아래처럼 AIR 2.0기반의 Flex 4.0 SDK를 디폴트로 잡았다.

    7. 이제 모든 개발환경이 완료되었다. 자신의 프로젝트 생성시에 원하는 SDK를 선택하면 되겠다. 
    8. 만약 프로젝트 중간에 AIR 1.5기반으로 만들다가 AIR 2.0기반으로 바꾸려면 해당프로젝트를 선택후 마우스 우클릭으로 Properties를 선택한다. 창이 뜨면 좌측 메뉴에서 Flex Compiler를 선택후 Flex SDK Version을 해당 SDK로 바꾸면 되겠다.



    Adobe AIR로 만든 아주아주 간단한 웹브라우져이다. Adobe AIR는 Webkit 엔진을 내장한 HTMLLoader라는 클래스가 있다. Webkit엔진은 사파리, 구글 크롬등에 사용하는 엔진이다. Flex 4에서는 이것을 HTML로 한번 랩핑해서 사용하고 있다. Adobe AIR에서 이 엔진을 사용하면 로드되는 페이지의 DOM에 직접 접근이 가능하며 DOM 이벤트 발생시 ActionScript 3.0 함수를 호출하게끔 하는 형태로도 변경이 가능해진다. 생각보다 활용도가 무궁무진하다. 게다가 AIR 2.0부터는 한글입력 문제가 해결되었다 . 또한 HTML5/CSS3를 지원하기 시작했다.

    아래 코드는 AIR 2.0, Flash Builder 4 Beta 2 환경에서 만들었다. 이 환경을 구축하는 방법은 다음과 같이 한다.

  • 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받는다.
  • AIR 2.0 SDK와 Runtime을 다운로드 받는다.
  • 다운받은 Runtime을 실행해 설치한다.
  • (MS Windows의 경우)SDK는 압축을 풀고 그안에 있는 내용을 Flash builder가 설치된 sdks/4.0.0과 sdks/3.4.1 폴더에 각각 덮어씌운다. 본인의 경우는 C:\Program Files\Adobe\Adobe Flash Builder Plug-in Beta 2\sdks\4.0.0
  • Flash Builder를 실행한다.
  • 메뉴에서 File > New > Flex Project를 선택한다.
  • 프로젝트 이름을 적고 Application Type은 AIR를 선택한다. Finish를 한다.
  • 다음 아래 코드를 메인 코드에 복사해서 덮어씌운다.




  • <?xml version="1.0" encoding="utf-8"?>
    <!--
    	간단한 Adobe AIR 웹브라우져
    	제작 : 지용호 
    -->
    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
    					   xmlns:s="library://ns.adobe.com/flex/spark" 
    					   xmlns:mx="library://ns.adobe.com/flex/halo"
    					   backgroundFrameRate="0.01"
    					   windowComplete="windowedapplication1_windowCompleteHandler(event)">
    	<fx:Script>
    		<![CDATA[
    			import flash.events.Event;
    			import flash.net.URLRequest;
    			import flash.net.navigateToURL;
    			
    			import mx.events.AIREvent;
    
    			private function windowedapplication1_windowCompleteHandler(event:AIREvent):void {
    				nativeWindow.x = 0;
    				nativeWindow.y = 0;
    				width = 1280;
    				height = 900;
    				//browser.htmlLoader.navigateInSystemBrowser = true;
    			}
    			private function linkClickHandler(o:Object):void {
    				navigateToURL( new URLRequest(o.currentTarget.href), "blank" );
    			}
    			private function browser_locationChangeHandler(event:Event):void {
    				trace( "browser_locationChangeHandler" );
    				tiAddress.text = browser.location;
    				btnBack.enabled = browser.historyPosition==0?false:true;
    				btnNext.enabled= (browser.historyPosition < browser.historyLength-1)?true:false;
    			}
    			private function onGoHandler(event:Event):void {
    				browser.location = tiAddress.text;
    			}
    			private function browser_completeHandler(event:Event):void
    			{
    				trace( "browser_completeHandler" );
    				tiAddress.text = browser.location;
    				var dom:Object = HTML(event.currentTarget).domWindow.document;
    				var links:Object = dom.getElementsByTagName("a");
    				for( var i:Number = 0; i < links.length; i++ ) {
    					if( links[i].target.toLowerCase() == "_blank" || links[i].target.toLowerCase() == "_new" ) {
    						links[i].onclick = linkClickHandler;
    					}
    				}
    			}
    		]]>
    	</fx:Script>
    	<s:VGroup width="100%" height="100%">
    		<s:HGroup verticalAlign="middle" paddingLeft="10" paddingRight="10" paddingTop="5" paddingBottom="5">
    			<s:Button id="btnBack" label="뒤로" click="browser.historyBack()"/>
    			<s:Button id="btnNext" label="앞으로" click="browser.historyForward()"/>
    			<s:Button label="새로고침" click="browser.reload()"/>
    			<s:Label text="주소 : "/>
    			<s:TextInput id="tiAddress" width="400" enter="onGoHandler(event)"/>
    			<s:Button label="가기" click="onGoHandler(event)"/>
    		</s:HGroup>
    		<s:HGroup verticalAlign="middle" paddingLeft="10" paddingRight="10" paddingTop="5" paddingBottom="5">
    			<s:Button label="지돌스타 블로그" click="browser.location='http://blog.jidolstar.com'"/>
    			<s:Button label="위콘 블로그" click="browser.location='http://weconize.com'"/>
    			<s:Button label="천문노트" click="browser.location='http://astronote.org'"/>
    			<s:Button label="Adobe RIA" click="browser.location='http://adoberia.co.kr'"/>
    			<s:Button label="Adobe Labs" click="browser.location='http://labs.adobe.com'"/>
    			<s:Button label="Adobe Development Center" click="browser.location='http://www.adobe.com/devnet/'"/>
    		</s:HGroup>
    		<!-- http://labs.adobe.com/wiki/index.php/Apollo:Articles:Using_HTML_in_Flex-based_Apollo_Applications -->
    		<mx:HTML id="browser" width="100%" height="100%" 
    				 location="http://blog.jidolstar.com"
    				 complete="browser_completeHandler(event)"
    				 locationChange="browser_locationChangeHandler(event)"/>
    	</s:VGroup>
    </s:WindowedApplication>
    
    

    Ajax, ActionScript 3.0, Flex, Flash등을 이용하면 AIR 애플리케이션을 만들 수 있다. Flash를 몰라도 Ajax를 알면 만들 수 있기 때문에 웹개발자들이 데스크탑 영역으로의 개발이 가능해졌다. 실제로 Ajax로 만들어진 애플리케이션도 꽤 된다. Adobe AIR 세계에 많은 개발자가 동참하길 바란다.


    글쓴이 : 지돌스타( http://blog.jidolstar.com/621 )

    + Recent posts