사용자정의 클래스를 만들 때

메서드와 이벤트리스너(Event를 Dispatch하는 클래스일 경우)와 관련된 코드힌트를 띄우는 방법입니다.


FlashDevelop(플래시디벨롭)으로만 해 본 거라서

다른 액션스크립트 개발툴에서도 되는지는 모릅니다.




1. Method details


/**

 * This is function.

 * @param a parameter1

 * @param b parameter2

 * @return something

 */

public function func1(a:int, b:String):int

{

return 0;

}

메서드 func1()을 사용할 때 코드힌트가 뜬다.





2. Event 메타태그


[Event(name = "complete", type = "flash.events.Event")]

public class ClassA extends EventDispatcher {


아래와 같이 addEventListenr( 를 입력하면 등록해놓은 이벤트가 뜬다.

이벤트가 여러개일 경우엔 메타태그를 여러 번 써도 된다.






1번과 같은 경우는 'documentation'이라고 해서 ASDoc문서를 구성할 수 있는 것들이다.

클래스 선언부 위에 

/**
*
*
*/

과 같은 꼴로 'documentation box'를 만들어서 @author, @version, @eventType등의 정보를 쓰고

메서드 선언부 위에도 @param, @return 등의 documentation tag를 달 수 있다.



자세한 내용은 documentation tag나 AsDoc 등의 검색어로 검색해보면 잘 나오니 pass..




p.s. /** ... */ 꼴 대신 슬래시를 3개 쓴 ///를 써도 가능하다.






function a() { b(); } function b() { c(); } function c() { d(); } function d() { trace(""); //중단점 1 setTimeout(e, 0); } function e() { trace(""); //중단점 2 } a();




● 중단점 1에서의 호출스택



d

c

b

a

init

Main





● 중단점 2에서의 호출스택 (비동기적으로 새로운 스택으로 실행된다.)



e

builtin::apply

onTimer

_timerDispatch

tick



flash.utils.setTimeout()함수를 실행하면

flash.utils.Timer객체의 함수들이 실행되고있다.

그래서 AS3.0레퍼런스에도 setTimeout()을 쓰기보다

repeat이 1인 Timer객체를 쓰라고 추천하고있다.








 

이 글은 tibyte.krcafe.naver.com/sdbx에 게시됩니다.

(설명글은 http://cafe.naver.com/sdbx/737에 있습니다.) 


플래시 클라이언트는 소켓서버에 연결할 때 정책파일(crossdomain.xml)을 달라는 요청을 보내는데 이 파일을 보내주지 않으면 통신을 시작할 수 없습니다.

클라이언트에서 Security.loadPolicyFile()메서드로 원격 서버와 같은 도메인에 연결하여 xml파일을 직접 받아올 수 있지만 그러지 못하는 상황에는 서버프로그램에서 이 정책파일을 직접 보내줄 수도 있습니다.

ActionScript 3.0으로 작성된 Adobe AIR 소켓서버 코드를 보겠습니다.

 

 


우선 ServerSocket객체를 만들고 bind()listen()을 수행하여 서버소켓을 가동시키는 함수입니다. (serverSocket은 이 클래스의 private멤버변수입니다.)


private function bind():void

{

//기존에 실행 중인 소켓서버가 있으면 닫고 새로 실행

if(serverSocket.bound)

{

serverSocket.close();

serverSocket = new ServerSocket();

}

serverSocket.bind(로컬포트, 로컬IP);

serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, onConnect);

serverSocket.listen();


//로그 코드 삽입

}

 



다음은 ServerSocketConnectEvent.CONNECT 이벤트가 발생했을 때  실행되는 onConnect함수입니다.



private function onConnect(event:ServerSocketConnectEvent):void

{

var socket:Socket = event.socket;

clientSockets[socket.remoteAddress] = 1;//연결된 클라이언트들을 배열로 관리

socket.addEventListener(Event.CLOSE, onClientDisconnect);//클라이언트가 접속을 끊었을

socket.addEventListener(ProgressEvent.SOCKET_DATA, onClientSocketCert);//클라이언트로부터 데이터가 들어올 때

//이 밖에도 다른 이벤트나 예외처리구문, 로그(기록) 남기는 구문들을 넣어줍니다.

}



 

연결이 되면 클라이언트는 서버로 메시지를 보내고 이에 따라 ProgressEvent.SOCKET_DATA 이벤트가 발생합니다.

 


 클라이언트가 정책파일을 찾지 못했을 때는 843번 포트로 "<policy-file-request/>" 이라고 정책파일을 요청하는 메시지를 보냅니다. 따라서 서버에서는 저 메시지가 들어왔을 때 정책파일을 보내주어야 합니다.

 



private function onClientSocketCert(event:ProgressEvent):void

{

var socket:Socket = event.target as Socket;

var message:String = socket.readUTFBytes(socket.bytesAvailable);

if (message == "<policy-file-request/>") {

var policy:String = "<cross-domain-policy>" +

"<allow-access-from domain=\"*.example.com\" to-ports=\"8800\"/>" +

"</cross-domain-policy>\x00";

socket.writeUTFBytes(policy);

socket.flush();

socket.close();

} else if(message=="BEGIN") {

socket.removeEventListener(ProgressEvent.SOCKET_DATA, onClientSocketCert);

socket.addEventListener(ProgressEvent.SOCKET_DATA, onClientSocketData);

socket.writeUTFBytes("READY");

socket.flush();

      }

//이 밖에도 로그 남기는 코드를 삽입

}

 

위 함수에서 볼 수 있듯이 XML파일을 문자열형태로 직접 전송합니다.

 

"<cross-domain-policy>" +

"<allow-access-from domain=\"*.example.com\" to-ports=\"8800\"/>" +

"</cross-domain-policy>\x00"

 

(여기서 쓴 8800번 포트는 임의로 정한 포트번호입니다.)

 

크로스도메인(crossdomain.xml)파일에서 도메인과 포트를 지정해주는데요, 와일드카드 문자를 쓸 수 있습니다.

 

 







 



private function onSelected($evt:Event):void {

var list:Array = _pathFile.getDirectoryListing();

for (var i:int = 0; i < _pathFile.length; i++) {

list[i].addEventListener(Event.COMPLETE, onFileLoaded);

            list[i].load();

     }

    }


private function onFileLoaded($evt:Event):void

{

trace(1);

 }


위와 같은 코드를 작성하고 실행했는데 1이 출력되지 않는 것이었다.

그런데 이렇게 짠 다른 프로그램은 잘 동작하는 현상..


이유는, 그 프로그램에서는 onFileLoaded함수에서 $evt인자를 받아 어떤 처리를 하는데,

여기선 list의 요소를 참조하지 않아서 객체가 못 쓰는 상태(객체의 모든 참조 제거)가 되어버린 것이었다..


결국 list배열을 멤버변수로 둬서 해결..




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


loop {

  switch(n) {

      case 1 : return 3

      case 2 : return 4

      case 3 : return 5


위와 같이 반복되는 switch문에서

n이 1인 경우와 3인 경우에 값이 반환되는 데 걸리는 시간이 같은거라는 착각이 잠시 들어서

직접 구조를 보기로 했다.


우선 비주얼스튜디오에서 x86어셈블리를 생성해 보았다.



switch(num) {

00D817FC   mov         eax,dword ptr [ebp-8] 

00D817FF   mov         dword ptr [ebp+FFFFFF30h],eax 

00D81805   cmp         dword ptr [ebp+FFFFFF30h],0 

00D8180C   je          00D81822 

00D8180E  cmp         dword ptr [ebp+FFFFFF30h],1 

00D81815   je          00D8182D 

00D81817  cmp         dword ptr [ebp+FFFFFF30h],2 

00D8181E     je          00D81838 

00D81820   jmp         00D81843 

case 0: result++; break;

00D81822     mov         eax,dword ptr [ebp-10h] 

00D81825      add         eax,1 

00D81828      mov         dword ptr [ebp-10h],eax 

00D8182B     jmp         00D8184C 

case 1: result++;break;

00D8182D     mov         eax,dword ptr [ebp-10h] 

00D81830      add         eax,1 

00D81833      mov         dword ptr [ebp-10h],eax 

00D81836      jmp         00D8184C 

case 2: result++;break;

00D81838      mov         eax,dword ptr [ebp-10h] 

00D8183B      add         eax,1 

00D8183E      mov         dword ptr [ebp-10h],eax 

00D81841      jmp         00D8184C 

default: result++;break;

00D81843      mov         eax,dword ptr [ebp-10h] 

00D81846     add         eax,1 

00D81849     mov         dword ptr [ebp-10h],eax 

}


return 0;

00D8184C      xor         eax,eax 

}



if - else 문을 썼을 때 처럼

num의 값과 case에 있는 값들을 윗쪽부터 차례차례 비교해나간다는 것을 볼 수 있다.




이번에는 액션스크립트 바이트코드를 보자.

다음과 같은 코드를 작성하고 flex sdk에 포함되어 있는 swfdump로 바이트코드를 생성했다.


    D0                       getlocal0    

    30                       pushscope    

    D0                       getlocal0    

    24 01                    pushbyte       1

    68 03                    initproperty   :n

    10 28 00 00              jump           L0


    09                   L1: label        

    D0                       getlocal0    

    24 11                    pushbyte       17

    68 05                    initproperty   :m

    10 68 00 00              jump           L2


    09                   L3: label        

    D0                       getlocal0    

    24 12                    pushbyte       18

    68 05                    initproperty   :m

    10 5E 00 00              jump           L2


    09                   L4: label        

    D0                       getlocal0    

    24 13                    pushbyte       19

    68 05                    initproperty   :m

    10 54 00 00              jump           L2


    09                   L5: label        

    D0                       getlocal0    

    24 14                    pushbyte       20

    68 05                    initproperty   :m

    10 4A 00 00              jump           L2


    D0                   L0: getlocal0    

    66 03                    getproperty   :n

    D5                       setlocal1    

    24 00                    pushbyte       0

    D1                       getlocal1    

    1A 06 00 00              ifstrictne     L6

    24 00                    pushbyte       0

    10 26 00 00              jump           L7


    24 01                L6: pushbyte       1

    D1                       getlocal1    

    1A 06 00 00              ifstrictne     L8

    24 01                    pushbyte       1

    10 19 00 00              jump           L7


    24 02                L8: pushbyte       2

    D1                       getlocal1    

    1A 06 00 00              ifstrictne     L9

    24 02                    pushbyte       2

    10 0C 00 00              jump           L7


    10 06 00 00          L9: jump           L10

    24 03                    pushbyte       3

    10 02 00 00              jump           L7


    24 03                L10:pushbyte       3

    08 01                L7: kill           1

    1B BD FF FF 03 9F FF FF A9 FF FF B3 FF FF BD FF FF  

   lookupswitch   default:L5 maxcase:3 L1 L3 L4 L5

    47                   L2: returnvoid  


예를 들어 A9로 점프하라는 명령이 있으면 L3 : 붙은 곳으로 점프되는 것이다.


코드를 보면, 우선 변수들을 초기화하고 L0으로 점프한다.

그리고 스택에 0을 넣고 ifstrictne명령으로 n과 0을 비교하는 연산을 수행한다.

스택에 들어있는 두 값이 다르면 점프를 하고 같으면 점프하지 않고 다음 인스트럭션을 실행한다.


여기서는 n이 1이므로 L6으로 점프하게 된다.

L6의 ifstrictne에선 값이 같다고 판단되므로 L8로 점프하지 않고 그냥 통과하여

jump 명령에 의해 L7로 점프한다.

여기서 점프하기 전에 스택에 1을 넣어두는데

이 값이 L7의  lookupswitch에서 어디로 분기할지를 결정하게 된다.


이 코드의 lookupswitch에서는 스택에 0이 있을 때 9F FF FF 만큼 점프하고,

1이 있을 때는 A9 FF FF만큼 점프한다. (2의 보수로 음수를 표현한 형태임)

A9 FF FF만큼 점프한 위치에는 L3이 있는데 이 부분의 인스트럭션부터 다시 아래로 실행을 시작한다.


L3으로 가 보면 m에 18을 대압하는 것을 볼 수 있다.

최종적으로는 L2로 점프하여 실행을 마친다.


===========================================================================














(이 글은 tibyte.kr와 cafe.naver.com/sdbx에 동시 게시됩니다.)



플래시를 제작하고 swf파일을 생성하면 액션스크립트 코드는

ABC(Actionscript ByteCode)라는 어셈블리 형식의 코드로 저장됩니다.

swf파일을 재생하면 그 코드를 AVM(Actionscript Virtual Machine) 인터프리터가

한줄씩 읽어서 실행하게 되는것이죠.


FlexSDK에 포함되어있는 swfdump 프로그램을 쓰면 이 코드를 쉽게 볼 수 있습니다.

이곳에서 FlexSDK를 다운로드 받을 수 있습니다.

http://www.adobe.com/devnet/flex/flex-sdk-download.html 


그리고 flexsdk가 설치된 경로를 환경변수에 등록해야하는데 이것은 검색을 해 보시면

쉽게 찾을 수 있을겁니다.



flexsdk 세팅을 완료했다면 cmd창을 열고 아래와 같은 명령어를 쳐줍니다.

가령 swf파일의 이름이 abc.swf라면,


swfdump -abc -showbytecode abc.swf > abc.txt


물론 swf파일이 다른 디렉터리에 있다면 경로를 지정해 주어야 합니다. 

제대로 실행됐다면 [그림2]와 같이 바이트코드가 있는 파일이 생성됩니다.!






[그림1] cmd창에서 swfdump 실행.




[그림2] 코드가 저장된 txt파일.





==================================================================================




▼이곳에 플래시가 삽입되어 있습니다. 




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package kr.tibyte
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Vector3D;
     
    /**
     * ...
     * @author
     */
    public class Main extends Sprite
    {
         
        [Embed(source="../../sample1.png")]
        private var SampleImage1:Class;
        [Embed(source="../../sample2.png")]
        private var SampleImage2:Class;
         
        private var _bitmaps:Vector.<Bitmap> = new Vector.<Bitmap>(IMG_NUM);
        private var _imgArr:Vector.<Sprite> = new Vector.<Sprite>(IMG_NUM);
        private var _imgWidth:int = 120;
        private var _imgHeight:int = 80;
        private var _r:Number; //회전반경
        private var _yaw:Number = 0;
        private var _container:Sprite = new Sprite();
         
        private const IMG_NUM:int = 6;
        private const THETA:Number = 0.003*3/180*Math.PI; //회전각속도
        private const MARGIN:int = 20; //이미지 간격
         
        public function Main():void
        {
             
            for (var i:int = 0; i<IMG_NUM; i++)
            {
                if (Math.random()>0.6)
                    _bitmaps[i] = new SampleImage1();
                else
                    _bitmaps[i] = new SampleImage2();
                 
                _bitmaps[i].x = -_bitmaps[i].width/2-MARGIN;
                _bitmaps[i].y = -_bitmaps[i].height/2;
                _imgArr[i] = new Sprite();
                _imgArr[i].addChild(_bitmaps[i]);
                _imgArr[i].width = _imgWidth;
                _imgArr[i].height = _imgHeight;
                _container.addChild(_imgArr[i]);
            }
            //회전반경을 이미지크기+마진값에 딱 맞게 설정
            _r = (_imgWidth+2*MARGIN)/(2*Math.tan(Math.PI/IMG_NUM));
             
            _container.x = 200;
            _container.y = 150;
            this.transform.perspectiveProjection.projectionCenter = new Point(_container.x, _container.y);
            addChild(_container);
             
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
            stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        }
         
        private function mouseMoveHandler($evt:MouseEvent):void
        {
             
            var old:Point = this.transform.perspectiveProjection.projectionCenter;
            old.y = stage.mouseY;
            root.transform.perspectiveProjection.projectionCenter = old;
         
        }
         
        private function enterFrameHandler($evt:Event):void
        {
             
            //마우스로 회전제어
            _yaw += THETA*(stage.mouseX-stage.stageWidth/2);
             
            display();
         
        }
         
 
         
        private function display():void
        {
            var angle:Number;
            for (var i:int = 0; i<IMG_NUM; i++)
            {
                angle = _yaw+i*2*Math.PI/IMG_NUM;
                _imgArr[i].x = _r*Math.cos(angle);
                _imgArr[i].z = _r*Math.sin(angle);
                 
                //angle과 수직이 되도록 rotationY 를 설정
                _imgArr[i].rotationY = (180/Math.PI)*-1*(Math.PI/2+angle);
                 
                 
                /* //rotationY 대신 transform속성으로 회전하기
                   _imgArr[i].transform.matrix3D.pointAt(new Vector3D(),Vector3D.Z_AXIS, new Vector3D(0, -1, 0));
                 */
            }
             
            //다시 그리기
            var seq:Vector.<int> = genSequence(IMG_NUM);
            seq.sort(compare);
             
            _container.removeChildren();
            for (i = 0; i<IMG_NUM; i++)
            {
                _container.addChild(_imgArr[seq[i]]);
            }
         
             
            //z소팅을 위한 비교함수
            function compare($a:int, $b:int):Number
            {
                if (_imgArr[$a].z>_imgArr[$b].z)
                    return -1;
                else if (_imgArr[$a].z==_imgArr[$b].z)
                    return 0;
                else
                    return 1;
            }
        }
 
         
        private function genSequence($n:int):Vector.<int>
        {
            var arr:Vector.<int> = new Vector.<int>($n);
            for (var i:int = 0; i<$n; i++) {
                arr[i] = i;
            }
            return arr.concat();
        }
    }
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
var file:File = new File();
file.addEventListener(Event.SELECT, onSelected);
file.browseForSave("save");
 
function onSelected(evt:Event):void
{
    if (evt.target.extension != "bin")
        evt.target.nativePath += ".bin";
     
    var stream:FileStream = new FileStream();
    stream.open(evt.target as File, FileMode.WRITE);
    stream.writeUTF("Content");
    stream.close();
}


File 객체에서 browseForSave로 저장창을 열고

Event.SELECT이벤트가 발생했을 때 

파일의 확장자를 확인하여 원하는 확장자로 덧붙인다.


FileStream을 써야 하기 때문에 Adobe AIR로 해야한다.






*********************************************************




var urlLdr:URLLoader = new URLLoader();

try {

urlLdr.load(new URLRequest("http://tibyte.kr/tibyte.txt"));

} catch (e:IOError) {  //cannot catch

}


다음과 같이 존재하지 않는 파일을 로드하면 런타임 에러가 뜬다.

예외처리를 했는데도 catch가 되지 않는데, 에러가 비동기적으로 발생하기 때문.

그래서 이벤트를 통해 예외처리를 해야한다. 


var urlLdr:URLLoader = new URLLoader();

urlLdr.addEventListener(IOErrorEvent.IO_ERROR, function h(e:IOErrorEvent){});

urlLdr.load(new URLRequest("http://tibyte.kr/tibyte.png"));


 ※Loader클래스일때는 contentLoaderInfo속성에 IOErrorEvent를 걸어주면 된다. 










액션스크립트3.0으로 만든 RGB큐브.



(▼여기에 플래시가 삽입되어 있습니다)




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import flash.display.Shape;
import flash.geom.Vector3D;
import flash.geom.Matrix3D;
import flash.events.MouseEvent;
 
var scale:int = 160;
var density:int = 10;
var densityCube:int = density*density*density;
var points:Vector.<Shape> = new Vector.<Shape>(densityCube);
var pointsPosition:Vector.<Vector3D> = new Vector.<Vector3D>(densityCube);
var centralPoint:Vector3D = new Vector3D(stage.stageWidth/2, stage.stageHeight/2, scale/2);
var angleSpd:Number = 0;
var mouseAngle:Number = 0;
var mouseDistance:Number = 0;
var mouseSin:Number = 0;
var mouseCos:Number = 1;
 
for(var i_z:int=0; i_z<density; i_z++) {
    for(var i_y:int=0; i_y<density; i_y++) {
        for(var i_x:int=0; i_x<density; i_x++) {
            var index:int = density*density*i_z+density*i_y+i_x;
            var color:uint = int(i_z/density*256)<<16|int(i_y/density*256)<<8|int(i_x/density*256);
            points[index] = new Shape();
            points[index].graphics.beginFill(color);
            points[index].graphics.drawRect(0,0,4,4);
            points[index].graphics.endFill();
             
            pointsPosition[index] = new Vector3D();
            pointsPosition[index].x = centralPoint.x-scale/2 + i_x*scale/(density-1);
            pointsPosition[index].y = centralPoint.y-scale/2 + i_y*scale/(density-1);
            pointsPosition[index].z = centralPoint.z-scale/2 + i_z*scale/(density-1);