이번엔 누를 수 있는 [캐릭터 만들기] 버튼을 구현해보겠습니다. 뒤에 다른 버튼을 추가할 것이므로 버튼의 이름은 button1이라고 하죠. StartScene.cpp 파일에 아래와 같이 버튼 부분을 구현하도록 합니다.
----------StartScene.cpp----------
//this에 character를 자식 노드로 추가하였습니다.
this->addChild(character);
//버튼 추가
auto button1 = MenuItemImage::create("title_btn_1.png", "");
button1->setPosition(Point(winSize.width / 2, 100));
//생성된 버튼을 메뉴에 추가한다.
auto menu = Menu::create(button1, NULL);
menu->setPosition(Point::ZERO);
this->addChild(menu);
return true;
}
이번에 새로 사용한 클래스는 MenuItemImage와 Menu입니다. MenuItemImage는 MenuItem, 즉 메뉴 항목의 한 종류이며, Cocos2d-x에서는 버튼을 메뉴 항목이라고 부른다고 이해하면 됩니다. 이러한 버튼은 Menu 클래스에 담아야만 화면에 붙일 수 있습니다.
MenuItem에는 다시 MenuItemSprite 등의 하위 클래스가 있고, MenuItemSprite 아래에 MenuItemImage가 있습니다. 간단히 말해 MenuItemImage는 파일명을 받아 MenuItem을 생성하고, MenuItemSprite는 Sprite를 받아 MenuItem을 생성합니다.
MenuItem은 Menu에 추가되어야 정상적으로 동작합니다. 따라서 MenuItem을 생성한 다음에는 Menu도 생성하고 앞에서 생성한 MenuItem을 반드시 Menu에 추가하여 사용하도록 합니다.
Menu의 create()에는 여러 개의 MenuItem이 들어갈 수 있습니다. 따라서 마지막 항목을 가리키기 위해 마지막에 NULL을 입력하는 습관을 들여야 합니다. 만약 NULL로 끝나지 않을 경우 에러가 발생하거나, 에러가 발생하지 않더라도 iOS에서는 동작하나 안드로이드에서는 오작동하는 등 디버깅이 어려워질 수도 있습니다. 여러 개의 항목을 Menu에 한 번에 담기 위해선 create(Item1, Item2, item3, …, NULL) 이런 식으로 사용할 수 있습니다.
Menu를 붙일 때는 setPoint(Point::ZERO)를 꼭 해주어야 터치 좌표가 정확하게 나옵니다(Point::ZERO는 Point(0, 0)과 동일한 의미입니다). 개별 버튼의 위치는 setPosition()에서 지정합니다.
이제 디버거를 실행해보도록 합시다. [캐릭터 만들기]라는 버튼이 생성되었습니다.
Figure 5‑17 버튼 추가 후 실행 화면
하지만 이 버튼이 눌려지는 것인지 아닌지는 알 수 없습니다. 따라서 버튼을 눌렀을 때 눌렸는지 알 수 있도록 변경해보도록 하겠습니다. 다음과 같이, 앞에서 추가한 코드 중 MenuItemImage의 create()에 두 번째 매개변수를 추가합니다.
//버튼 추가
auto button1 = MenuItemImage::create("title_btn_1.png", "title_btn_1_on.png");
button1->setPosition(Point(winSize.width / 2, 100));
두 번째 매개변수는 selectedImage, 즉 눌렸을 때 이미지입니다. 참고로 첫 번째 매개변수는 normalImage라고 합니다. 이렇게 수정하고 디버거를 실행하고, 버튼을 눌러보면 이미지가 바뀌는 것을 확인할 수 있습니다.
Figure 5‑18 눌렀을 때 이미지 추가 후
MenuItemImage의 create()에는 normalImage와 selectImage 외에도 disabledImage를 매개변수로 추가할 수 있습니다. disabledImage란 버튼이 사용 가능하지 않을 때(누를 수 없을 때) 표시될 이미지를 뜻합니다. 다음과 같이, 앞의 코드에 매개변수 두 개를 추가하고, 그 아래 한 행을 추가합니다.
//버튼 추가
auto button1 = MenuItemImage::create("title_btn_1.png", "title_btn_1_on.png", "title_btn_1_dis.png", [&](Ref *sender) {});
button1->setEnabled(false);
button1->setPosition(Point(winSize.width / 2, 100));
여기서 MenuItemImage의 네 번째 매개변수 [&](Ref *sender) {}를 콜백 메서드라고 합니다. 이 버튼을 눌렀을 때 자동으로 실행되는 메서드라고 이해하면 됩니다. 여기에서는 별도의 메서드를 생성하지 않고 이름 없는 메서드를 즉석에서 생성하여 등록했습니다. 이를 람다lambda 방식이라고 부릅니다. 람다는 이름없는 메서드입니다. 쉽게 말해 따로 선언하지 않고 바로 등록하여 사용하는 방식입니다. 특정 작업을 여러 번 사용하지 않는 경우 람다를 사용하는 것이 좋지만, 여러 번 사용한다면 따로 메서드를 생성하는 것이 좋습니다. 람다나 콜백 메서드에 대해서는 다음 절에서 설명하기로 하고 일단 넘어가겠습니다.
그 아래 행에 추가한 setEnabled() 메서드는 버튼을 누를 수 있게 할지 없게 할지를 설정하는 메서드입니다. 버튼은 기본값으로 누를 수 있지만 이 메서드에 false를 넣으면 누를 수 없게 되고, 따라서 표시되는 이미지도 nomalImage가 아니라 disabledImage로 바뀝니다. 여기에서는 누를 수 없는 버튼 이미지가 표시되는지 확인하기 위해 false를 넣었습니다.
이 코드를 실행해보면 [캐릭터 만들기] 버튼이 title_btn_1_dis.png로 바뀌어 있고, 해당 버튼은 누를 수 없습니다.
Figure 5‑19 사용 불가 이미지 추가 후
이번에는 [게임 하기] 버튼을 아래에 추가해볼 것입니다. 그 전에 앞에서 추가했던 button1->setEnabled(false); 행은 삭제합시다. 기본값이 true이므로 이 행을 지우면 버튼은 누를 수 있는 버튼으로 바뀔 것입니다. 다음과 같이 새로운 버튼 button2의 코드를 추가하고, Menu에도 이 버튼을 추가해줍니다.
----------StartScene.cpp----------
//버튼 추가
auto button1 = MenuItemImage::create("title_btn_1.png", "title_btn_1_on.png", "title_btn_1_dis.png", [&](Ref *sender) {});
button1->setPosition(Point(winSize.width / 2, 100));
auto button2 = MenuItemImage::create("title_btn_2.png", "title_btn_2_on.png");
button2->setPosition(Point(winSize.width / 2, 40));
//생성된 버튼을 메뉴에 추가한다.
auto menu = Menu::create(button1, button2, NULL);
menu->setPosition(Point::ZERO);
this->addChild(menu);
return true;
}
button1 부분에서 setEnabled() 행을 삭제했고, button2를 생성하는 코드를 추가했으며, Menu의 create()에 button2를 추가해주었습니다. 만약 버튼을 생성했다고 해도 Menu에 추가하지 않으면 화면에 보이지 않습니다. 버튼을 생성했는데 화면에 보이지 않는다면 Menu에 추가하지 않았는지 확인해봅시다.
이제 디버거를 실행해볼까요?
Figure 5‑20 버튼 두 개 추가 후
[게임 하기] 버튼이 추가되었고, 두 버튼 모두 눌렀을 때 이미지가 변하는 것을 볼 수 있어야 정상입니다.
지금은 버튼이 생성되었고 눌러지기만 할 뿐이지 눌렀을 때 이벤트가 발생하진 않습니다. button1에는 람다 콜백 메서드를 등록했지만 하는 일이 없으며, button2에는 아무 콜백 메서드도 등록하지 않았죠. 따라서 버튼을 눌러도 아무 일도 일어나지 않는 것입니다.
그럼 이제 이벤트가 발생되게 해보겠습니다. StartScene.h 파일을 열고, button2를 눌렀을 때 실행될 콜백 메서드를 선언하겠습니다.
----------StartScene.h----------
// implement the "static create()" method manually
CREATE_FUNC(StartScene);
void onClickButton2(Ref *object);
};
단순히 생성자 마지막에 한 줄 추가한 것이 전부입니다. 다음으로 StartScene.cpp를 열고 아래와 같이 파일 제일 끝에 콜백 메서드를 정의합니다.
----------StartScene.cpp----------
return true;
}
void StartScene::onClickButton2(Ref *object) {
log("onClickButton2");
}
이것이 콜백 메서드를 선언하고 정의하는 일반적 방법입니다. 이 콜백 메서드가 하는 일은 단순히 로그를 출력하는 것뿐입니다(log()는 콘솔의 출력창에 메시지를 출력하는 간단한 메서드입니다).
그럼 이 콜백 메서드를 button2에 등록하려면 어떻게 할까요? 앞 절에서 이미 살펴봤습니다. 바로 MenuItemImage를 생성할 때 추가 매개변수로 넣으면 됩니다. 이때 알아야 할 문법을 잠깐 살펴보겠습니다.
cocos2d-x가 3.0으로 버전업되면서 메서드 부분에도 많은 변화가 생겼습니다. 다음과 같이 네 가지의 콜백 메서드 등록하는 방법이 있습니다.
// in v2.1
CCMenuItemImage *item = CCMenuItemImage::create("normal.png", this, menu_selector(MyClass::callback));
// in v3.0 (short version)
auto item = MenuItemImage::create("normal.png", CC_CALLBACK_1(MyClass::callback, this));
// in v3.0 (long version)
auto item = MenuItemImage::create("normal.png", std::bind(&MyClass::callback, this, std::placeholders::_1));
// in v3.0 you can use lambdas or any other "Function" object
auto item = MenuItemImage::create("normal.png",
[&](Ref *sender) {
// do something. Item "sender" clicked
});
① 방식은 menu_selector()를 이용한 방법입니다. 2.x 버전에서 사용하던 방식이므로 몰라도 괜찮습니다. 나머지는 3.0 버전에서 사용할 수 있는 방식인데, 이 중 ③ long 버전은 따로 알 필요가 없습니다. ② short 버전과 동일하기 때문입니다. 따라서 short 버전과 ④ 람다에 대해서만 알아보도록 하겠습니다.
// in v3.0 (short version)
auto item = MenuItemImage::create("normal.png", CC_CALLBACK_1(MyClass::callback, this));
short 버전의 콜백 메서드 등록부는 CC_CALLBACK_1(selector, target)으로 이루어집니다. CC_CALLBACK_1(__selector__, __target__)는 std::bind(&__selector__, __target__)와 동일한 의미입니다. selector는 호출될 메서드를 뜻하고 target은 위치를 뜻합니다. target에 this라고 입력하면 자기 자신을 뜻합니다.
// in v3.0 you can use lambdas or any other "Function" object
auto item = MenuItemImage::create("normal.png",
[&](Ref *sender) {
// do something. Item "sender" clicked
});
람다는 미리 정의한 콜백 메서드를 등록하는 게 아니라 콜백을 즉석에서 입력해버리는 방식입니다. 예제의 경우, 버튼, 즉 sender가 클릭되면 { // do something… } 에 정의된 코드를 실행하라는 의미입니다.
두 가지 방법은 사용 환경에 따라 어느 한 쪽이 편리할 수 있습니다. selector를 사용하는 방식은 동일한 메서드를 서로 다른 객체에서 여러 번 콜백 메서드로 등록할 경우 편리합니다. 람다 방식의 경우, 다른 곳에서 사용되지 않을 간단한 동작을 하는 메서드를 선언 없이 사용할 수 있으므로 코드 작성이 편리해질 수 있습니다. 상황에 맞추어 두 가지 방식 모두 편리하게 사용하면 됩니다.
3.0의 콜백 메서드 등록 방법을 보면 selectImage나 disabledImage 매개변수를 지정하지 않고 바로 콜백 메서드를 매개변수로 적은 것을 볼 수 있습니다. 콜백 메서드 등록을 위해 필요한 매개변수는 normalImage 하나뿐이며, 나머지는 선택적입니다.
앞에서 button1의 람다 내부에는 아무것도 넣지 않았습니다. button1의 람다에도 로그 메서드를 추가하고, button2에는 방금 정의한 콜백 메서드를 등록해보겠습니다.
----------StartScene.cpp----------
//버튼 추가
auto button1 = MenuItemImage::create("title_btn_1.png", "title_btn_1_on.png", "title_btn_1_dis.png", [&](Ref *sender) { log("onClickButton1"); });
button1->setPosition(Point(winSize.width / 2, 100));
auto button2 = MenuItemImage::create("title_btn_2.png", "title_btn_2_on.png", CC_CALLBACK_1(StartScene::onClickButton2, this));
button2->setPosition(Point(winSize.width / 2, 40));
button1의 람다에는 로그 메서드를 추가했고, button2에는 short 버전의 방식으로 콜백 메서드를 등록하였습니다. button2는 별도의 disabledImage를 지정하지 않았습니다. 이제 디버거를 실행한 다음 각 버튼을 눌러보고, 로그가 출력되는지 확인해봅시다. 로그는 출력창에서 볼 수 있습니다.
Figure 5‑21 로그 확인
디버깅 시 출력창이 보이지 않으면 ‘보기’ 메뉴에서 ‘출력’을 선택하면 됩니다.