AS3로 SystemManager 의 Preloading을 흉내내기
* 이 글은 앞서 쓴 http://www.diebuster.com/?p=321 와 관련이 있으므로 미리 읽어보시면 도움이 됩니다.
Flex 프레임웍에서 SystemManager가 하는 일은 뭐냐? 라고 물어보면 한마디로 시스템 초기화라고 할 수 있습니다. 최초 이것저것 기본적인 실행 환경을 만들어 준 뒤, 진짜 주인공인 Application 객체를 호출하고 바톤을 넘기게 됩니다.
SystemManager의 초반 작업 중 중요한 부분은 preloading 입니다.
SystemManager가 Preloading을 구현하는 비밀
공식문서를 뒤져도 나오지 않는 메타데이터 태그 중 Main.as(제네레이트 된)의 소스를 보면 나오는 코드가 있습니다. 바로 아래 한 줄인데,
[Frame(factoryClass="SystemManager")]
이 [Frame] 메타데이터 태그에 대한 공식 문서는 없는 건지 못 찾은 건지 모르겠으나 여하간 mxmlc가 인식하는 건 분명합니다.
이 메타데이터 태그의 용도는 어플리케이션 클래스가 자동으로 stage의 자식이 되는 것을 막고 factoryClass를 stage의 0번째 자식으로 추가해주는 기능을 합니다.
따라서 이 팩토리 클래스를 무비클립으로 만들어 고전적인 if( framesLoaded === totalFrames ) 조건을 이용해 본체의 로딩 완료여부를 체크한 후 본체를 생성(팩토링)하여 사용하게 해주는 것입니다. 전체 swf를 하나의 무비클립으로 본다면 0번 프레임과 그 외로 나눠주는 기능인 셈입니다.
이러한 팩토리 클래스는 다음과 같은 흐름과 조건을 갖습니다.
- 무비클립을 상속받은 팩토리 클래스를 제작하고 이를 어플리케이션 클래스의 [Frame(factoryClass=””)] 항목에 지정해준다.
- 팩토리 클래스는 onEnterFrame 이벤트를 통해 framesLoaded === totalFrames 조건을 감시한다.
- 로딩되는 상태를 모니터링 하기 위해 loaderInfo.bytesLoaded 및 loaderInfo.bytesTotal 속성을 사용한다.
- 프레임로딩 완료 후 투영을 통해 Main클래스를 생성하여 addChild 한다.
이 과정은 단순해 보이지만 2가지 숨겨진 테크닉이 요구됩니다.
- Main클래스의 이름을 알아내야 한다. 만약 클래스 투영 시 main 이라고 기술한다면 반드시 어플리케이션 클래스의 이름은 main이 되어야 하는데, 보통 자유롭게 이름을 사용하므로 좋지 않다.
- 프리로딩 절차가 끝난 후 Main 클래스가 실행되는 시점에서 stage 상에 팩토리 클래스는 완전히 제거 되어 있어야 한다.
어플리케이션 클래스의 이름을 알아내기
어플리케이션 클래스의 이름을 알아내는 힌트는 loadInfo 객체에 있습니다. loadInfo는 현재 로딩된 swf 의 이름을 알아내는 loaderURL 이란 속성을 제공합니다. 개발자가 어플리케이션 클래스 이름과 swf의 이름을 다르게 가져가면 문제가 되지만 딱히 이것 외엔 방법이 없어 보이므로 loaderURL 기반으로 객체 이름을 투영해보겠습니다.
var main:Class;
main = getDefinitionByName(
loaderInfo.loaderURL.substring( //원래 문자열은 c:/…/main.swf 와 같은 완전한 swf의 경로
loaderInfo.loaderURL.lastIndexOf('/') + 1, // 마지막 슬래시 부터
loaderInfo.loaderURL.lastIndexOf('.') //확장자의 점까지를 잘라낸다.
) //이 문자열 작업의 결과로 main 을 얻게 된다.
) as Class; //main이름 기준으로 투영!
머 간단하죠 ^^; 이제 팩토리를 안전하게 제거하고 main만 스테이지에 남겨보겠습니다.
stage.addChild(new main); stage.removeChildAt(0);
이로서 main입장에선 깨끗하게 자신만 stage에 들어간 상태로 시작할 수 있게 됩니다.
Factory Class 샘플
간단히 제작한 Fatory Class의 샘플을 하나 보겠습니다.
package{
import flash.display.*;
import flash.events.*;
import flash.utils.*;
public class Cfactory extends MovieClip{
public function Cfactory(){
stop();
addEventListener(Event.ADDED_TO_STAGE,ADDED_TO_STAGE);
}
private function ADDED_TO_STAGE( $e:Event ):void {
removeEventListener(Event.ADDED_TO_STAGE,ADDED_TO_STAGE);
addEventListener( Event.ENTER_FRAME, hnProgress);
}
private function hnProgress( $e:Event ):void {
graphics.clear();
if( framesLoaded === totalFrames ){ //로딩완료시
removeEventListener(Event.ENTER_FRAME, hnProgress);
nextFrame();
hnLoaded();
}else{ //로딩중 게이지 갱신
graphics.lineStyle( 1, 0 );
graphics.beginFill(0xffffff);
graphics.drawRoundRect( stage.stageWidth-50, stage.stageHeight-5, 100, 10, 5 );
graphics.endFill();
graphics.beginFill(0x9a9a9a);
graphics.lineStyle( 0, 0 );
graphics.drawRoundRect( stage.stageWidth-49, stage.stageHeight-4, 98 * loaderInfo.bytesLoaded / loaderInfo.bytesTotal, 8, 5 );
graphics.endFill();
}
}
private function hnLoaded():void{
var main:Class;
main = getDefinitionByName(
loaderInfo.loaderURL.substring(
loaderInfo.loaderURL.lastIndexOf('/') + 1,
loaderInfo.loaderURL.lastIndexOf('.')
)
) as Class;
stage.addChild(new main);
stage.removeChildAt(0);
}
}
}
이 코드는 제가 만들었으므로 자유롭게 사용하셔도 됩니다.
Main Class
실제 위의 팩토리 클래스를 사용하는 어플리케이션 클래스도 보겠습니다.
package {
import flash.display.*;
import flash.events.*;
[Frame(factoryClass="Cfactory")]
public class test extends Sprite {
[Embed(source="menu1_0.jpg")]
public var picture1:Class;
[Embed(source="menu1_1.jpg")]
public var picture2:Class;
public function test() {
addEventListener(Event.ADDED_TO_STAGE,ADDED_TO_STAGE);
}
private function ADDED_TO_STAGE( $e:Event ):void {
removeEventListener(Event.ADDED_TO_STAGE,ADDED_TO_STAGE);
trace(stage.numChildren); // 1을 반환
trace(stage.getChildAt(0)===this); //true 반환
}
}
}
부록 * root란 무엇인가?
root는 경우에 따라 다르게 해석되기 때문에 난해합니다. root는 아래와 같은 경우로 나눠서 생각해볼 수 있습니다.
- Loader.root : 로더의 root는 언제나 loader.content.getChildAt(0) 와 같은 의미다.
- Bitmap.root : 비트맵의 root는 언제나 자기 자신이다. Bitmap.root == Bitmap
- Stage.root : 스테이지의 root도 언제나 자기 자신이다. Stage.root == Stage
- displayObject : DisplayObject의 root는 DisplayObject의 포함관계 트리 상의 최상위 객체를 나타낸다.
- 로드 된 SWF 소속의 displayObject : 해당 SWF내에서의 포함관계 트리 상 최상위 객체를 나타낸다.
간단히 정리하면 일반적으로 stage에 등록된 첫번째 displayObjectContainer가 바로 root입니다. 따라서 그 클래스가 그 시점에서 stage의 첫번째 자식인 경우는 root 자기 자신이므로 쓰나 안쓰나 똑같습니다.
위에 설명한 팩토리 클래스의 경우 최초 stage에 로딩되는 클래스 이므로 당연히 this.root 와 this는 같은 의미가 됩니다. 여러 예제 등에서 어플리케이션 클래스에서 root를 사용하는 경우가 있는데, 구지 사용할 필요는 없는 것이죠.
관련된 글:
정말 좋은 팁입니다.
AS3 프로젝트를 가지고 장난칠때 잘 사용해서 사용자들에게 기다리는 일이 없도록 해야겠네요.
그리고 Flash Builder에서 AS3 로 프로젝트를 만들때 위의 내용만 가지고 하면 “default css file not found” 이라는 경고가 뜨더라구요. 아무래도 이 과정이 Flex와 연결되는 부분으로 컴파일러가 처리하는 것 같은데 저 경고문구 없애려고 빈 null.css를 만들어 컴파일 옵션에 -defaults-css-url null.css 을 주었습니다.
컴파일러 단계에서 처리하는 거라 어쩔 수 없다고 생각이 들긴 하지만 다른 방법이 없을까 궁금하기도 하네요.
아직 정확하게 확인을 하지는 않았는데 .actionscript 프로젝트 설정 파일에 조정 옵션이 있었던 듯 합니다. 확인해보고 다른 포스트로 올릴께요. 근데 위에 코멘트 하신걸로 벌써 블로깅을 하나 하셨군요 ^^
ActionScript 3.0에서 Preloader 구현 및 Default css file not found 경고 메시지 없애기…
Flex의 SystemManager는 Flex가 구동될 때 Application이 동작하기전 각종 설정을 하면서 사용자들에게 충분히 그 시간을 기다릴 수 있도록 UI적으로 Preloading 화면을 보여준다. 하지만 Flex가 아닌 ActionScript 3.0 프로젝트로 만들면 이런 UI를 보여주지 않는다. 하지만 못할 것도 없는 것이 Flex도 되는데 ActionScript라고 못할까? 사실 매우 쉬운 방법으로 이 기능을 추가할 수 있다. 다음…
오픈캐스트를 통해 처음 오게되었는데… 현실적으로 도움되는 좋은 글이 많네요~^^a 좀 어렵기도..
그냥 보고가기 지송해서 글남겨요~~ 또 오겠습니다~ __)
^^ 고맙습니다. 도움이 되시길 바랍니당~
좋은 클래스 감사합니다.
유용하게 쓰겠습니다.
항상수고하십니다.
stage numChildren >> 2
stage getChildAt >> false
저는 이렇게 뜨는군요;;
그래도 잘 작동하니’ㅅ’a;;; 문제 없다고 생각합니다;;;
소스가 틀렸을지도 몰라요 ^^ 실제 제가 쓰는 클래스가 아니라 이 글을 적을때 생각난걸 걍 작성한거라 ㅎㅎ
감사합니다. 정말 많은 도움이 되었습니다.
제가 딱 찾던 내용을 정리 해주셨네요. ^^
반갑습니다. 나중에 완성된 어플이 공개가능하다면 보여주세요~
좋은 정보 감사합니다. 잘보고 갑니다. 자주 구경오겠습니다.^^
오래된 포스트인데도 꾸준히 찾아주시는듯. 감사합니다.