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

지난번 연재 7-1, 7-2와 이어집니다.

7.6.  Timer구현하기

게임을 함에 있어 긴장감이 필요하므로 제한시간을 구현하도록 하겠습니다.

 

타이머의 스프라이트를 추가하고 스프라이트에 ProgressTimer를 추가하여 에니메이션을 실행하도록 하겠습니다.

----------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);

 

    cocos2d::Sprite *_timerBG;

    cocos2d::ProgressTimer *_progressBar;

    void setTimer();

};

 

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

 

…생략…

 

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

bool GameScene::init()

{

 

…생략…

 

    setImages();

 

    setCountDown();

 

    //타이머 바 추가

    _timerBG = Sprite::create("gage_bg.png");

    _timerBG->setPosition(Point(winSize.width / 2, 365));

    this->addChild(_timerBG);

 

    auto progress_sprite = Sprite::create("gage_bar.png");

    _progressBar = ProgressTimer::create(progress_sprite);

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

 

    //프로그레스바가 변하는 형태

    _progressBar->setType(ProgressTimer::Type::BAR);

    //프로그레스바가 변하는 비율, Point(1, 0)의 경우 width만 변한다.

    _progressBar->setBarChangeRate(Point(1, 0));

    //프로그레스바가 변할 중심점 AnchorPoint와 비슷

    _progressBar->setMidpoint(Point(0, 0.5f));

    _progressBar->setPercentage(100);

 

    _timerBG->addChild(_progressBar);

 

    return true;

}

 

…생략…

 

void GameScene::setCountDownEnd(Ref *object){

    _isCountDown = false;

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

 

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

 

    setTimer();

}

 

…생략…

 

void GameScene::setTimer(){

    _progressBar->runAction(ProgressFromTo::create(5, 100, 0));

}

 

ProgressTimer는 스프라이트를 파라메터로 받아 해당 스프라이트를 시간에 따라 보여지는 것을 제어할 수 있는 클레스입니다.

 

ProgressTimer의 속성을 제어하여 여러가지 모양의 ProgressTimer를 만들 수 있습니다.

 

카운트 다운이 끝나면 setTimer() 함수를 호출하여 ProgressTimer에 ProgressFromTo()라는 에니메이션을 추가하였습니다. ProgressFromTo()는 시간과 초기값, 끝나는 값을 파라메터로 지정하면 해당 속성값에 해당하는 에니메이션이 실행됩니다.

 

디버거를 실행하여 ProgressTimer를 확인하도록 합니다.

 

Figure 7‑17 실행화면

 

ProgressTimer를 추가했으면 시간을 타이머에 표시하도록 하겠습니다.

ProgressTimer에 5초간 에니메이션을 실행하도록 하였으니 5초동안 시간이 갱신되도록 수정하겠습니다.

----------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);

 

    cocos2d::Sprite *_timerBG;

    cocos2d::ProgressTimer *_progressBar;

    void setTimer();

 

    cocos2d::LabelTTF *_labelCountDown;

    float _countDownTimer;

    void updateTimer(float time);

};

 

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

 

…생략…

 

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

bool GameScene::init()

{

 

…생략…

 

    //프로그레스바가 변하는 형태

    _progressBar->setType(ProgressTimer::Type::BAR);

    //프로그레스바가 변하는 비율, Point(1, 0)의 경우 width만 변한다.

    _progressBar->setBarChangeRate(Point(1, 0));

    //프로그레스바가 변할 중심점 AnchorPoint와 비슷

    _progressBar->setMidpoint(Point(0, 0.5f));

    _progressBar->setPercentage(100);

 

    _timerBG->addChild(_progressBar);

 

    _labelCountDown = LabelTTF::create("5.0", "Arial", 20);

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

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

    _timerBG->addChild(_labelCountDown);

 

    return true;

}

 

…생략…

 

void GameScene::setTimer(){

    _progressBar->runAction(ProgressFromTo::create(5, 100, 0));

 

    _countDownTimer = 5.0f;

    schedule(schedule_selector(GameScene::updateTimer));

}

 

void GameScene::updateTimer(float time){

    _countDownTimer -= time;

    if (_countDownTimer < 0){

        _countDownTimer = 0;

        unschedule(schedule_selector(GameScene::updateTimer));

        _labelCountDown->setString("0.0");

    }

 

    log("_countDownTimer : %f", _countDownTimer);

    char str[10] = { 0 };

    sprintf(str, "%2.1f", _countDownTimer);

    _labelCountDown->setString(str);

}

 

LabelTTF를 이용하여 _labelCountDown을 생성해 숫자를 표시하고 시간을 체크하는 _countDownTimer라는 변수를 생성하였습니다.

schedule()를 이용하여 updateTimer()를 지속적으로 호출하였고 _countDownTimer와 schedule()로 넘어오는 시간값을 계산해 소수점 한자리까지 _labelCountDown의 Label에 표시하였습니다.

 

0초가 되면 unscheduled()을 호출해 스케쥴을 정지시켰습니다.

 

디버거를 실행해 기능이 동작하는지 확인합니다.

 

 

Figure 7‑18 실행화면

 

7.7.  Score구현

 

Score를 시간의 경과에 따라 유동적으로 구현하도록 하겠습니다.

----------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);

 

    cocos2d::Sprite *_timerBG;

    cocos2d::ProgressTimer *_progressBar;

    void setTimer();

 

    cocos2d::LabelTTF *_labelCountDown;

    float _countDownTimer;

    void updateTimer(float time);

 

    int _score;

};

 

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

 

…생략…

 

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

bool GameScene::init()

{

 

…생략…

 

    _labelCountDown = LabelTTF::create("5.0", "Arial", 20);

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

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

    _timerBG->addChild(_labelCountDown);

 

    //score 초기화

    _score = 0;

 

    return true;

}

 

…생략…

 

 

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){

        //최고 10000점, 시간의 경과에 따라 점수가 낮아진다.

        _score = (_countDownTimer / 5.0f) * 10000;

        char scoreChar[50];

        sprintf(scoreChar, "%d", _score);

        _labelScore->setString(scoreChar);

 

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

    }

    else{

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

    }

}

 

_countDownTimer에 남은시간이 들어있으니 이 시간을 전체 시간인 5초를 적용하여 점수를 획득합니다.

 

이점수를 labelScore에 setString()을 이용해 수정하였습니다.

 

디버거를 실행해 확인합니다.

 

Figure 7‑19 실행화면

 

이미지를 선택한 시점에 남은 시간에 따라 점수가 추가됩니다.

 

선택하였을 때 timer가 계속 떨어지므로 이미지를 선택하면 타이머가 정지하도록 수정하겠습니다.

----------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();

 

    unschedule(schedule_selector(GameScene::updateTimer));

    _progressBar->stopAllActions();

 

    if (_targetNo == clickNo){

        //최고 10000점, 시간의 경과에 따라 점수가 낮아진다.

        _score = (_countDownTimer / 5.0f) * 10000;

        char scoreChar[50];

        sprintf(scoreChar, "%d", _score);

        _labelScore->setString(scoreChar);

 

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

    }

    else{

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

    }

}

 

unschedule을 이용해 schedule을 중지하고, _progressBar의 Action을 정지하도록 했습니다.

 

 

Figure 7‑20 실행화면

이미지를 선택하면 시간이 더 이상 흐르지 않는 것을 확인할 수 있습니다.

 

7.8.  게임오버 기능 구현

 

시간이 다되거나 이미지를 선택한 경우 획득한 스코어를 표시하고 StartScene으로 이동하도록 하겠습니다.

----------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);

 

    cocos2d::Sprite *_timerBG;

    cocos2d::ProgressTimer *_progressBar;

    void setTimer();

 

    cocos2d::LabelTTF *_labelCountDown;

    float _countDownTimer;

    void updateTimer(float time);

 

    int _score;

 

    void gameOver();

};

 

----------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();

 

    unschedule(schedule_selector(GameScene::updateTimer));

    _progressBar->stopAllActions();

 

    if (_targetNo == clickNo){

        //최고 10000점, 시간의 경과에 따라 점수가 낮아진다.

        _score = (_countDownTimer / 5.0f) * 10000;

        char scoreChar[50];

        sprintf(scoreChar, "%d", _score);

        _labelScore->setString(scoreChar);

 

        char resultChar[100];

        sprintf(resultChar, "성공하였습니다.\n\n%d점을 획득하였습니다.", _score);

        this->addChild(TextPopup::create(resultChar, this, callfunc_selector(GameScene::gameOver), false));

    }

    else{

        char resultChar[100];

        sprintf(resultChar, "실패하였습니다.\n\n%d점을 획득하였습니다.", _score);

        this->addChild(TextPopup::create(resultChar, this, callfunc_selector(GameScene::gameOver), false));

    }

}

 

…생략…

 

void GameScene::updateTimer(float time){

    _countDownTimer -= time;

    if (_countDownTimer < 0){

        _countDownTimer = 0;

        unschedule(schedule_selector(GameScene::updateTimer));

        _labelCountDown->setString("0.0");

 

        char resultChar[100];

        sprintf(resultChar, "실패하였습니다.\n\n%d점을 획득하였습니다.", _score);

        this->addChild(TextPopup::create(resultChar, this, callfunc_selector(GameScene::gameOver), false));

    }

 

    log("_countDownTimer : %f", _countDownTimer);

    char str[10] = { 0 };

    sprintf(str, "%2.1f", _countDownTimer);

    _labelCountDown->setString(str);

}

 

void GameScene::gameOver(){

    //버튼 선택으로 호출된 함수가 아니며, 받은 Object를 이용하여 처리하는 로직이 없으므로 NULL을 파라메터로 넣었습니다.

    onClickBack(NULL);

}

 

점수를 계산하고 텍스트를 수정하여 TextPopup의 파라메터로 수정하였고, OK버튼 선택시 콜백메소드로 GameOver()를 호출하도록 하였습니다.

 

디버거를 실행하여 3가지 상황을 테스트 해봅니다.

Figure 7‑21 이미지를 정확히 선택한 경우

 

Figure 7‑22 이미지를 찾는데 실패한 경우

 

Figure 7‑23 시간이 다 된 경우

 

세가지 상황에서 모두 동작하는 것을 확인할 수 있습니다.

 

7.9.  Stage 구현

[7.1.8]에서 게임오버에 대한 기능을 추가하였습니다.

 

게임에 성공했을 경우 다음스테이지로 이동하게 구현하면서 3스테이지까지 구현하도록 하겠습니다.

----------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);

 

    cocos2d::Sprite *_timerBG;

    cocos2d::ProgressTimer *_progressBar;

    void setTimer();

 

    cocos2d::LabelTTF *_labelCountDown;

    float _countDownTimer;

    void updateTimer(float time);

 

    int _score;

 

    void gameOver();

 

    int _stage;

    void nextStage();

};

 

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

 

…생략…

 

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

bool GameScene::init()

{

 

…생략…

 

    _labelCountDown = LabelTTF::create("5.0", "Arial", 20);

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

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

    _timerBG->addChild(_labelCountDown);

 

    //score 초기화

    _score = 0;

 

    //stage 초기화

    _stage = 1;

 

    return true;

}

 

…생략…

 

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();

 

    unschedule(schedule_selector(GameScene::updateTimer));

    _progressBar->stopAllActions();

 

    if (_targetNo == clickNo){

        //최고 10000점, 시간의 경과에 따라 점수가 낮아진다.

        //기존의 _score에 현재점수를 더해준다.

        _score = _score + (_countDownTimer / 5.0) * 10000;

        char scoreChar[50];

        sprintf(scoreChar, "%d", _score);

        _labelScore->setString(scoreChar);

 

        char resultChar[100];

        sprintf(resultChar, "성공하였습니다.\n\n%d점을 획득하였습니다.", _score);

 

        if (_stage < 3){

            _stage++;

            //게임에 성공했고 아직 스테이지가 남았으므로 nextStage를 호출

            this->addChild(TextPopup::create(resultChar, this, callfunc_selector(GameScene::nextStage), false));

        }

        else{

            //게임에 성공했으나 3스테이지까지 도달했으므로 gameOver를 호출

            this->addChild(TextPopup::create(resultChar, this, callfunc_selector(GameScene::gameOver), false));

        }

    }

    else{

        char resultChar[100];

        sprintf(resultChar, "실패하였습니다.\n\n%d점을 획득하였습니다.", _score);

        this->addChild(TextPopup::create(resultChar, this, callfunc_selector(GameScene::gameOver), false));

    }

}

 

…생략…

 

void GameScene::nextStage(){

    setTarget();

    setImages();

    setCountDown();

 

    //타이머 바 초기화

    _progressBar->setPercentage(100);

    _labelCountDown->setString("5.0");

}

 

nextStage()를 추가하였고 _score에 점수를 넣을 때 기존의 점수에 더해서 추가하도록 수정하였습니다.

 

_stage가 3보다 작으면 nextStage()를 호출하고 아니면 gameOver()를 호출하도록 구현하였습니다.

 

디버거를 실행하여 확인하도록 합니다.

  

Figure 7‑24 1~3 스테이지까지 진행하면서 점수가 쌓이는 화면

 

여기까지 캐릭터 이미지를 생성하고 생성된 이미지를 이용하여 게임을 만들어보았습니다.

기존에 주어진 이미지가 아닌 본인이 직접 생성한 이미지를 이용하여 게임을 진행하면 처음에 이미지 리소스를 추가하여 진행한 것보다 더 많은 수의 이미지를 적용할 수 있고 유저가 선택한 이미지로 게임을 할 수 있게 됩니다.

 

지금까지 Cocos2d-x로 게임을 만들기 위한 기본이 되는 클래스들을 사용하여 게임을 만들어보았습니다.

이러한 방식으로 다양하게 응용하여 여러종류의 게임을 만들어 볼 수 있습니다.

 

Android 와 iOS로 포팅을 하기 위해서는 부록을 참고하시기 바랍니다.


Prev | Next