7.    카드 형식 게임 <정해진 그림 찾기> 구현

지난번 연재 7-1과 이어집니다.

7.4.  이미지 선택기능 구현하기(게임룰 구현)

이미지를 선택하여 타겟 이미지와 같은이미지인지 체크해보겠습니다.

 

GameScene을 아래와 같이 수정합니다.

 

----------GameScene.h----------

#include "cocos2d.h"

 

class GameScene : public cocos2d::Layer

{

public:

    // there's no 'id' in cpp, so we recommend returning the class instance pointer

    static cocos2d::Scene* createScene();

 

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone

    virtual bool init();

 

    // implement the "static create()" method manually

    CREATE_FUNC(GameScene);

 

    cocos2d::LabelTTF * _labelScore;

 

    void onClickBack(Ref *object);

 

    cocos2d::Sprite* _targetBack;

    void setTarget();

 

    cocos2d::LayerColor* _imagesBack;

    void setImages();

 

    //카운트 다운중이면 터치를 취소하기 위한 변수

    bool _isCountDown;

    void setCountDown();

    //카운트 다운이 끝나는 callback

    void setCountDownEnd(Ref *object);

 

    int _targetNo;

    void onClickCard(Ref *object);

};

 

----------gameScene.cpp----------

#include "GameScene.h"

#include "DatabaseManager.h"

#include "GalleryScene.h"

#include "TextPopup.h"

 

USING_NS_CC;

 

…생략…

 

void GameScene::setTarget(){

    if (_targetBack)

        _targetBack->removeFromParentAndCleanup(true);

 

    character *item = DatabaseManager::getInstance()->selectRandomGalleryDB();

 

    //타겟의 번호를 넣는다.

    _targetNo = item->no;

 

    _targetBack = Sprite::create("s_bg_1.png");

    auto head = GalleryScene::getImage("TB_FACE", item->headNo, item->headColorNo);

    auto hair1 = GalleryScene::getImage("TB_HAIR1", item->hair1No, item->hair1ColorNo);

    auto hair2 = GalleryScene::getImage("TB_HAIR2", item->hair2No, item->hair2ColorNo);

    auto eye = GalleryScene::getImage("TB_EYE", item->eyeNo, item->eyeColorNo);

    auto mouth = GalleryScene::getImage("TB_MOUTH", item->mouthNo, item->mouthColorNo);

    auto etc = GalleryScene::getImage("TB_ETC", item->etcNo, item->etcColorNo);

    auto bg = GalleryScene::getImage("TB_BG", item->bgNo, item->bgColorNo);

 

    //getImage()에서 setPosition이 되어있기에 따로 지정하지 않아도 된다.

    _targetBack->addChild(bg, 0);

    _targetBack->addChild(hair2, 1);

    _targetBack->addChild(head, 2);

    _targetBack->addChild(eye, 3);

    _targetBack->addChild(mouth, 3);

    _targetBack->addChild(hair1, 4);

    _targetBack->addChild(etc, 5);

 

    auto frame = Sprite::create("box_2.png");

    frame->setPosition(Point(_targetBack->getContentSize().width / 2, _targetBack->getContentSize().height / 2));

    _targetBack->addChild(frame, 6);

 

    //50px 로 크기를 줄여줌

    float scale = 50 / _targetBack->getContentSize().width;

    _targetBack->setScale(scale);

 

    _targetBack->setPosition(Point(85, 410));

 

    this->addChild(_targetBack);

}

 

void GameScene::setImages(){

    _imagesBack->removeAllChildrenWithCleanup(true);

 

    auto galleryList = DatabaseManager::getInstance()->selectGalleryDB(true);

    int size = galleryList.size();

 

    Vector<MenuItem*> itemArray;

    for (int i = 1; i <= size; i++){

        character *item = galleryList.front();

 

        auto back = Sprite::create("s_bg_1.png");

        auto head = GalleryScene::getImage("TB_FACE", item->headNo, item->headColorNo);

        auto hair1 = GalleryScene::getImage("TB_HAIR1", item->hair1No, item->hair1ColorNo);

        auto hair2 = GalleryScene::getImage("TB_HAIR2", item->hair2No, item->hair2ColorNo);

        auto eye = GalleryScene::getImage("TB_EYE", item->eyeNo, item->eyeColorNo);

        auto mouth = GalleryScene::getImage("TB_MOUTH", item->mouthNo, item->mouthColorNo);

        auto etc = GalleryScene::getImage("TB_ETC", item->etcNo, item->etcColorNo);

        auto bg = GalleryScene::getImage("TB_BG", item->bgNo, item->bgColorNo);

 

        //getImage()에서 setPosition이 되어있기에 따로 지정하지 않아도 된다.

        back->addChild(bg, 0);

        back->addChild(hair2, 1);

        back->addChild(head, 2);

        back->addChild(eye, 3);

        back->addChild(mouth, 3);

        back->addChild(hair1, 4);

        back->addChild(etc, 5);

 

        auto frame = Sprite::create("box_face.png");

        frame->setPosition(Point(back->getContentSize().width / 2, back->getContentSize().height / 2));

        back->addChild(frame, 6);

 

        auto menuItem = MenuItemSprite::create(back, NULL, CC_CALLBACK_1(GameScene::onClickCard, this));

 

        //기능을 위해 번호를 넣어준다.

        menuItem->setTag(item->no);

 

        //55px 로 크기를 줄여줌

        float scale = 55 / back->getContentSize().width;

        menuItem->setScale(scale);

 

        int row = (i - 1) / 4; //0~3

        int col = (i - 1) % 4; //0~3

 

        menuItem->setAnchorPoint(Point(0, 1));

 

        //좌표를 동적할당

        float x = col * menuItem->getContentSize().width * scale + (col + 1) * 20;

        float y = _imagesBack->getContentSize().height - (row * menuItem->getContentSize().height * scale + (row + 1) * 10);

 

        menuItem->setPosition(Point(x, y));

 

        itemArray.pushBack(menuItem);

 

        galleryList.pop_front();

 

        auto blindImg = Sprite::create("box_back.png");

        blindImg->setPosition(Point(menuItem->getContentSize().width / 2, menuItem->getContentSize().height / 2));

        menuItem->addChild(blindImg);

 

        back->setScaleX(0);

        //MenuItem에 추가되는 sprite는 앵커포인트가 0, 0으로 추가된다.

        back->setAnchorPoint(Point(0.5f, 0.5f));

        //따라서 앵커포인트를 수정하고 위치를 수정해주었다.

        back->setPosition(Point(back->getContentSize().width / 2, back->getContentSize().height / 2));

 

        //카운트다운 시간인 3초간 지연시키고 가로Scale을 0으로 에니메이션

        blindImg->runAction(Sequence::create(DelayTime::create(3), ScaleTo::create(0.2f, 0, 1), NULL));

        //카운트다운 시간 + 이전 에니메이션 동작시간인 0.2 초를 더해준값만큼 딜레이

        back->runAction(Sequence::create(DelayTime::create(3 + 0.2f), ScaleTo::create(0.2f, 1, 1), NULL));

    }

 

    auto menu = Menu::createWithArray(itemArray);

    menu->setPosition(Point::ZERO);

    _imagesBack->addChild(menu);

}

 

…생략…

 

void GameScene::onClickCard(Ref *object){

    //카운트 다운중이면 return;

    if (_isCountDown)

        return;

 

    auto selectImg = (MenuItemSprite *)object;

    int clickNo = selectImg->getTag();

 

    if (_targetNo == clickNo){

        this->addChild(TextPopup::create("성공하였습니다.", this, NULL, false));

    }

    else{

        this->addChild(TextPopup::create("실패하였습니다.", this, NULL, false));

    }

}

 

타겟의 번호를 갖는 변수와 선택된 이미지의 콜백메소드를 등록하였고, 두 번호를 비교하여 선택된 이미지와 타겟 이미지가 같은지를 체크하여 이미 만들어둔 TextPopup을 이용해 결과를 보여주었습니다.

 

디버거를 실행하여 타겟 이미지를 선택하였을때와 타겟이미지가 아닌 이미지를 선택하였을 때 기능이 제대로 동작하는지 확인합니다.

  

Figure 7‑11 실행화면

 

7.5.  배경음악 / 효과음 추가

게임 화면이 심심하니 배경음악과 효과음을 추가해보겠습니다.

 

sound의 위치와 파일명을 확인하도록 합니다.

 

Figure 7‑12 사운드 파일 위치1

Figure 7‑13 사운드 파일 위치2

Resources의 내부의 sound폴더에 sound리소스가 들어있습니다.

 

사운드 리소스를 확인하고 GameScene에 아래와 같이 코드를 추가합니다.

----------GameScene.cpp----------

#include "GameScene.h"

#include "DatabaseManager.h"

#include "GalleryScene.h"

#include "TextPopup.h"

#include "SimpleAudioEngine.h"

 

USING_NS_CC;

 

…생략…

 

// on "init" you need to initialize your instance

bool GameScene::init()

{

    //////////////////////////////

    // 1. super init first

    if (!Layer::init())

    {

        return false;

    }

 

    //code here

 

    CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("sound/Pixel Peeker Polka - slower.mp3", true);

 

    Size winSize = Director::getInstance()->getOpenGLView()->getDesignResolutionSize();

 

    //배경

    auto bg = Sprite::create("game_bg.png");

    bg->setPosition(Point(winSize.width / 2, winSize.height / 2));

    this->addChild(bg);

 

    //타이틀

    auto title = Sprite::create("game_title.png");

    title->setPosition(Point(winSize.width / 2, winSize.height - 20));

    this->addChild(title);

 

    //돌아가기 버튼

    auto back = MenuItemImage::create("btn_back.png", "btn_back_on.png", CC_CALLBACK_1(GameScene::onClickBack, this));

    back->setPosition(Point(30, winSize.height - 20));

 

    auto menu = Menu::create(back, NULL);

    menu->setPosition(Point::ZERO);

    this->addChild(menu);

 

    //스코어 창

    auto score = Sprite::create("box_scroe.png");

    score->setPosition(Point(250, 410));

    this->addChild(score);

 

    //스코어 표시 Label

    _labelScore = LabelTTF::create("0", "arial", 20);

    _labelScore->setPosition(Point(score->getContentSize().width / 2, 22));

    //score Sprite에 추가해주었다.

    score->addChild(_labelScore);

 

    _targetBack = NULL;

    setTarget();

 

    _imagesBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, 340);

    _imagesBack->setPosition(Point(0, 0));   //LayerColor는 기본 AnchorPoint가 (0, 0)이다.

    this->addChild(_imagesBack);

 

    setImages();

 

    setCountDown();

 

    return true;

}

 

void GameScene::onClickBack(Ref* object)

{

    if (_isCountDown)

        return;

 

    CocosDenshion::SimpleAudioEngine::getInstance()->stopBackgroundMusic(true);

 

    log("onClickBack");

    Director::getInstance()->popScene();

}

 

사운드를 제어할 수 있는 SimpleAudioEngine을 이용하여 배경음악을 추가하였습니다.

두번째 파라메터는 Roof 즉 반복에 대한 옵션입니다. true는 반복되고 false는 1번만 실행됩니다.

onClickBack()함수 내부에서 배경음악을 정지시켰습니다.

 

이렇게 넣었을경우 게임화면에 들어가면 배경음악이 실행되고 onClickBack()함수가 동작되면 배경음악이 정지되도록 구현하였습니다.

 

여기서 스마트폰에서 고려해야될 부분은 아이폰과 안드로이드의 HOME버튼의 기능입니다.

 

HOME버튼을 선택하게되면 해당 OS의 런쳐로 돌아가게 되는데 이때 배경음악이 백그라운드로 동작되게 됩니다.

 

이부분에 대한 처리가 AppDelegate.cpp에 정의되어있으니 SimpleAudioEngine을 사용하였으면 아래와 같이 수정하도록 합니다.

----------AppDelegate.cpp----------

#include "AppDelegate.h"

#include "StartScene.h"

#include "SimpleAudioEngine.h"

 

USING_NS_CC;

using namespace CocosDenshion;

 

…생략…

 

// This function will be called when the app is inactive. When comes a phone call,it's be invoked too

void AppDelegate::applicationDidEnterBackground() {

    Director::getInstance()->stopAnimation();

 

    // if you use SimpleAudioEngine, it must be pause

    SimpleAudioEngine::getInstance()->pauseBackgroundMusic();

}

 

// this function will be called when the app is active again

void AppDelegate::applicationWillEnterForeground() {

    Director::getInstance()->startAnimation();

 

    // if you use SimpleAudioEngine, it must resume here

    SimpleAudioEngine::getInstance()->resumeBackgroundMusic();

}

 

AppDelegate.cpp의 하단을 보면 applicationDidEnterBackground()와 applicationWillEnterForeground()가 정의되어있습니다.

 

이부분에서 배경음악을 pause하고 resume하는 코드가 이미 추가되어있습니다.

주석처리가 되어있다면 주석을 해제하여야 합니다.

applicationDidEnterBackground()는 앱이 Background로 진입하면 호출되고 applicationWillEnterForeground()는 다시 앱이 실행되면 호출 됩니다.

 

applicationDidEnterBackground()와 applicationWillEnterForeground()의 기능을 이해하고 넘어가도록 합니다.

 

디버거를 실행하여 사운드가 제대로 나오는지 확인하도록 합니다.

Figure 7‑14 실행화면

 

배경음악이 추가된 것을 확인하였으면, 카운트다운이 완료되고 이미지가 오픈될 때 효과음을 추가하도록 하겠습니다.

----------GameScene.cpp----------

 

…생략…

 

void GameScene::setCountDownEnd(Ref *object){

    _isCountDown = false;

    ((Node*)object)->removeFromParentAndCleanup(true);

 

    CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/flip_sound.wav");

}

 

카운트 다운이 완료되고 호출되는 setCountDownEnd()함수에 효과음을 추가하였습니다.

 

디버거를 실행하여 사운드가 제대로 동작되는지 확인하도록 합니다.

 

Figure 7‑15 실행화면

카운트다운이 완료되고 효과음이 출력되었으면 이미지를 선택하였을때도 효과음을 추가도록 합니다.

----------GameScene.cpp----------

 

…생략…

 

void GameScene::onClickCard(Ref *object){

    //카운트 다운중이면 return;

    if (_isCountDown)

        return;

 

    CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/click_sound.wav");

 

    auto selectImg = (MenuItemSprite *)object;

    int clickNo = selectImg->getTag();

 

    if (_targetNo == clickNo){

        this->addChild(TextPopup::create("성공하였습니다.", this, NULL, false));

    }

    else{

        this->addChild(TextPopup::create("실패하였습니다.", this, NULL, false));

    }

}

 

이미지가 선택되면 호출되는 onClickCard()함수에 효과음을 추가하였습니다.

 

Figure 7‑16 실행화면

 

디버거를 실행하여 효과음이 나오는지 확인하도록 합니다.


Prev | Next