INDIES

[cocos2d-x] 입력 처리 및 메모리 관리 본문

프로그래밍/cocos2d-x

[cocos2d-x] 입력 처리 및 메모리 관리

jwvg0425 2014. 10. 16. 13:49

1. cocos2d-x에서의 입력 처리

1-1. keyboard 입력

기본적으로 모든 cocos2d-x에서 입력에 관련된 처리는 event-driven 방식을 이용한다. 즉 각 클래스가 입력이 들어왔을 때 어떤 처리를 수행할지를 정의하는 콜백 함수를 만들어두고, 이 함수를 엔진에 등록하면 엔진이 게임 루프를 돌다 해당 입력이 들어왔을 때 자동으로 등록된 함수를 호출하여 각 클래스가 입력에 따른 처리를 수행할 수 있게 한다.

Keyboard 입력 처리 함수의 원형

void onKeyPressed(cocos2d::EventKeyboard::KeyCode keyCode, cocos2d::Event* event);
void onKeyReleased(cocos2d::EventKeyboard::KeyCode keyCode, cocos2d::Event* event);

onKeyPressed함수는 특정 키가 눌린 시점에 호출이 되고 onKeyReleased함수는 특정 키가 눌렸다 떨어진 시점에 호출이 된다. 첫번째 인자로 넘어온 keyCode가 어떤 키가 눌렸는지를 말하고 event 인자는 쓰이지 않는다.

  • 작성 예제

    //cocos2d 네임스페이스 사용
    USING_NS_CC;
    
    //ESC 키가 눌렸을 경우 게임을 종료한다.
    void SampleClass::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event)
    {
      if(keyCode == EventKeyboard::KeyCode::KEY_ESCAPE)
      {
          Director::getInstance()->end();
      }
    }
    

Keyboard 입력 처리 함수 등록

키보드 입력 처리 함수를 모두 작성했으면 해당 입력 함수를 엔진에 등록하는 작업이 필요하다. 이 등록하는 과정을 이해하기 위해서는 우선 cocos2d-x의 이벤트 처리 방법에 대해 알아야 한다.

cocos2d-x의 이벤트 처리는 크게 3가지로 구성되는데, 각각은 다음과 같다.

  • Event listeners 이벤트의 종류에 따라 서로 다른 5가지의 event listener가 존재한다(이벤트 종류는 각각 touch, keyboard, accleration, mouse, custom). event listener는 각 이벤트를 수신하는 역할과 함께 해당 이벤트를 수신하여 처리하는 일련의 코드를 캡슐화하는 역할을 해준다.

  • Event Dispatcher 등록된 Event listener에게 이벤트의 발생을 알려주는 역할을 한다.

  • Event objects 발생한 이벤트에 대한 정보를 담고 있다.

    여기서 지금 받고자 하는 입력이 keyboard입력이므로 우선 Keyboard Event에 대한 listener를 만들어야한다. (이 과정은 일반적으로 event 처리를 하고자 하는 Node를 상속받은 클래스의 init함수에서 이루어진다.

auto keyListener = EventListenerKeyboard::create();
keyListener->onKeyPressed = CC_CALLBACK_2(SampleClass::onKeyPressed, this);
keyListener->onKeyReleased = CC_CALLBACK_2(SampleClass::onKeyReleased, this);

여기서 CC_CALLBACK_2는 방금 선언한 event 처리 함수를 콜백으로 어떻게 호출할지 그 형태를 만들어주는 매크로다. CC_CALLBACK_0부터 CC_CALLBACK_3까지 종류가 다양한데, 그에 대한 설명은http://injakaun.blog.me/220057713284 이 곳에 잘 설명되어 있다.

일단 위 과정을 통해 keyListener를 만들었으니 이제 이 Listener를 Distpatcher에 등록하여 실제로 이벤트 발생시에 Listener가 그 이벤트를 수신하여 그에 따른 처리를 해줄 수 있게 해 주어야 한다.

_eventDispatcher->addEventListenerWithSceneGraphPriority(keyListener, this);

EventListener를 추가하는 것에는 크게 두 가지 방법이 있는데, 그 두가지는 각각addEventListenerWithSceneGraphPriorityaddEventListenerWithFixedPriority이다. 첫번째 함수는 해당 event를 처리하는 콜백 함수를 불러올 때 각 노드가 그려지는 순서와 동일한 순서대로 호출이 된다. 예를 들어 A,B 라는 클래스가 있고 이 둘이 모두 keyboardPressed 이벤트를 수신한다고 하자. 둘 모두 addEventListenerWithSceneGraphPriority 함수로 키보드 이벤트 처리 콜백 함수를 등록했다고 할 때, A가 B보다 먼저 그려진다면(Z-order가 더 높다면) 이벤트 처리 콜백 함수도 A의 콜백 함수가 B의 콜백보다 먼저 호출된다. 반면 FixedPriority같은 경우 2번째 인자로 넘긴 숫자에 따른 고정된 우선 순위에 따라 콜백 함수를 호출하게 된다.

  • 샘플 예제 코드

    /// ESC키가 눌렸을 때 프로그램을 종료시키는 Node의 예제 코드입니다.
    
    #include "cocos2d.h"
    
    SampleClass.h
    ------------
    
    class SampleClass : cocos2d::Node
    {
    public:
      virtual bool init();
      void onKeyPressed(cocos2d::EventKeyboard::KeyCode keyCode, cocos2d::Event* event);
      CREATE_FUNC(SampleClass);
    }
    
    SampleClass.cpp
    ------------
    
    #include "SampleClass.h"
    
    USING_NS_CC
    
    bool SampleClass::init()
    {
      if(!Node::init())
      {
          return false;
      }
    
      auto keyListener = EventListenerKeyboard::create();
      keyListener->onKeyPressed = CC_CALLBACK_2(SampleClass::onKeyPressed, this);
      _eventDispatcher->addEventListenerWithSceneGraphPriority(keyListener, this);
    
      return true;
    }
    
    void SampleClass::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event)
    {
      if(keyCode == EventKeyboard::KeyCode::KEY_ESCAPE)
      {
          Director::getInstance()->end();
      }
    }
    

    keyboard 입력의 경우 key를 꾹 누르고 있는 경우에 대한 처리는 해주지 않으므로, 이런 경우는 onKeyPressed함수와 onKeyReleased함수를 적절히 조합하여 스스로 구현해야 한다.

1-2. mouse 입력

mouse 입력도 keyboard 입력과 거의 유사하다.

mouse 입력 처리 함수 원형

 void onMouseMove(Event *event);
 void onMouseDown(Event *event);
 void onMouseUp(Event *event);
 void onMouseScroll(Event *event);

각각 마우스가 움직일 때, 클릭했을 때, 클릭했다 뗐을 때, 마우스 휠을 스크롤 했을 때 호출되는 함수들이다. Keyboard와는 다르게 인자를 하나만 받는다.

  • mouse 입력 처리 함수 예제

    void SampleClass::onMouseDown(Event *event)
    {
      EventMouse* e = (EventMouse*)event;
    
      e->getMouseButton();
    }
    
    void SampleClass::onMouseMove(Event *event)
    {
      EventMouse* e = (EventMouse*)event;
    
      CCLOG("x : %f",event->getCursorX()); // mouse X 좌표
      CCLOG("y : %f",event->getCursorY()); // mouse Y 좌표
    }
    
    void SampleClass::onMouseScroll(Event *event)
    {
      EventMouse* e = (EventMouse*)event;
    
      CCLOG("x : %f",event->getScrollX()); // X 방향 스크롤 값
      CCLOG("y : %f",event->getScrollY()); // Y 방향 스크롤 값
    }
    

    EventMouse에서 getMouseButton()을 하면 정수값이 반환된다. 이 정수값의 의미는 cocos2d-x 내부에서 다음과 같이 정의되어있다.

#define MOUSE_BUTTON_LEFT       0
#define MOUSE_BUTTON_RIGHT      1
#define MOUSE_BUTTON_MIDDLE     2
#define MOUSE_BUTTON_4          3
#define MOUSE_BUTTON_5          4
#define MOUSE_BUTTON_6          5
#define MOUSE_BUTTON_7          6
#define MOUSE_BUTTON_8          7

또 유의해야할 점은 cocos2d-x의 좌표계가 아래->위로 가면 y좌표가 증가하는 반면 EventMouse에서 getCursorY()함수를 통해 얻은 y값은 위에서 아래로 가면 y좌표가 증가하는 반전된 좌표축을 이용한다는 사실을 유의해둬야 한다.

  • mouse 입력 처리 함수 등록 예제

    auto mouseListener = EventListenerMouse::create();
    mouseListener->onMouseMove = CC_CALLBACK_1(MouseTest::onMouseMove, this);
    mouseListener->onMouseDown = CC_CALLBACK_1(MouseTest::onMouseDown, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);
    

    mouse 입력 처리 등록도 keyboard랑 별반 다를 것은 없으나, mouse의 경우 CC_CALLBACK_2가 아니라 CC_CALL_BACK_1을 이용한다.

    keyboard, mouse를 포함한 모든 이벤트에 대한 내용은 http://www.cocos2d-x.org/wiki/EventDispatcher_Mechanism 문서에 자세히 잘 정리되어 있으니 이 문서를 참조하는 것도 좋다.

2. 자동 메모리 관리 시스템

cocos2d-x는 원래 Obj-c 로 작성되어있던 cocos2d를 크로스 플랫폼이 가능하도록 C++로 포팅한 것이기 때문에 메모리 관리 시스템 역시 Obj-c의 참조 횟수(Reference count) 기반 메모리 관리 시스템을 거의 그대로 따라 구현했다.

cocos2d-x의 메모리 관련 핵심 함수는 autorelease()함수와 retain(), release()함수이다.

일반적으로 CREATE_FUNC 매크로를 이용해 만들어지는 해당 클래스의 create함수는 내부적으로 해당 객체를 동적으로 할당한 다음 생성된 객체의 autorelease()함수를 호출하고 해당 객체의 포인터를 리턴한다. 이때 autorelease() 함수는 자신의 레퍼런스 카운트 값을 1 늘린 뒤 cocos2d-x의 메모리 관리 풀인 AutoreleasePool에 해당 객체를 저장시킨다. AutoreleasePool 컨테이너는 게임의 mainLoop가 돌 때 매 번 비워지며 이 때 배열에 저장된 모든 객체에 대해 release()함수를 호출한다.

release()함수는 자신의 레퍼런스 카운트 값을 1 감소시키며 레퍼런스 카운트 값이 0일 경우 스스로 메모리를 삭제한다.(delete this) retain()함수는 이와 반대로 자신의 레퍼런스 카운트 값을 1 증가시키는 역할을 한다.

따라서 create() 함수에 의해 생성된 클래스의 경우 특별히 레퍼런스 카운트 값을 증가시키지 않으면 다음 프레임에 자동으로 메모리가 해제된다. 자동으로 해제되지 않기를 원한다면 따로 retain()함수를 호출하여 레퍼런스 카운트 값을 증가시켜줘야 한다. 단, 이 경우 별도로 release() 함수를 호출해야만 해당 객체의 메모리가 할당 해제되므로 주의를 기울여 사용해야한다.

addChild함수의 경우 내부적으로 addChild함수의 인자로 넘어온 객체의 레퍼런스 카운트 값을 1 증가시킨다. 반대로 removeChild함수의 경우는 레퍼런스 카운트 값을 1 감소시키며, 부모가 release 될 때는 자식들도 모두 release된다.


'프로그래밍 > cocos2d-x' 카테고리의 다른 글

[cocos2d-x] dot(pixel art) 그래픽 쓸 때  (0) 2015.06.14