노무현 대통령 배너

이벤트에 뭔가 같이 보내보자 – 커스텀 이벤트 만들고 사용해보기

by on 6.07, 2009, under AS3.0 API

sale_imgAS2.0 에서 AS3.0 으로 넘어와 보면 가장 처음 느끼는 것이 “뭐이리 복잡하게 해야 되는 거냐?” 하는 점이겠죠. 버튼에 액션을 넣어 뭔가를 하고 싶은 경우라면 button.onRelease = function() { //하고싶은일 } 이나 심지어 무비클립에 on(release) { //하고싶은일 } 같이 간단한 코드만으로 하고싶은 일을 하던것에 비해서 AS3.0 의 이벤트 리스너 방식은 귀찮기만 해 보입니다.

이 포스트에서는 이벤트 시스템이 2.0 이전의 콜백 방식에 비해 가지는 유리한 점 중의 하나를 살펴보겠습니다. 커스텀 이벤트 클래스를 이용하여 이벤트를 보내면서 특정한 정보를 보낼 수 있는 방법입니다. 콜백 방식으로는 부릴 수 없는 재주죠.

우선 커스텀 이벤트 클래스를 하나 만들어 봅니다.

0
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
package 
{
	import flash.events.Event;
 
	public class CustomEvent extends Event
	{
		static public const CALL:String = "call";
		private var _data:Object;
 
		public function CustomEvent( type:String, data:Object, bubble:Boolean = false, cancelable:Boolean = false )
		{
			super( type, bubble, cancelable );
			this._data = data;
		}
 
		override public function clone():Event
		{
			return new CustomEvent( type, _data, bubbles, cancelable );
		}
 
		public function get data():Object
		{
			return _data;
		}
	}
}

커스텀 클래스의 모양새는 거의 이런 식입니다. bubble, cancelable 등 몇가지 파라미터들이 있습니다만, 이 포스트에서는 커스텀 클래스를 이용한 데이터 전송만을 다룰 것이므로, 생성자에서 object 를 인자로 받아 변수로 저장하고 있다는 것만 기억하면 되겠습니다.

커스텀 클래스는 어떻게 사용하는지 볼까요?

0
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
package 
{
	import flash.display.Sprite
	import flash.events.*;
	import CustomEvent;
 
	public class CustomEventTest extends Sprite 
	{
		private var broadcast:EventDispatcher = new EventDispatcher();
		private var info:Object = { info1:"의류관 세일중. ", info2:"무료시식코너 운영중. ", info3:"제휴카드 무이자 할부중. " };
 
		public function CustomEventTest ()
		{
			stage.addEventListener( MouseEvent.CLICK, clickHandler );
			broadcast.addEventListener( CustomEvent.CALL, broadcastListen );
			broadcast.addEventListener( CustomEvent.CALL, customer1Action );
			broadcast.addEventListener( CustomEvent.CALL, customer2Action );
		}
 
		private function clickHandler( e:MouseEvent ):void 
		{
			broadcast.dispatchEvent( new CustomEvent( CustomEvent.CALL, info ) );
		}
 
		private function broadcastListen( e:CustomEvent ):void 
		{
			trace( "----모든 이벤트 목록 : ", e.data.info1, e.data.info2, e.data.info3 );
		}
 
		private function customer1Action(e:CustomEvent):void 
		{
			trace( e.data.info2, ": 먹으러 가볼까나" );
		}
 
		private function customer2Action(e:CustomEvent):void 
		{
			trace( e.data.info1, ": 찜해놨던 옷을 사러가자" );
			trace( "옷을 샀으니 이제 방송 안들어" )
			broadcast.removeEventListener( CustomEvent.CALL, customer2Action );
		}
 
	}
}

위의 내용은 백화점에서 세일 정보를 방송 ( broadcast ) 하고 있고, 그 정보에 반응하는 백화점 고객이 있다는 설정 입니다.

옵저버 디자인 패턴의 등장인물 소개에서 주제를 여러가지 단어로 설명을 했었는데요,[01] broadcaster 도 주제를 표현하는 단어로 볼 수 있습니다. (그래서 이런 비유를 선택했습니다.)

실제로 이벤트 시스템은 옵저버 디자인 패턴에서 주제객체가 update 메서드를 이용해 옵저버들에게 정보를 전달하는 방식과 동일합니다. 디스패처는 이벤트 리스너 목록에 있는 리스너들에게 정보를 뿌려주죠.

인스턴스 멤버를 만든 부분을 볼까요?

0
1
private var broadcast:EventDispatcher = new EventDispatcher();
private var info:Object = { info1:"의류관 세일중. ", info2:"무료시식코너 운영중. ", info3:"제휴카드 무이자 할부중. " };

위와 같이 EventDispatcher 객체를 생성하면서 broadcast 라고 이름 지었고, 이벤트를 통해 전달할 정보인 info 를 Object 객체로 만들었습니다.

아래는 커스텀 이벤트를 발생시키는 디스패치 이벤트 입니다.

0
broadcast.dispatchEvent( new CustomEvent( CustomEvent.CALL, info ) );

이렇게 이벤트를 디스패치 하게 되면 broadcast 객체에 addEventListener 시켜 CustomEvent.CALL 이벤트가 발생하는지 귀 기울이고 있던 리스너에 info 가 전달 됩니다.

이벤트 모델은 리스너들이 정보를 받아보는지 아닌지에 대한 것은 디스패처가 관심 없습니다. 다만 정보를 이벤트를 통해 보낼 뿐이죠. 이러한 구조는 객체지향 프로그래밍이 지향하는 약한 결합 (Loose Coupling)을 표현합니다.

이 글을 복사해서 퍼가시는건 허용하지 않습니다. 글의 주소를 다른곳에 알려주시는 것은 환영합니다.
  1. dispatcher, supplier, notifier, publisher, server, service, provider []

관련된 글

:, , , , , , , , , , , , , ,

5 Comments for this entry

  • 또뱅No Gravatar

    자주 질문 드려서 죄송합니다! ㅎㅎ 제가 알고싶은게 너무 많거든요~

    3일동안 머리를 짜내다가 결국엔 질문 드립니다!

    일단 그림으로 보여드릴게요

    Main
    / \
    A B
    / \ / \
    C D E F
    / \
    G H

    이렇게 생긴 구조가 있습니다.

    Main 에서 A와 B를 addChild

    A는 C와 D를 , B는 E와 F를 addChild

    C는 G를 F는 H를 addChild했습니다.

    Main은 도큐먼트 클래스이고 모든 as파일들은 sprite를 상속받았습니다.

    따로 ClassEvent라는 커스텀 이벤트를 만들어 놓았구요

    ClassEvent의 생성자 매개변수로는 (type : String, Obj : Object)로 되어 있습니다.

    bubble과 cancelable 는 생략했습니다.

    그리고 나머지는 커스텀 이벤트와 똑같습니다.

    여기서 Main.as의 코드 내용을 보여드리겠습니다.

    public class Main extends Sprite
    {
    private var _A = A;
    private var _B = B;

    public var _Dispatch : EventDispatcher = new EventDispatcher();

    public function Main() : void
    {
    _A = new A();
    _B = new B();

    this.addChild(_A);
    this.addChild(_B);

    parent.addEventListener(MouseEvent.CLICK, clickHandler);
    }

    private function clickHandler($e : MouseEvent) : void
    {
    _Dispatch.dispatchEvent(new ClassEvent(ClassEvent.SEND_OBJECT, this));
    }
    }

    글이 짤리네요 ..

    A와 B를 addChild하고 클릭했을 때 dispatchEvent를 한 상황입니다.

    이떄 매개변수로 넣은 this[Onject Main] 을 H.as에서 받으려면

    어떤식으로 해야되는지 도저히 모르겠습니다.

    H.as에서 public var _Dispatch : EventDispatcher = new EventDispatcher();

    를 넣고 _Dispatch.addEventListener(ClassEvent.SEND_OBJECT, handler);

    뭐 이런식으로 받으려고 해도

    안받아지더라구요

    H.parent.parent.parent. 막 이런식으로 parent를 남발하기가 좀 그래서

    이벤트로 Object를 보내 그걸 바로 받고 싶은데

    잘 모르겠네요 ;;

    이것만 해결되면 만들던 게임이 거의 완성이 되는데

    아직 초보라 막코딩을 하는 수준이라……..

    한번 해보고 싶어서 질문 드렸습니다.

    수고하세요!

  • 또뱅No Gravatar

    아 글을 너무 헷갈리게 적었네요 ;;

    그냥 코드를 보여드리겠습니다 ㅠㅠ;;

    Main 이구요!

    0
    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
    
    package  
    {
    	import flash.display.Sprite;
    	import flash.events.EventDispatcher;
    	import flash.events.MouseEvent;
    	/**
    	 * ...
    	 * @author DDo
    	 */
    	public class Main extends Sprite
    	{
    		private var _B = B;
     
    		private var _A = A;
     
    		//static public var mainCl : Main
     
    		public function Main() : void
    		{
    			//mainCl = this;
     
    			_A = new A();
    			_B = new B();
     
    			this.addChild(_A);
    			this.addChild(_B);
     
    			parent.addEventListener(MouseEvent.CLICK, clickHandler);
     
    		}
     
    		private function clickHandler($e : MouseEvent) : void
    		{
    			//this.dispatchEvent(new ClassEvent(ClassEvent.SEND_OBJECT, this));
    			_B.clickHandler();
     
    		}
    	}
    }

    A입니다.

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    package  
    {
    	import flash.display.Sprite;
    	/**
    	 * ...
    	 * @author DDo
    	 */
    	public class A extends Sprite
    	{
    		private var _C : C;
     
    		private var _D : D;
     
     
    		public function A() : void
    		{
    			_C = new C();
    			_D = new D();
    			this.addChild(_C);
    			this.addChild(_D);
    		}
    	}
    }

    B입니다.

    0
    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
    
    package  
    {
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.EventDispatcher;
    	/**
    	 * ...
    	 * @author DDo
    	 */
    	public class B extends Sprite
    	{
    		static public var S_B : B;
     
    		public function B() : void
    		{
     
    		}
     
    		private function callHandler($e : ClassEvent) : void
    		{
    			trace($e.currentTarget);
    		}
     
    		public function clickHandler() : void
    		{
    			trace("클릭해서 B로 넘어왔음요 !");
    			this.dispatchEvent(new ClassEvent(ClassEvent.SEND_OBJECT, this));
    		}
    	}
    }

    C는 아무 내용도 없이 그냥 빈것 입니다.

    D입니다.

    0
    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
    
    package  
    {
    	import flash.display.Sprite;
    	/**
    	 * ...
    	 * @author DDo
    	 */
    	public class D extends Sprite
    	{
     
    		public function D() : void
    		{
    			trace(B.S_B); // null이 뜹니다!
     
    			B.S_B.addEventListener(ClassEvent.SEND_OBJECT, sendObject);
    			// null 이므로 읽히지 않습니다...........
    		}
     
    		private function sendObject($e : ClassEvent) : void
    		{
    			trace("D 에서의 반응입니다");
    			trace($e.Obj, $e.currentTarget);
    		}
     
    	}
    }

    가르쳐 주신 커스텀 이벤트의 파일 이름은 ClassEvent이구요

    data가 Obj로 변했다는 것 빼곤 모두 똑같습니다.

    제가 만든 코드의 전부 입니다

    Main.. 그러니까 stage에서 클릭했을때의 이벤트를 B로 보내서

    B에서 dispatchEvent를 했고

    그 이벤트 내용(?) 을 D에서 받고 싶어서 리스너를 작동 시켰는데 아무 반응이 없길래

    trace로 확인을 해보니 null값이 나왔습니다.;;

    음…. null값이 안나와야 읽을 수 있는데 여기서 딱 막혀버렸어요 ..

    D에서 받고싶은데 방법을 잘 몰라서 질문했습니다..

    윗글은 신경안쓰셔도 됩니다. 글이 길어서 죄송합니다 ㅎㅎ;;

    • 세계의끝No Gravatar

      B 클래스에 선언되어 있는 B형 타입의 S_B 변수는 선언만 되어있고 실제 객체가 할당되어 있지 않는 상태군요.
      객체가 존재하지 않으니 D 클래스에서 addEventListener() 를 달아 보려해도 null 객체 에러가 뜰 것이고요.
      이런 경우에 B 클래스의 생성자 함수에

      0
      
      B.S_B = this;

      라고 한줄 추가하면 B클래스로 생성한 객체가 변수에 할당되어 이벤트 핸들러든 이벤트를 디스패치 하든 모두 할 수 있게 됩니다.
      단, 디스패치든 이벤트 리스너든 달기 위해서는 B 클래스의 객체가 먼저 생성되어 있어야겠죠.
      무슨 이야기냐면, 이 구조에서 실질적인 호스트코드는 D 클래스로 생성한 객체의 코드인데, D 객체는 A 클래스 객체가 만들어지면 동시에 자신도 만들어지고 있습니다. 만약 A 객체가 먼저 만들어지면 D클래스의 생성자 함수가 호출되어 B.S_B 변수에 addEventListener() 를 하려는 타이밍에는 B 객체가 생성되어 있으리라는 보장이 없다는 말입니다.
      실제로 Main 클래스에서 A 클래스 객체를 먼저 생성하고 계시고 있죠.
      따라서 Main 클래스에서 객체를 new 하는 순서를 바꿀 필요가 있습니다.

      0
      1
      
      _B = new B();
      _A = new A();

      이렇게 말이죠.

      질문하신 댓글의 코드가 보기가 불편해서(다른 분들이 보기 어려우실까 하여) 포스트 본문과 같이 Syntax Coloring 을 적용했습니다. ^^

      • 또뱅No Gravatar

        와 정말 좋은 설명 감사합니다!! 제 코드까지 보기좋게 꾸며 주셨네요 ^^

        답해주신 모든 내용이 이해가 갔습니다.

        그런데 만약 B보다 A를 먼저 addChild해야 하는 상황이면 어떻게 되는 건가요??

        제가 Object를 던지는 방식을 dispatchEvent나 Object를 직접적으로 던지는 방법밖에 몰라서

        이렇게 코드를 짜고 있는데 이런 방식을 쓰지 않고도 원하는 클래스 오브젝트에 접근하는 방법이

        있는지도 좀 알려주세요 ㅎㅎ

        (AS3.0 클래스 구조의 여러가지 상황에서 부모, 자식 객체의 참조 방법)에서의 내용은

        전부 쓰고 있습니다만 현재 만들고 있는 게임이 처음 만드는 게임이라

        addChild된 구조가 상당히 얽혀 있는 상태라서요;;

        모든 자료를 찾아가며 공부 하고 있지만 정작 필요한 내용은 없는거 같아서요 ㅎㅎ

        바쁘신데 많은 질문 드려도 괜찮을진 모르겠지만

        시간나시면 답변 주세요 !!

        윗 댓글에 대해서 정말 감사드립니다! 많은 도움이 되었습니다 ~

        수고하세요 “`

    • 세계의끝No Gravatar

      addChild() 와 null 객체 에러와는 직접 관련이 없습니다. 화면에 addChild() 되어 있지 않아도 addEventListener() 다는데는 문제 없으니까요.
      굳이 표현하자면 new 와 관계 있다고 할 수 있는데요, 객체가 new 되는 시점이 서로 모순되지 않아야 null 객체 에러가 줄어들겠죠.

      “null 객체 에러 예방을 위한 액션스크립트 클래스의 초기화 순서 이해하기” http://ufx.kr/blog/853
      위 포스트를 정독해 보시면 초기화 순서에서 꼬이지 않는 약간의 노하우를 캐치하실지도 모릅니다.
      요점은, 생성자 이전, 또는 변수 선언 수준에서 모두 new 를 때리시라는것.
      런타임에서 new 를 최소화 하시라는것, 이 두가지라고 할 수 있습니다.

1 Trackback or Pingback for this entry

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Meta