scaleform.minarto.com

UIComponent 1 본문

CLIK

UIComponent 1

미나토 2011. 5. 16. 19:47


Scaleform 에 대한 첫번째 글이 되는군요...

예, 제가 올해 5월부로 스케일폼 쪽 일을 시작하게 되었습니다. 그러다보니 r&d 겸 해서 글을 적기 시작하겠습니다.


 스케일폼은 as2 기반입니다.

그러다 보니 일반적인 as3 기반 환경과는 매우 다른 방식으로 개발을 진행하게 됩니다.

as2 와 as3 의 가장 큰 차이가 이벤트 방식입니다.


as2 는 함수를 부르는 콜백 방식을 기본으로 합니다. 물론 addListener 가 있긴 합니다. 하지만, 늘 scope 문제가 생겨서 실제 개발자분들이 작업하실 때는 위임문제를 해결해주는 Delegate 클래스를 사용하곤 합니다. (뭐, 안 쓰시는 분들도 있겠죠 ㅎㅎㅎ)

뭐, 예를 들면 다음과 같습니다..

import barunson.core.IDefault;
import barunson.display.Loader;
import barunson.display.MultiLoaderItem;
import barunson.events.Event;
import gfx.utils.Delegate;


/**
 * ...
 * @author minarto
 */
class barunson.display.Multiloader implements IDefault
{
/**
* __loader
* 로더
*/
private var __loader:Loader;
/**
* __count
* 로드된 갯수
*/
private var __count:Number = 0;
/**
* spreadLoad
* 로드될 때마다 보여줄지 여부. false 면 모두 완료된 후에 visible = true됨
*/
public var spreadLoad:Boolean = false;
/**
*/
private var __items:Array;
/**
* __loader
* 생성자
*/
public function Multiloader() 
{
init();
}
/**
* init
* 초기화
*/
public function init():Void
{
__count = 0;
__loader = new Loader();
__loader.onLoadComplete = Delegate.create(this, itemLoadComplete);
}
/**
* destroy
* 객체 삭제
*/
public function destroy():Void
{
__count = 0;
delete __loader;
delete __items;
delete this;
}
/**
* on__loaderror
* 로드 에러
* @param $content 로드될 MovieClip
* @param $errorCode 에러정보
* @param $httpStatus 서버에서 반환하는 http 상태코드
*/
private function onLoaderror ( $content:MovieClip, $errorCode:String, $httpStatus:Number ):Void
{
}
/**
* onLoadInit
* 로드 완료 (첫번째 프레임)
* @param $content 로드될 MovieClip
*/
private function onLoadInit ( $content:MovieClip ):Void
{
}
/**
* onLoadComplete
* 로드 완료
* @param $content 로드될 MovieClip
* @param $httpStatus 서버에서 반환하는 http 상태코드
*/
private function itemLoadComplete($content:MovieClip, $httpStatus:Number ):Void
{
var item:MultiLoaderItem = __items[__count];
var initObj:Object = item.initObj;
$content._x = initObj.x;
$content._y = initObj.y;
if (isNaN(initObj._alpha)) $content._alpha = initObj._alpha;
if (isNaN(initObj._width)) $content._width = initObj._width;
if (isNaN(initObj._height)) $content._height = initObj._height;
if (isNaN(initObj._xscale)) $content._xscale = initObj._xscale;
if (isNaN(initObj._yscale)) $content._yscale = initObj.height._yscale;
if (!spreadLoad) 
{
$content._visible = false;
}
++ __count;
item = __items[__count];
if (item)
{
__loader.load(item.src, item.content);
}
else
{
if (!spreadLoad) 
{
var i:Number = __items.length;
while (-- i > - 1)
{
item = __items[i];
item.content._visible = true;
}
}
onLoadComplete();
}
}
/**
* onLoadComplete
* 로드 완료
* @param $content 로드될 MovieClip
* @param $httpStatus 서버에서 반환하는 http 상태코드
*/
public function onLoadComplete():Void
{
}
/**
* onLoadProgress
* 로드된 양
* @param $content 로드될 MovieClip
* @param $bytesLoaded 로드된 양
* @param $bytesTotal 로드될 양
*/
private function onLoadProgress($content:MovieClip, $bytesLoaded:Number, $bytesTotal:Number):Void
{
}
/**
* 로드
*/
public function load($items:Array):Void
{
__count = 0;
__items = $items;
var item:MultiLoaderItem = __items[0];
if (item)
{
__loader.load(item.src, item.content);
}
else
{
onLoadComplete();
}
}
}


위의 코드는 제가 as2용 멀티로더를 구현하기 위해 만든 클래스 일부분입니다. (샘플을 만들어 붙이려다 만들기 귀찮아서 그냥 통째로... ㅡㅡ;;;)

init() 함수에서 

__loader.onLoadComplete = Delegate.create(this, itemLoadComplete);

와 같이 Delegate.create(this 를 해주지 않았다면 itemLoadComplete 함수에서는 __count 를 불러올 수 없습니다. itemLoadComplete 함수에서는 new Multiloader() 를 통해 생겨난 객체인 this 를 참조하지 않기 때문입니다.

Delegate.create(this 는 위에 말한대로 참조해야할 위치를 알려준 것이죠. this.__count 를 읽으란 얘기입니다.

사실  Delegate.create 함수는 코드만 보면 별것이 아니라서(몇줄 되지도 않습니다) 개인적으로 만들어서 쓰는 분들도 많고, 인터넷에도 많이 돌아다닙니다. 저는 그냥 gfx.utils.Delegate 패키지에 있는 녀석을 썼습니다.


as3였다면 뭐 이정도가 됐을 겁니다.

__loader.addEventListener(Event.COMPLETE,  itemLoadComplete);



암튼 이렇게 방식이 달라서 as3 기준으로 개발해왔던 개발자분들은 꽤 까다롭습니다.
(사실 저에게 이것보다 더 문제는 as2기반의 sdk 가 없어서 개발환경 구축이 어렵다는 겁니다만... as2를 개발하기 위해 선택한 Flashdevelop도 너무나 익숙치 않고...)


 그래서 스켈폼에서 TweenMax 로 유명한 g.skinner 에게 부탁을 해서 만든 것이 CLICK 컴포넌트 입니다. 전 이 이름을 첨 들었을 때, 무슨 버튼 컴포넌트 모음인가 했습니다.(사실 꽤 맞습니다 ㅎㅎㅎ)


그런데 사실 그것보다는 as3 기반의 EventDispatcher 클래스 메소드를 사용할 수 있게 만든 UIComponent 라고 할 수 있습니다. (저도 as3 처럼 쓰려고 Sprite 를 구현하려고 EventDispatcher 클래스를 만드는데 코드 힌트로 뜨지 멉니까... 그래서 as3용 프레임웍을 만들려던 계획을 포기하고 그냥 쓰려고 합니다. 이미 sf4가 나온 마당에 의미도 없어보이고...)


실제로 as3 와 같이 UIComponent 가 EventDispatcher 를 상속받지는 않습니다. 이미 UIComponent 는 as2의 만능 클래스(?)인 MovieClip 을 상속받기 때문입니다.


다만 MovieClip 이 메소드나 속성을 자유롭게 추가/삭제할 수 있는 dynamic 클래스인 것을 이용하여. 

EventDispatcher.initialize(UIComponent );


을 통해 addEventListener를 구현합니다. 앞서 말한 scope 문제로 인해 메소드의 인수가 완벽히 addEventListener 와 같진 않지만 말이죠... :^)

그리고 단일 객체인 EventDispatcher._instance 를 통해 이벤트를 등록해버려 효율적인 removeEventListener 도 가능하게 만들죠.

그리고 개발자들의 gc의 편의를 위해 removeAllEventListeners 도 만들어놨더군요... 기특한 gs 같으니라고...ㅎㅎㅎ

암튼 이로 인해 as3기반의 이벤트 방식이 가능해지긴 했습니다. (물론 디스플레이 계층에 따른 이벤트 전파를 구현하지는 않았습니다... 구현이 문제가 아니라 해봤자 느릴테니...)


그리고 재밌는 것은 protected 메소드가 없는 것을 private 으로 대처했더군요... 없으니깐 돌려막았나(?) 봅니다. ㅎㅎㅎ