8. 북극곰 탈출 게임

지난번 연재 8-1, 8-2, 8-3, 8-4, 8-5와 이어집니다.

8.6. 게임 화면 만들기

8.6.8. 메뉴 기능 구현하기

Figure 41 실행화면

이렇게 갇혀버릴경우 재시작을 선택해야 하는 경우가 생깁니다.

 

재시작 기능을 위한 메뉴기능을 구현해보도록 하겠습니다.

 

메뉴버튼을 기존에 만들어둔 setMenuLayer() 메소드에서 구현하도록 하겠습니다.

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

#include "cocos2d.h"

#include "Polarbear.h"

 

USING_NS_CC;

 

class GameScene : public cocos2d::Layer

{

public:

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

    static cocos2d::Scene* createScene(Node *stageScene, int stage);

 

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

 

    LayerColor *_mapLayer;

    LayerColor *_menuLayer;

 

    void setMapLayer(int mapArr[4][5]);

    Menu* getMaptile(int row, int col, int type);

    void setMenuLayer();

 

    Sprite *getPenguinSprite();

    Sprite *getGoalFlagSprite();

 

    Polarbear *_polarbear;

 

    void onClickTile(Ref *object);

 

    int _polarbearCurrentTag;

    bool checkTileMove(int tileTag, int bearTag);

 

    Sprite *_beforeIceTile;

 

    void polarbearAnimationFinish();

 

    int getTileType(int col, int row);

 

    void runIceBreakAnimation(Sprite *ice);

 

    void fallSeaCallBack();

 

    void onClickPause(Ref *object);

};

 

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

 

…생략…

 

void GameScene::setMenuLayer(){

    //화면 크기를 가져옴

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

 

    auto pause = MenuItemImage::create("btn_pause.png", "btn_pause_on.png", CC_CALLBACK_1(GameScene::onClickPause, this));

    pause->setPosition(Point(winSize.width - 20, winSize.height - 20));

 

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

    menu->setPosition(Point::ZERO);

    _menuLayer->addChild(menu);

}

 

…생략…

 

void GameScene::onClickPause(Ref *object){

    log("onClickPause");

}

콜백메소드를 생성하고 setMenuLayer()에서 버튼을 생성해 추가해주었습니다.

 

기존에 생성해둔 _menuLayer에 추가하였습니다.

 

Figure 42 실행화면

 

Figure 43 로그

버튼을 선택하면 로그가 출력되는 것을 확인할 수 있습니다.

 

그럼이제 pause버튼을 선택하면 나타날 팝업을 생성하도록 하겠습니다.

 

PausePopup이라는 이름의 클래스를 Classes 폴더에 생성합니다. [5.2] 참고.

Figure 44 클래스 추가

PausePopup.cpp파일과 PausePopup.h파일이 생성되었습니다.

 

PausePopup.h파일과 PausePopup.cpp파일을 아래와 같이 수정하도록 합니다.

----------PausePopup.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class PausePopup :public Layer

{

public:

    static PausePopup * create();

 

    bool init();

 

    virtual void onEnter();

    bool onTouchBegan(Touch* touch, Event* event);

};

 

----------pausePopup.cpp----------

#include "PausePopup.h"

 

PausePopup * PausePopup::create(){

    PausePopup *ret = new PausePopup();

    if (ret && ret->init())

    {

        ret->autorelease();

    }

    else

    {

        CC_SAFE_DELETE(ret);

    }

 

    return ret;

}

 

bool PausePopup::init(){

    //여기에 팝업을 작성한다.

 

    //화면 크기를 가져옴

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

 

    auto fadeBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);

    this->addChild(fadeBack);

    fadeBack->runAction(FadeTo::create(0.5f, 200));

 

    auto pauseBg = Sprite::create("pop_up.png");

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

    this->addChild(pauseBg);

 

    return true;

}

 

void PausePopup::onEnter(){

    Layer::onEnter();

 

    setTouchEnabled(true);

    setTouchMode(Touch::DispatchMode::ONE_BY_ONE);

}

 

bool PausePopup::onTouchBegan(Touch* touch, Event* event){

    return true;

}

 

PausePopup 클래스를 생성하였습니다. 그럼 일시정지 버튼을 선택하면 이 PausePopup을 출력하도록 하겠습니다.

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

#include "GameScene.h"

#include "BackgroundLayer.h"

#include "GameoverPopup.h"

#include "PausePopup.h"

 

//받은 파라메터를 담은 변수

static Node *_stageScene;

static int _stage;

 

…생략…

 

void GameScene::onClickPause(Ref *object){

    log("onClickPause");

    this->addChild(PausePopup::create(), 99);

}

 

onClickPause()가 호출되면 PausePopup을 추가하였습니다.

 

디버거를 실행하여 팝업이 출력되는지 확인합니다.

Figure 45 실행화면

 

팝업이 출력됐습니다.

 

그럼이제 팝업에 버튼들을 위치시키고 콜백메소드를 등록합니다.

----------PausePopup.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class PausePopup :public Layer

{

public:

    static PausePopup * create();

 

    bool init();

 

    virtual void onEnter();

    bool onTouchBegan(Touch* touch, Event* event);

 

    void onClickBack(Ref *object);

    void onClickRestart(Ref *object);

    void onClickGoStage(Ref *object);

};

 

----------PausePopup.cpp----------

 

…생략…

 

bool PausePopup::init(){

    //여기에 팝업을 작성한다.

 

    //화면 크기를 가져옴

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

 

    auto fadeBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);

    this->addChild(fadeBack);

    fadeBack->runAction(FadeTo::create(0.5f, 200));

 

    auto pauseBg = Sprite::create("pop_up.png");

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

    this->addChild(pauseBg);

 

    auto backMenu = MenuItemImage::create("btn_back2.png", "btn_back2_on.png", this, menu_selector(PausePopup::onClickBack));

    backMenu->setPosition(Point(pauseBg->getContentSize().width / 2, pauseBg->getContentSize().height / 2 + 50));

    auto restartMenu = MenuItemImage::create("btn_restart.png", "btn_restart_on.png", this, menu_selector(PausePopup::onClickRestart));

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

    auto goStageMenu = MenuItemImage::create("btn_stage.png", "btn_stage_on.png", this, menu_selector(PausePopup::onClickGoStage));

    goStageMenu->setPosition(Point(pauseBg->getContentSize().width / 2, pauseBg->getContentSize().height / 2 - 50));

 

    auto menu = Menu::create(backMenu, restartMenu, goStageMenu, NULL);

    menu->setPosition(Point::ZERO);

    pauseBg->addChild(menu);

 

    return true;

}

 

…생략…

 

void PausePopup::onClickBack(Ref *object){

    log("onClickBack");

}

 

void PausePopup::onClickRestart(Ref *object){

    log("onClickRestart");

}

 

void PausePopup::onClickGoStage(Ref *object){

    log("onClickGoStage");

}

 

버튼들을 생성했고 콜백메소드를 등록했습니다.

 

디버거를 실행하고 버튼을 눌러 로그를 확인해봅니다.

Figure 46 실행화면

Figure 47 로그

버튼이 추가되었고 콜백메소드도 등록되었습니다.

 

그럼 먼저 간단하게 구현할 수 있는 돌아가기와 스테이지부터 구현해보도록 하겠습니다.

----------PausePopup.cpp----------

 

…생략…

 

void PausePopup::onClickBack(Ref *object){

    log("onClickBack");

 

    this->removeFromParentAndCleanup(true);

}

 

void PausePopup::onClickRestart(Ref *object){

    log("onClickRestart");

}

 

void PausePopup::onClickGoStage(Ref *object){

    log("onClickGoStage");

 

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

}

 

디버거를 실행해 돌아가기 버튼과 스테이지 버튼을 선택해 기능이 동작하는지 확인합니다.

Figure 48 실행화면

 

기능이 제대로 동작합니다.

 

그럼 다시 시작하기를 구현하도록 하겠습니다.

 

GameScene에서 restartGame()메소드를 하나 생성합니다.

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

#include "cocos2d.h"

#include "Polarbear.h"

 

USING_NS_CC;

 

class GameScene : public cocos2d::Layer

{

public:

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

    static cocos2d::Scene* createScene(Node *stageScene, int stage);

 

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

 

    LayerColor *_mapLayer;

    LayerColor *_menuLayer;

 

    void setMapLayer(int mapArr[4][5]);

    Menu* getMaptile(int row, int col, int type);

    void setMenuLayer();

 

    Sprite *getPenguinSprite();

    Sprite *getGoalFlagSprite();

 

    Polarbear *_polarbear;

 

    void onClickTile(Ref *object);

 

    int _polarbearCurrentTag;

    bool checkTileMove(int tileTag, int bearTag);

 

    Sprite *_beforeIceTile;

 

    void polarbearAnimationFinish();

 

    int getTileType(int col, int row);

 

    void runIceBreakAnimation(Sprite *ice);

 

    void fallSeaCallBack();

 

    void onClickPause(Ref *object);

 

    void restartGame();

};

 

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

 

…생략…

 

void GameScene::restartGame(){

    log("restartGame");

}

 

GameScene에 restartGame()을 생성했습니다.

 

그럼 PausePopup Class에서 이 restartGame()을 호출해보도록 하겠습니다.

----------PausePopup.cpp----------

#include "PausePopup.h"

#include "GameScene.h"

 

…생략…

 

void PausePopup::onClickRestart(Ref *object){

    log("onClickRestart");

 

    ((GameScene *)this->getParent())->restartGame();

    onClickBack(NULL);

}

 

GameScene.h파일을 include하고 getParent()를 이용하여 부모 노드를 가져와 restartGame()을 호출하였습니다.

restartGame()를 호출한다음 팝업창을 닫아주었습니다.

 

디버거를 실행하고 다시 시작하기를 선택하여 로그를 확인합니다.

 

Figure 49 실행화면

Figure 50 로그

로그를 보니 restartGame과 onClickBack이 호출되었습니다.

 

그럼이제 이 restartGame()에서 게임을 초기화 하도록 하겠습니다.

이미 메소드들을 만들어둬서 초기화 하고 호출해주기만 하면 됩니다.

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

 

…생략…

 

void GameScene::restartGame(){

    log("restartGame");

 

    _mapLayer->removeAllChildrenWithCleanup(true);

    _menuLayer->removeAllChildrenWithCleanup(true);

 

    //Stage에 따라 다른 맵 배열을 이용해 생성한다.

    switch (_stage)

    {

    case 1:

        setMapLayer(STAGE1);

        break;

    case 2:

        setMapLayer(STAGE2);

        break;

    case 3:

        setMapLayer(STAGE3);

        break;

    }

 

    setMenuLayer();

}

 

_mapLayer와 _menuLayer의 자식노드를 모두 없애주고 Stage에 따라 setMapLayer()와 setMenuLayer()를 호출해주었습니다.

 

menuLayer까지 초기화 해준이유는 스코어를 menuLayer에 표시할 예정이기 때문입니다.

 

그럼 디버거를 실행하고 다시 시작하기를 선택해봅니다.

Figure 51 실행화면

다시 시작하기를 선택하면 게임이 초기화 되는 것을 확인할 수 있습니다.

 

8.6.9. 펭귄 구출 이벤트 구현하기

펭귄이 있는 빙하 타일로 이동하면 펭귄을 구출하여 북극곰 뒤에 위치시키도록 할 것입니다.

 

먼저 펭귄이 있는 타일로 위치하면 철망이 벗겨지고 펭귄이 사라지도록 하겠습니다.

 

이미 polarBearAnimationFinish()에서 타일의 타입을 체크하여 구분해주었습니다.

 

펭귄이 존재하는 타일로 이동했을 경우의 처리를 해주도록 하겠습니다.

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

 

…생략…

 

//펭귄 스프라이트 생성

Sprite* GameScene::getPenguinSprite(){

    //펭귄 에니메이션 프레임 추가

    auto texture = TextureCache::getInstance()->addImage("penguin.png");

    float textureWidth = texture->getContentSize().width / 3;

    float textureHeight = texture->getContentSize().height;

 

    SpriteFrame *frame[3];

 

    frame[0] = SpriteFrame::createWithTexture(texture, Rect(0, 0, textureWidth, textureHeight));

    frame[1] = SpriteFrame::createWithTexture(texture, Rect(textureWidth * 1, 0, textureWidth, textureHeight));

    frame[2] = SpriteFrame::createWithTexture(texture, Rect(textureWidth * 2, 0, textureWidth, textureHeight));

 

    //펭귄 스프라이트 추가

    auto penguin = Sprite::createWithSpriteFrame(frame[0]);

    penguin->setTag(1);

 

    //펭귄에 에니메이션 추가

    Vector<SpriteFrame*> AniFrames;

    AniFrames.pushBack(frame[0]);

    AniFrames.pushBack(frame[1]);

    AniFrames.pushBack(frame[2]);

 

    auto animation = Animation::createWithSpriteFrames(AniFrames);

    animation->setDelayPerUnit(0.3f);

    auto animate = Animate::create(animation);

 

    penguin->runAction(RepeatForever::create(animate));

 

    //펭귄에 철망 추가

    auto cage = Sprite::create("cage.png");

    cage->setTag(1);

    cage->setPosition(Point(penguin->getContentSize().width / 2, 20));

    penguin->addChild(cage);

 

    //help 메시지 추가

    auto help = Sprite::create("help.png");

    help->setAnchorPoint(Point(0.5f, 0));

    help->setPosition(Point(17, 40));

    penguin->addChild(help);

 

    //help 메시지에 에니메이션 추가

    //에니메이션 초기화

    auto action1 = Spawn::createWithTwoActions(ScaleTo::create(0, 0.5f), FadeTo::create(0, 0));

    //진행될 에니메이션

    auto action2 = Spawn::createWithTwoActions(ScaleTo::create(0.5f, 1), FadeTo::create(0.4f, 100));

    //에니메이션을 추가한다.

    auto action = Sequence::createWithTwoActions(action1, action2);

    //에니메이션을 반복시킴

    help->runAction(RepeatForever::create(action));

 

    return penguin;

}

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

 

        break;

    case 2:            //목적지 도착

 

        break;

    case 3:            //펭귄 구출

    {

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        penguin->removeFromParentAndCleanup(true);

 

        break;

    }

    case 4:            //금이간 빙하

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

}

 

펭귄과 케이지에 Tag를 지정하고 Tag에따라 스프라이트를 가져왔습니다.

 

펭귄 스프라이트에 케이지와 Help메시지가 있기 때문에 펭귄 스프라이트만 제거하면 3개의 스프라이트가 모두 사라지게됩니다.

 

swich ~ case: 구문 내부에서 변수를 정의하게되면

initialization of 'help' is skipped by 'case' label

이러한 에러가 발생하게됩니다. 이럴땐 { }를 이용하여 case구문을 묶어주면 에러를 해결할 수 있습니다.

 

디버거를 실행해 펭귄 스프라이트가 제거되는지 확인하도록 합니다.

Figure 52 실행화면

 

펭귄 스프라이트가 제거가 되었습니다.

 

그러나 그냥 사라지기만 하는 것은 재미가 없으니 케이지를 먼저 제거하여 펭귄을 구출한 것 처럼 보이도록 하겠습니다.

 

케이지에는 Move, Scale, Rotate, Fade 에니메이션을 한번에 적용하도록 하겠습니다.

 

한마디로 이동하는데 크기가 변하면서 회전하면서 서서히 사라지는 에니메이션입니다.

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

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

 

        break;

    case 2:            //목적지 도착

 

        break;

    case 3:            //펭귄 구출

    {

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        auto move = MoveBy::create(0.5f, Point(200, 100));

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(action);

 

        break;

    }

    case 4:            //금이간 빙하

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

}

 

MoveBy와 ScaleTo, RotateBy, FadeOut 에니메이션을 Spawn을 이용하여 한번에 적용하여 cage 스프라이트에 동작시켰습니다.

 

디버거를 실행하고 펭귄이 있는 타일로 이동해봅니다.

Figure 53 실행화면

 

케이지 스프라이트에 에니메이션이 적용되었습니다.

 

그럼 케이지 스프라이트의 에니메이션 동작방향을 좀더 랜덤하게 좌우로 움직이도록 적용해보겠습니다.

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

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

 

        break;

    case 2:            //목적지 도착

 

        break;

    case 3:            //펭귄 구출

    {

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(action);

 

        break;

    }

    case 4:            //금이간 빙하

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

}

 

CCRANDOM_MINUS1_1()를 이용해 -1~1 사이의 값을 랜덤하게 생성하고 이값이 0보다 작으면 multiplication 변수에 -1을 그렇지 않으면 1을 넣어 MoveBy의 포지션중 x에 곱해주었습니다.

이렇게 해줌으로 MoveBy의 x값이 -200이나 200이 나오게 됩니다.

 

디버거를 실행해 좌우로 랜덤하게 케이지가 이동하는지 확인합니다.

Figure 54 실행화면

왼쪽으로 이동하는 에니메이션과 오른쪽으로 이동하는 에니메이션이 랜덤하게 나타나는 화면을 확인할 수 있습니다.

 

Figure 55 실행화면

 

철창이 사라지고난 뒤 펭귄 스프라이트는 아직 남아있는 것을 확인할 수 있습니다.

 

그럼 케이지 스프라이트에 대한 동작은 처리하였고 펭귄 스프라이트에 대한 처리를 해보도록 하겠습니다.

 

펭귄스프라이트는 케이지 에니메이션이 동작이 끝나면 사라지도록 만들겠습니다.

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

#include "cocos2d.h"

#include "Polarbear.h"

 

USING_NS_CC;

 

class GameScene : public cocos2d::Layer

{

public:

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

    static cocos2d::Scene* createScene(Node *stageScene, int stage);

 

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

 

    LayerColor *_mapLayer;

    LayerColor *_menuLayer;

 

    void setMapLayer(int mapArr[4][5]);

    Menu* getMaptile(int row, int col, int type);

    void setMenuLayer();

 

    Sprite *getPenguinSprite();

    Sprite *getGoalFlagSprite();

 

    Polarbear *_polarbear;

 

    void onClickTile(Ref *object);

 

    int _polarbearCurrentTag;

    bool checkTileMove(int tileTag, int bearTag);

 

    Sprite *_beforeIceTile;

 

    void polarbearAnimationFinish();

 

    int getTileType(int col, int row);

 

    void runIceBreakAnimation(Sprite *ice);

 

    void fallSeaCallBack();

 

    void onClickPause(Ref *object);

 

    void restartGame();

 

    void removeMe(Node *Node);

};

 

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

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

 

        break;

    case 2:            //목적지 도착

 

        break;

    case 3:            //펭귄 구출

    {

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

        break;

    }

    case 4:            //금이간 빙하

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

}

 

…생략…

 

void GameScene::removeMe(Node *node){

    node->removeFromParentAndCleanup(true);

}

 

노드를 받아 제거하는 콜백메소드를 생성하였습니다. 케이지도 Sequence 클래스를 사용하여 에니메이션이 동작한 뒤 removeMe를 호출하도록 하였습니다.

 

디버거를 실행하고 펭귄 스프라이트가 사라지는지 확인합니다.

Figure 56 실행화면

 

케이지의 에니메이션이 동작하고 펭귄 스프라이트가 제거되었습니다.

 

구출한 펭귄의 마릿수를 체크할 변수를 추가하고 구출한 펭귄이 북극곰을 따라가도록 구현하도록 하겠습니다.

----------Polarbear.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class Polarbear :public Layer

{

public:

    Polarbear();

    ~Polarbear();

 

    static Polarbear * create(Node *listener, SEL_CallFunc selector);

 

    bool init(Node *listener, SEL_CallFunc selector);

 

    Sprite *_polarbear;

 

    //대기에니메이션

    Action *_wait;

    //왼쪽으로 뛰는 에니메이션

    Action *_jumpLeft;

    //위쪽으로 뛰는 에니메이션

    Action *_jumpTop;

    //아래로 뛰는 에니메이션

    Action *_jumpBottom;

 

    //0 : 대기, 1 : 왼쪽 점프, 2 : 오른쪽 점프, 3 : 위쪽 점프, 4 : 아래쪽 점프

    void setAnimation(int type);

 

    bool setMoveTo(Point moveTo);

 

    //wait 에니메이션을 동작시키는 메소드

    void setWait();

 

    //에니메이션이 동작중인지 체크하는 변수

    bool _isAnimation;

 

    Node *_listener;

    SEL_CallFunc _selector;

 

    void callCallback();

 

    void fallSea(Node *listener, SEL_CallFunc selector);

 

    bool _isFall;

 

    Action *_penguinWait;

    int _penguinCnt;

    void setPenguin();

};

 

----------Polarbear.cpp----------

#include "Polarbear.h"

 

Polarbear::Polarbear(void)

{

    //layer의 크기를 지정해준다.

    this->setContentSize(Size(43, 70));

    this->setAnchorPoint(Point(0.5f, 0));

    this->ignoreAnchorPointForPosition(false);

    //Layer나 LayerColor의 경우 AnchorPoint 변경을 위해선 ignoreAnchorPointForPosition(false)를 해주어야 한다.

    //이유는 Layer가 Scene에서 Point(0, 0)으로 적용되어 있기 때문에 기본 AnchorPoint가 Point(0, 0)이다.

 

    _isAnimation = false;

    _isFall = false;

 

    //처음 생성시 펭귄이 없으므로 0으로 초기화

    _penguinCnt = 0;

}

 

Polarbear::~Polarbear(void)

{

    _wait->release();

    _jumpLeft->release();

    _jumpTop->release();

    _jumpBottom->release();

 

    _penguinWait->release();

}

 

…생략…

 

bool Polarbear::init(Node *listener, SEL_CallFunc selector){

    _listener = listener;

    _selector = selector;

 

…생략…

 

    {

        //펭귄 대기 에니메이션

        SpriteFrame *frame[3];

 

        frame[0] = SpriteFrame::create("penguin_walk1.png", Rect(0, 0, 29, 34));

        frame[1] = SpriteFrame::create("penguin_walk2.png", Rect(0, 0, 29, 34));

        frame[2] = SpriteFrame::create("penguin_walk3.png", Rect(0, 0, 29, 34));

 

        //프레임을 Array에 추가

        Vector<SpriteFrame*> AniFrames;

        AniFrames.pushBack(frame[0]);

        AniFrames.pushBack(frame[1]);

        AniFrames.pushBack(frame[2]);

 

        auto animation = Animation::createWithSpriteFrames(AniFrames);

        animation->setDelayPerUnit(0.33f);

        _penguinWait = RepeatForever::create(Animate::create(animation));

        _penguinWait->retain();

    }

 

    return true;

}

 

…생략…

 

void Polarbear::setPenguin(){

    float penX;        //펭귄의 위치를 위한 변수

    float bearX;

 

    penX = _polarbear->getContentSize().width / 2 + _penguinCnt * 10 + 5;

    bearX = this->getContentSize().width / 2 - (_penguinCnt + 1) * 4;

 

    auto pen = Sprite::create("penguin_walk1.png");

    pen->setTag(_penguinCnt);

    pen->setPosition(Point(penX, pen->getContentSize().height / 2));

    _polarbear->addChild(pen);

    pen->runAction((Action *)_penguinWait->clone());

 

    //북극곰의 위치를 조절한다.

    _polarbear->setPositionX(bearX);

 

    _penguinCnt++;

}

setPenguin() 메소드를 추가하였습니다. _penguinCnt를 이용해 펭귄의 개수를 체크하였습니다.

 

생성된 펭귄 스프라이트를 북극곰 스프라이트에 추가해주었고 펭귄이 추가될때마다 북극곰의 위치를 조금씩 변경해주었습니다.

 

그럼 이 setPenguin()을 호출해주도록 하겠습니다.

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

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

 

        break;

    case 2:            //목적지 도착

 

        break;

    case 3:            //펭귄 구출

    {

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        _polarbear->setPenguin();

        break;

    }

    case 4:            //금이간 빙하

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

}

 

펭귄 구출하는 부분에서 setPenguin()을 호출해주었습니다.

 

디버거를 실행하여 펭귄이 북극곰에 추가되는지 확인합니다.

Figure 57 실행화면

 

펭귄이 3마리까지 추가되는 것을 확인할 수 있습니다.

 

하지만 북극곰의 방향에 따라 펭귄과 북극곰의 위치를 변경해주어야 좀더 자연스럽게 보입니다.

 

북극곰이 오른쪽으로 이동하게되면 북극곰이 오른쪽 그리고 그뒤에 펭귄이 위치하게 해야합니다.

 

그럼 이 위치를 변경해주는 메소드를 만들어보도록 하겠습니다.

----------Polarbear.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class Polarbear :public Layer

{

public:

    Polarbear();

    ~Polarbear();

 

    static Polarbear * create(Node *listener, SEL_CallFunc selector);

 

    bool init(Node *listener, SEL_CallFunc selector);

 

    Sprite *_polarbear;

 

    //대기에니메이션

    Action *_wait;

    //왼쪽으로 뛰는 에니메이션

    Action *_jumpLeft;

    //위쪽으로 뛰는 에니메이션

    Action *_jumpTop;

    //아래로 뛰는 에니메이션

    Action *_jumpBottom;

 

    //0 : 대기, 1 : 왼쪽 점프, 2 : 오른쪽 점프, 3 : 위쪽 점프, 4 : 아래쪽 점프

    void setAnimation(int type);

 

    bool setMoveTo(Point moveTo);

 

    //wait 에니메이션을 동작시키는 메소드

    void setWait();

 

    //에니메이션이 동작중인지 체크하는 변수

    bool _isAnimation;

 

    Node *_listener;

    SEL_CallFunc _selector;

 

    void callCallback();

 

    void fallSea(Node *listener, SEL_CallFunc selector);

 

    bool _isFall;

 

    Action *_penguinWait;

    int _penguinCnt;

    void setPenguin();

 

    bool _isFlip;

    void setFlipNode(bool isFlip);

};

 

----------Polarbear.cpp----------

 

…생략…

 

void Polarbear::setAnimation(int type){

    //기존 스프라이트 에니메이션을 멈춘다.

    _polarbear->stopAllActions();

 

    switch (type)

    {

    case 0:

        _polarbear->runAction(_wait);

        _isAnimation = false;

        break;

    case 1:

        setFlipNode(false);

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    case 2:

        setFlipNode(true);

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    case 3:

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpTop, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    case 4:

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpBottom, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    }

}

 

…생략…

 

void Polarbear::setPenguin(){

    float penX;        //펭귄의 위치를 위한 변수

    float bearX;

 

    if (!_isFlip){

        penX = _polarbear->getContentSize().width / 2 + (_penguinCnt * 10 + 5);

        bearX = this->getContentSize().width / 2 - _penguinCnt * 4;

    }

    else{

        penX = _polarbear->getContentSize().width / 2 - (_penguinCnt * +10 + 5);

        bearX = this->getContentSize().width / 2 + _penguinCnt * 4;

    }

 

    auto pen = Sprite::create("penguin_walk1.png");

    pen->setTag(_penguinCnt);

    pen->setFlipX(_isFlip);

    pen->setPosition(Point(penX, pen->getContentSize().height / 2));

    _polarbear->addChild(pen);

    pen->runAction((Action *)_penguinWait->clone());

 

    //북극곰의 위치를 조절한다.

    _polarbear->setPositionX(bearX);

 

    _penguinCnt++;

}

 

void Polarbear::setFlipNode(bool isFlip){

    _isFlip = isFlip;

    _polarbear->setFlipX(isFlip);

 

    float bearX;

    if (!isFlip)

        bearX = this->getContentSize().width / 2 - _penguinCnt * 4;

    else

        bearX = this->getContentSize().width / 2 + _penguinCnt * 4;

 

    _polarbear->setPositionX(bearX);

 

    for (int i = 0; i < _penguinCnt; i++){

        auto pen = (Sprite *)_polarbear->getChildByTag(i);

        pen->setFlipX(isFlip);

 

        float penX;

        if (!isFlip)

            penX = _polarbear->getContentSize().width / 2 + (i * 10 + 5);

        else

            penX = _polarbear->getContentSize().width / 2 - (i * 10 + 5);

 

        pen->setPositionX(penX);

    }

}

 

setFlipNode()를 생성하였고 북극곰의 위치와 펭귄의 위치 그리고 setFlipX() 값을 변경하여 위치를 바꾸어주었습니다.

 

isFlip이란 변수를 생성하여 방향을 체크하였습니다.

 

디버거를 실행해 좌우 이동시 북극곰이 앞을 보고 있는지 확인하도록 합니다.

Figure 58 실행화면

방향에 따라 북극곰이 앞을 보고 있는 것을 확인할 수 있습니다.

 

그럼 이제 펭귄 스프라이트에 북극곰처럼 Jump에니메이션을 추가하도록 하겠습니다.

----------Polarbear.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class Polarbear :public Layer

{

public:

    Polarbear();

    ~Polarbear();

 

    static Polarbear * create(Node *listener, SEL_CallFunc selector);

 

    bool init(Node *listener, SEL_CallFunc selector);

 

    Sprite *_polarbear;

 

    //대기에니메이션

    Action *_wait;

    //왼쪽으로 뛰는 에니메이션

    Action *_jumpLeft;

    //위쪽으로 뛰는 에니메이션

    Action *_jumpTop;

    //아래로 뛰는 에니메이션

    Action *_jumpBottom;

 

    //0 : 대기, 1 : 왼쪽 점프, 2 : 오른쪽 점프, 3 : 위쪽 점프, 4 : 아래쪽 점프

    void setAnimation(int type);

 

    bool setMoveTo(Point moveTo);

 

    //wait 에니메이션을 동작시키는 메소드

    void setWait();

 

    //에니메이션이 동작중인지 체크하는 변수

    bool _isAnimation;

 

    Node *_listener;

    SEL_CallFunc _selector;

 

    void callCallback();

 

    void fallSea(Node *listener, SEL_CallFunc selector);

 

    bool _isFall;

 

    Action *_penguinWait;

    Action *_penguinJumpLeft;

    Action *_penguinJumpTop;

    Action *_penguinJumpBottom;

 

    int _penguinCnt;

    void setPenguin();

 

    bool _isFlip;

    void setFlipNode(bool isFlip);

 

    void setPenguinAnimation(int type);

};

 

----------Polarbear.cpp----------

 

…생략…

 

Polarbear::~Polarbear(void)

{

    _wait->release();

    _jumpLeft->release();

    _jumpTop->release();

    _jumpBottom->release();

 

    _penguinWait->release();

    _penguinJumpLeft->release();

    _penguinJumpTop->release();

    _penguinJumpBottom->release();

}

 

…생략…

 

 

bool Polarbear::init(Node *listener, SEL_CallFunc selector){

    _listener = listener;

    _selector = selector;

 

…생략…

 

    {

        //펭귄 대기 에니메이션

        SpriteFrame *frame[3];

 

        frame[0] = SpriteFrame::create("penguin_walk1.png", Rect(0, 0, 29, 34));

        frame[1] = SpriteFrame::create("penguin_walk2.png", Rect(0, 0, 29, 34));

        frame[2] = SpriteFrame::create("penguin_walk3.png", Rect(0, 0, 29, 34));

 

        //프레임을 Array에 추가

        Vector<SpriteFrame*> AniFrames;

        AniFrames.pushBack(frame[0]);

        AniFrames.pushBack(frame[1]);

        AniFrames.pushBack(frame[2]);

 

        auto animation = Animation::createWithSpriteFrames(AniFrames);

        animation->setDelayPerUnit(0.33f);

        _penguinWait = RepeatForever::create(Animate::create(animation));

        _penguinWait->retain();

    }

 

    {

        //펭귄 좌측점프 에니메이션

        SpriteFrame *frame[4];

 

        frame[0] = SpriteFrame::create("penguin_jump1.png", Rect(0, 0, 29, 34));

        frame[1] = SpriteFrame::create("penguin_jump2.png", Rect(0, 0, 29, 34));

        frame[2] = SpriteFrame::create("penguin_jump1.png", Rect(0, 0, 29, 34));

        frame[3] = SpriteFrame::create("penguin_jump2.png", Rect(0, 0, 29, 34));

 

        //프레임을 Array에 추가

        Vector<SpriteFrame*> AniFrames;

        AniFrames.pushBack(frame[0]);

        AniFrames.pushBack(frame[1]);

        AniFrames.pushBack(frame[2]);

        AniFrames.pushBack(frame[3]);

 

        auto animation = Animation::createWithSpriteFrames(AniFrames);

        animation->setDelayPerUnit(0.25f);

        _penguinJumpLeft = Animate::create(animation);

        _penguinJumpLeft->retain();

    }

 

    {

        //펭귄 위로점프 에니메이션

        SpriteFrame *frame[3];

 

        frame[0] = SpriteFrame::create("penguin_jump_back1.png", Rect(0, 0, 29, 34));

        frame[1] = SpriteFrame::create("penguin_jump_back2.png", Rect(0, 0, 29, 34));

        frame[2] = SpriteFrame::create("penguin_jump_back3.png", Rect(0, 0, 29, 34));

 

        //프레임을 Array에 추가

        Vector<SpriteFrame*> AniFrames;

        AniFrames.pushBack(frame[0]);

        AniFrames.pushBack(frame[1]);

        AniFrames.pushBack(frame[2]);

 

        auto animation = Animation::createWithSpriteFrames(AniFrames);

        animation->setDelayPerUnit(0.33f);

        _penguinJumpTop = RepeatForever::create(Animate::create(animation));

        _penguinJumpTop->retain();

    }

 

    {

        //펭귄 아래로 점프 에니메이션

        SpriteFrame *frame[3];

 

        frame[0] = SpriteFrame::create("penguin_jump_front1.png", Rect(0, 0, 29, 34));

        frame[1] = SpriteFrame::create("penguin_jump_front2.png", Rect(0, 0, 29, 34));

        frame[2] = SpriteFrame::create("penguin_jump_front3.png", Rect(0, 0, 29, 34));

 

        //프레임을 Array에 추가

        Vector<SpriteFrame*> AniFrames;

        AniFrames.pushBack(frame[0]);

        AniFrames.pushBack(frame[1]);

        AniFrames.pushBack(frame[2]);

 

        auto animation = Animation::createWithSpriteFrames(AniFrames);

        animation->setDelayPerUnit(0.33f);

        _penguinJumpBottom = RepeatForever::create(Animate::create(animation));

        _penguinJumpBottom->retain();

    }

 

    return true;

}

 

void Polarbear::setAnimation(int type){

    //기존 스프라이트 에니메이션을 멈춘다.

    _polarbear->stopAllActions();

 

    switch (type)

    {

    case 0:

        _polarbear->runAction(_wait);

        _isAnimation = false;

        break;

    case 1:

        setFlipNode(false);

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    case 2:

        setFlipNode(true);

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    case 3:

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpTop, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    case 4:

        _polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpBottom, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));

        break;

    }

 

    setPenguinAnimation(type);

}

 

…생략…

 

void Polarbear::setPenguinAnimation(int type){

    Action *action;

    switch (type)

    {

    case 0:

        action = _penguinWait;

        break;

    case 1:

        action = _penguinJumpLeft;

        break;

    case 2:

        action = _penguinJumpLeft;

        break;

    case 3:

        action = _penguinJumpTop;

        break;

    case 4:

        action = _penguinJumpBottom;

        break;

    }

 

    for (int i = 0; i < _penguinCnt; i++){

        auto pen = (Sprite *)_polarbear->getChildByTag(i);

        //기존 스프라이트 에니메이션을 멈춘다.

        pen->stopAllActions();

        pen->runAction((Action *)action->clone());

    }

}

 

setPenguinAnimation()을 생성하여 펭귄 스프라이트를 가져와 에니메이션을 동작시켰습니다.

 

기존에 flip이나 위치는 변경해주었기 때문에 에니메이션만 추가해주었습니다.

 

디버거를 실행해 에니메이션이 동작되는지 확인합니다.

Figure 59 실행화면

왼쪽 오른쪽 위 아래 모두 에니메이션이 정상 동작하는 것을 확인할 수 있습니다.

 

에니메이션 변수들을 보면 Retain()을 호출해주었습니다. Retain()은 retainCount를 증가시켜 자동으로 릴리즈 되는 것을 막습니다. 따라서 Retain()을 호출하여 autorelease()가 되지 않습니다.

따라서 소멸자에서 변수를 릴리즈 해주었습니다.

 

이제 마지막으로 북극곰이 금이간 빙하 타일을 밟았을 때 Fade에니메이션을 주도록 하겠습니다.

----------Polarbear.cpp----------

 

…생략…

 

void Polarbear::fallSea(Node *listener, SEL_CallFunc selector){

    _isFall = true;

 

    _listener = listener;

    _selector = selector;

 

    auto moveBy = MoveBy::create(0.5f, Point(0, -50));

    //떨어지는 에니메이션에 EaseAction을 적용했습니다.

    auto easeMove = EaseSineIn::create(moveBy);

 

    //서서히 사라지는 에니메이션

    auto fadeout = FadeOut::create(0.5f);

 

    //두개의 에니메이션을 동시에 동작시키는 에니메이션

    auto action = Spawn::createWithTwoActions(easeMove, fadeout);

 

    //에니메이션을 순서대로 동작시키는 에니메이션, 에니메이션이 끝나면 Call_callback()을 호출합니다.

    auto action2 = Sequence::createWithTwoActions(action, CallFunc::create(CC_CALLBACK_0(Polarbear::callCallback, this)));

 

    _polarbear->runAction(action2);

 

    for (int i = 0; i < _penguinCnt; i++){

        auto pen = (Sprite *)_polarbear->getChildByTag(i);

        pen->runAction(FadeOut::create(0.5));

        //_polarbear에 add 되어있기때문에 move에니메이션을 필요가없다.

    }

}

디버거를 실행하여 금이간 빙하 타일을 밟았을 때 펭귄에 에니메이션이 적용되는지 확인합니다.

 

Figure 60 실행화면

FadeOut에니메이션이 적용되었습니다.

 

8.6.10. 북극곰 클래스 구현하기

이번에는 점수를 표시해주도록 하겠습니다. 북극곰 탈출게임은 빙하타일을 최대한 많이 밟고 펭귄을 구출하는 것을 목표로 하고있습니다. 짧은 길을 찾는 것이 아니라 먼 ~ 길을 찾아가는 퍼즐게임입니다.

따라서 점수는 (밟은 타일 개수 * 1000) + (구출한 펭귄 마릿수 * 5000) + 시간보너스로 구현하도록 하겠습니다.

 

먼저 UI를 위치시키도록 하겠습니다.

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

#include "cocos2d.h"

#include "Polarbear.h"

 

USING_NS_CC;

 

class GameScene : public cocos2d::Layer

{

public:

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

    static cocos2d::Scene* createScene(Node *stageScene, int stage);

 

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

 

    LayerColor *_mapLayer;

    LayerColor *_menuLayer;

 

    void setMapLayer(int mapArr[4][5]);

    Menu* getMaptile(int row, int col, int type);

    void setMenuLayer();

 

    Sprite *getPenguinSprite();

    Sprite *getGoalFlagSprite();

 

    Polarbear *_polarbear;

 

    void onClickTile(Ref *object);

 

    int _polarbearCurrentTag;

    bool checkTileMove(int tileTag, int bearTag);

 

    Sprite *_beforeIceTile;

 

    void polarbearAnimationFinish();

 

    int getTileType(int col, int row);

 

    void runIceBreakAnimation(Sprite *ice);

 

    void fallSeaCallBack();

 

    void onClickPause(Ref *object);

 

    void restartGame();

 

    void removeMe(Node *Node);

 

    LabelTTF *_labelScore;

    LabelTTF *_labelPenguin;

    int _score;

    int _rescueCount;

};

 

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

 

…생략…

 

void GameScene::setMenuLayer(){

    //화면 크기를 가져옴

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

 

    auto pause = MenuItemImage::create("btn_pause.png", "btn_pause_on.png", CC_CALLBACK_1(GameScene::onClickPause, this));

    pause->setPosition(Point(winSize.width - 20, winSize.height - 20));

 

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

    menu->setPosition(Point::ZERO);

    _menuLayer->addChild(menu);

 

    _score = 0;

    _rescueCount = 0;

 

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

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

    _menuLayer->addChild(_scoreIcon);

 

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

    _labelScore->setAnchorPoint(Point(0, 0.5f));

    _labelScore->setPosition(Point(60, winSize.height - 20));

    _menuLayer->addChild(_labelScore);

 

    auto penguinIcon = Sprite::create("icon_penguin.png");

    penguinIcon->setPosition(Point(140, winSize.height - 20));

    _menuLayer->addChild(penguinIcon);

 

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

    _labelPenguin->setPosition(Point(160, winSize.height - 20));

    _labelPenguin->setColor(Color3B(247, 136, 211));

    _menuLayer->addChild(_labelPenguin);

}

LabelTTF를 2개를 헤더파일에 선언하고 위치시키도록 하였습니다. 라벨은 다른곳에서 호출하여 숫자를 바꾸어 주도록 할 것입니다.

 

디버거를 실행하고 UI를 확인합니다.

Figure 61 실행화면

좌측 상단에 스코어가 위치한 것을 알 수 있습니다.

 

이번엔 점수를 계산하고 표시하도록 하겠습니다.

 

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

#include "GameScene.h"

#include "BackgroundLayer.h"

#include "GameoverPopup.h"

#include "PausePopup.h"

 

//#define으로 변하지않는 값들을 정의할 수 있습니다.

//#define에는 세미콜론을 하지 않습니다.

#define RESCUE 5000

#define TILE 1000

 

//받은 파라메터를 담은 변수

static Node *_stageScene;

static int _stage;

 

…생략…

 

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

        _score = _score + TILE;

        break;

    case 2:            //목적지 도착

        _score = _score + TILE;

        break;

    case 3:            //펭귄 구출

    {

        _score = _score + RESCUE;

        _rescueCount = _rescueCount + 1;

 

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        _polarbear->setPenguin();

        break;

    }

    case 4:            //금이간 빙하

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

 

    char c_score[50];

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

    _labelScore->setString(c_score);

 

    char c_rescue[20];

    sprintf(c_rescue, "%d", _rescueCount);

    _labelPenguin->setString(c_rescue);

}

상황에 맞게 점수와 구출한 펭귄의 수를 더해주었습니다.

 

그리고 LabelTTF에 setString() 해주었습니다.

 

디버거를 실행해 타일을 이동하여 점수를 확인합니다.

 

Figure 62 실행화면

점수가 업데이트 되는 것을 확인할 수 있습니다.

 

8.6.11. 타이머 기능 구현

게임에 긴장감을 더하기 위해 타이머를 추가하도록 하겠습니다.

7장을 참고하여 작성하였습니다. 자세한 내용은 7장을 참고하시기 바랍니다.

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

#include "cocos2d.h"

#include "Polarbear.h"

 

USING_NS_CC;

 

class GameScene : public cocos2d::Layer

{

public:

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

    static cocos2d::Scene* createScene(Node *stageScene, int stage);

 

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

 

    LayerColor *_mapLayer;

    LayerColor *_menuLayer;

 

    void setMapLayer(int mapArr[4][5]);

    Menu* getMaptile(int row, int col, int type);

    void setMenuLayer();

 

    Sprite *getPenguinSprite();

    Sprite *getGoalFlagSprite();

 

    Polarbear *_polarbear;

 

    void onClickTile(Ref *object);

 

    int _polarbearCurrentTag;

    bool checkTileMove(int tileTag, int bearTag);

 

    Sprite *_beforeIceTile;

 

    void polarbearAnimationFinish();

 

    int getTileType(int col, int row);

 

    void runIceBreakAnimation(Sprite *ice);

 

    void fallSeaCallBack();

 

    void onClickPause(Ref *object);

 

    void restartGame();

 

    void removeMe(Node *Node);

 

    LabelTTF *_labelScore;

    LabelTTF *_labelPenguin;

    int _score;

    int _rescueCount;

 

    void setTimer();

    void updateTimer(float time);

    void stopTimer();

 

    float _countdownTimer;

 

    ProgressTimer *_progressBar;

    LabelTTF *_labelCountdown;

};

 

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

#include "GameScene.h"

#include "BackgroundLayer.h"

#include "GameoverPopup.h"

#include "PausePopup.h"

 

//#define으로 변하지않는 값들을 정의할 수 있습니다.

//#define에는 세미콜론을 하지 않습니다.

#define RESCUE 5000

#define TILE 1000

#define TIME 30.00f

#define GOAL_BONUS 10000

 

//받은 파라메터를 담은 변수

static Node *_stageScene;

static int _stage;

 

…생략…

 

void GameScene::setMenuLayer(){

    //화면 크기를 가져옴

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

 

    auto pause = MenuItemImage::create("btn_pause.png", "btn_pause_on.png", CC_CALLBACK_1(GameScene::onClickPause, this));

    pause->setPosition(Point(winSize.width - 20, winSize.height - 20));

 

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

    menu->setPosition(Point::ZERO);

    _menuLayer->addChild(menu);

 

    _score = 0;

    _rescueCount = 0;

 

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

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

    _menuLayer->addChild(_scoreIcon);

 

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

    _labelScore->setAnchorPoint(Point(0, 0.5f));

    _labelScore->setPosition(Point(60, winSize.height - 20));

    _menuLayer->addChild(_labelScore);

 

    auto penguinIcon = Sprite::create("icon_penguin.png");

    penguinIcon->setPosition(Point(140, winSize.height - 20));

    _menuLayer->addChild(penguinIcon);

 

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

    _labelPenguin->setPosition(Point(160, winSize.height - 20));

    _labelPenguin->setColor(Color3B(247, 136, 211));

    _menuLayer->addChild(_labelPenguin);

 

    auto timerBack = Sprite::create("time_bar.png");

    timerBack->setPosition(Point(80, winSize.height - 40));

    _menuLayer->addChild(timerBack);

 

    auto progressSprite = Sprite::create("time_gage.png");

    _progressBar = ProgressTimer::create(progressSprite);

    _progressBar->setPosition(Point(timerBack->getContentSize().width / 2 + 8, timerBack->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);

 

    timerBack->addChild(_progressBar);

 

    _labelCountdown = LabelTTF::create("30.00", "Arial Bold", 10);

    _labelCountdown->setColor(Color3B::BLACK);

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

    _labelCountdown->setPosition(Point(timerBack->getContentSize().width / 2 + 8, timerBack->getContentSize().height / 2 - 1));

    timerBack->addChild(_labelCountdown);

 

    setTimer();

}

 

…생략…

 

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

        _score = _score + TILE;

        break;

    case 2:            //목적지 도착

        _score = _score + TILE;

 

        stopTimer();

        //남은점수에 비례하여 GOAL_BONUS를 추가해주었습니다.

        _score = _score + ((_countdownTimer / TIME) * GOAL_BONUS);

        break;

    case 3:            //펭귄 구출

    {

        _score = _score + RESCUE;

        _rescueCount = _rescueCount + 1;

 

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        _polarbear->setPenguin();

        break;

    }

    case 4:            //금이간 빙하

        stopTimer();

 

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

 

    char c_score[50];

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

    _labelScore->setString(c_score);

 

    char c_rescue[20];

    sprintf(c_rescue, "%d", _rescueCount);

    _labelPenguin->setString(c_rescue);

}

 

…생략…

 

void GameScene::setTimer(){

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

 

    _countdownTimer = TIME;

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

 

        this->addChild(GameoverPopup::create(), 99);

    }

 

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

    char str[10] = { 0 };

    //소수점 둘째자리까지만 표시한다.

    sprintf(str, "%2.2f", _countdownTimer);

    _labelCountdown->setString(str);

}

 

void GameScene::stopTimer(){

    unschedule(schedule_selector(GameScene::updateTimer));

    _progressBar->stopAllActions();

}

TIME에 30을 추가하여 30초동안 타이머가 동작하도록 하였습니다. 타이머를 생성하여 메뉴레이어에 추가하였고, setTimer(), updateTimer(), stopTimer()를 생성하여 게임내 필요한 시점에서 호출하였습니다.

 

디버거를 실행하고 타이머를 확인합니다.

 

Figure 63 실행화면

좌측 상단에 타이머가 표시되었습니다.

 

Figure 64 실행화면

게임오버가되면 stopTimer()를 호출하여 타이머가 멈춥니다.

 

Figure 65 실행화면

목적지에 도착하여도 stopTimer()가 호출되어 타이머가 멈춥니다.

 

위 3가지 상황을 모두 확인했다면 타이머 추가가 완료하였습니다.

 

그럼이제 pause버튼을 선택하였을 때 스케쥴과 액션을 잠시 멈추고 게임화면으로 돌아오면 다시 실행하도록 하겠습니다.

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

#include "cocos2d.h"

#include "Polarbear.h"

 

USING_NS_CC;

 

class GameScene : public cocos2d::Layer

{

public:

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

    static cocos2d::Scene* createScene(Node *stageScene, int stage);

 

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

 

    LayerColor *_mapLayer;

    LayerColor *_menuLayer;

 

    void setMapLayer(int mapArr[4][5]);

    Menu* getMaptile(int row, int col, int type);

    void setMenuLayer();

 

    Sprite *getPenguinSprite();

    Sprite *getGoalFlagSprite();

 

    Polarbear *_polarbear;

 

    void onClickTile(Ref *object);

 

    int _polarbearCurrentTag;

    bool checkTileMove(int tileTag, int bearTag);

 

    Sprite *_beforeIceTile;

 

    void polarbearAnimationFinish();

 

    int getTileType(int col, int row);

 

    void runIceBreakAnimation(Sprite *ice);

 

    void fallSeaCallBack();

 

    void onClickPause(Ref *object);

 

    void restartGame();

 

    void removeMe(Node *Node);

 

    LabelTTF *_labelScore;

    LabelTTF *_labelPenguin;

    int _score;

    int _rescueCount;

 

    void setTimer();

    void updateTimer(float time);

    void stopTimer();

    void resumeTimer();

    void pauseTimer();

 

    float _countdownTimer;

 

    ProgressTimer *_progressBar;

    LabelTTF *_labelCountdown;

};

 

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

 

…생략…

 

 

void GameScene::onClickPause(Ref *object){

    log("onClickPause");

       

    pauseTimer();

 

    this->addChild(PausePopup::create(), 99);

}

 

void GameScene::restartGame(){

    log("restartGame");

 

    resumeTimer();

 

    _mapLayer->removeAllChildrenWithCleanup(true);

    _menuLayer->removeAllChildrenWithCleanup(true);

 

    //Stage에 따라 다른 맵 배열을 이용해 생성한다.

    switch (_stage)

    {

    case 1:

        setMapLayer(STAGE1);

        break;

    case 2:

        setMapLayer(STAGE2);

        break;

    case 3:

        setMapLayer(STAGE3);

        break;

    }

 

    setMenuLayer();

}

 

…생략…

 

void GameScene::pauseTimer(){

    this->pauseSchedulerAndActions();

    _progressBar->pauseSchedulerAndActions();

}

 

void GameScene::resumeTimer(){

    this->resumeSchedulerAndActions();

    _progressBar->resumeSchedulerAndActions();

}

 

----------PausePopup.cpp----------

 

…생략…

 

void PausePopup::onClickBack(Ref *object){

    log("onClickBack");

 

    GameScene *gamesScene = (GameScene *)this->getParent();

    gamesScene->resumeTimer();

 

    this->removeFromParentAndCleanup(true);

}

onClickPause()에서 pauseTimer()를 호출해주고 PausePopup 클래스의 onClickBack()에서 GameScene의 resumeTimer()를 호출해주었습니다.

 

중요한 것은 pause버튼을 선택하고 다시 시작하기를 하였을 때 스케쥴과 액션이 pause상태라 resume을 호출해주었습니다.

 

디버거를 실행하여 pause버튼을 누르면 스케쥴과 액션이 멈추고 돌아가기를 선택하면 다시 실행되는지 확인합니다.

Figure 66 실행화면

  

기능이 정상적으로 동작하는 것을 확인할 수 있습니다.

 

8.6.12. 게임 성공 시 결과 팝업 구현하기

게임을 성공하였으면 스코어를 표시해주는 결과팝업을 만들어보도록 하겠습니다.

파라메터로 받아야할 것은 점수와 구출한 펭귄의 수 이렇게 2개의 파라메터를 받아 결과 화면을 표현해주도록 하겠습니다.

 

그리고 파티클을 추가하여 좀더 화려하게 보이도록 만들어주도록 하겠습니다.

 

먼저 ResultPopup이라는 이름의 Class를 Classes 폴더에 생성합니다. [5.2] 참고.

Figure 67 클래스 추가

ResultPopup.cpp파일과 ResultPopup.h파일이 생성되었습니다.

 

ResultPopup.h파일과 ResultPopup.cpp파일을 아래와 같이 수정하도록 합니다.

----------ResultPopup.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class ResultPopup :public Layer

{

public:

    static ResultPopup * create(int score, int penguin);

 

    bool init(int score, int penguin);

 

    virtual void onEnter();

    bool onTouchBegan(Touch* touch, Event* event);

 

    void onClickOK(Ref *object);

};

 

----------ResultPopup.cpp----------

#include "ResultPopup.h"

 

ResultPopup * ResultPopup::create(int score, int penguin){

    ResultPopup *ret = new ResultPopup();

    if (ret && ret->init(score, penguin))

    {

        ret->autorelease();

    }

    else

    {

        CC_SAFE_DELETE(ret);

    }

 

    return ret;

}

 

bool ResultPopup::init(int score, int penguin){

    //여기에 팝업을 작성한다.

 

    //화면 크기를 가져옴

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

 

    auto fadeBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);

    this->addChild(fadeBack);

    fadeBack->runAction(FadeTo::create(0.5f, 200));

 

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

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

    this->addChild(back);

 

    //펭귄 스프라이트 추가

    auto pen1 = Sprite::create("score_penguin2.png");

    pen1->setPosition(Point(back->getContentSize().width / 2 - 50, 150));

    back->addChild(pen1);

 

    auto pen2 = Sprite::create("score_penguin2.png");

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

    back->addChild(pen2);

 

    auto pen3 = Sprite::create("score_penguin2.png");

    pen3->setPosition(Point(back->getContentSize().width / 2 + 50, 150));

    back->addChild(pen3);

 

    //Score영역

    auto scoreBg = Sprite::create("box_score.png");

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

    back->addChild(scoreBg);

 

    auto okMenu = MenuItemImage::create("btn_ok.png", "btn_ok_on.png", CC_CALLBACK_1(ResultPopup::onClickOK, this));

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

 

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

    menu->setPosition(Point::ZERO);

    back->addChild(menu);

 

    return true;

}

 

void ResultPopup::onEnter(){

    Layer::onEnter();

 

    setTouchEnabled(true);

    setTouchMode(Touch::DispatchMode::ONE_BY_ONE);

}

 

bool ResultPopup::onTouchBegan(Touch* touch, Event* event){

    return true;

}

 

void ResultPopup::onClickOK(Ref *object){

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

}

 

ResultPopup 클래스를 생성하고 UI를 위치하도록 하였습니다.

 

그럼 ResultPopup을 호출하도록 하겠습니다.

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

#include "GameScene.h"

#include "BackgroundLayer.h"

#include "GameoverPopup.h"

#include "PausePopup.h"

#include "ResultPopup.h"

 

//#define으로 변하지않는 값들을 정의할 수 있습니다.

//#define에는 세미콜론을 하지 않습니다.

#define RESCUE 5000

#define TILE 1000

#define TIME 30.00f

#define GOAL_BONUS 10000

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

        _score = _score + TILE;

        break;

    case 2:            //목적지 도착

        _score = _score + TILE;

 

        stopTimer();

        //남은점수에 비례하여 GOAL_BONUS를 추가해주었습니다.

        _score = _score + ((_countdownTimer / TIME) * GOAL_BONUS);

 

        this->addChild(ResultPopup::create(_score, _rescueCount), 99);

        break;

    case 3:            //펭귄 구출

    {

        _score = _score + RESCUE;

        _rescueCount = _rescueCount + 1;

 

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        _polarbear->setPenguin();

        break;

    }

    case 4:            //금이간 빙하

        stopTimer();

 

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

 

    char c_score[50];

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

    _labelScore->setString(c_score);

 

    char c_rescue[20];

    sprintf(c_rescue, "%d", _rescueCount);

    _labelPenguin->setString(c_rescue);

}

목적지에 도착하면 ResultPopup을 추가해주었습니다. ResultPopup에는 얻은 점수와 구출한 펭귄수를 파라메터로 넘겨주었습니다.

 

디버거를 실행하여 목적지까지 이동해보도록 합니다.

 

Figure 68 실행화면

목적지까지 이동하면 결과팝업이 추가되었습니다.

 

확인을 누르면 스테이지선택 Scene으로 이동됩니다.

 

다음으로 추가할 기능은 Score를 숫자가 점점 올라가는 것으로 표현하도록 하겠습니다.

3초동안 60번 숫자가 변경되도록 만들겠습니다.  

----------ResultPopup.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class ResultPopup :public Layer

{

public:

    static ResultPopup * create(int score, int penguin);

 

    bool init(int score, int penguin);

 

    virtual void onEnter();

    bool onTouchBegan(Touch* touch, Event* event);

 

    void onClickOK(Ref *object);

 

    //획득한 점수

    int _totalScore;

    //업데이트 해줄 임시로 표시할 점수

    int _tempScore;

    //스케쥴당 업데이트할 점수

    int _tickScore;

    //스케쥴 호출 횟수

    int _updateCount;

 

    LabelTTF *_labelScore;

 

    void updateScore(float time);

};

 

----------ResultPopup.cpp----------

 

…생략…

 

 

bool ResultPopup::init(int score, int penguin){

    //여기에 팝업을 작성한다.

 

    //화면 크기를 가져옴

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

 

    auto fadeBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);

    this->addChild(fadeBack);

    fadeBack->runAction(FadeTo::create(0.5f, 200));

 

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

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

    this->addChild(back);

 

    //펭귄 스프라이트 추가

    auto pen1 = Sprite::create("score_penguin2.png");

    pen1->setPosition(Point(back->getContentSize().width / 2 - 50, 150));

    back->addChild(pen1);

 

    auto pen2 = Sprite::create("score_penguin2.png");

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

    back->addChild(pen2);

 

    auto pen3 = Sprite::create("score_penguin2.png");

    pen3->setPosition(Point(back->getContentSize().width / 2 + 50, 150));

    back->addChild(pen3);

 

    //Score영역

    auto scoreBg = Sprite::create("box_score.png");

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

    back->addChild(scoreBg);

 

    auto okMenu = MenuItemImage::create("btn_ok.png", "btn_ok_on.png", CC_CALLBACK_1(ResultPopup::onClickOK, this));

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

 

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

    menu->setPosition(Point::ZERO);

    back->addChild(menu);

 

    //스코어 추가

    _labelScore = LabelTTF::create("0", "Arial", 40);

    _labelScore->setPosition(Point(scoreBg->getContentSize().width / 2, 25));

    _labelScore->setColor(Color3B(25, 163, 242));

    scoreBg->addChild(_labelScore);

 

    _totalScore = score;

    _tempScore = 0;

    _tickScore = score / 60;

    _updateCount = 0;

 

    this->schedule(schedule_selector(ResultPopup::updateScore), 3.0f / 60, 59, 0.05f);

 

    return true;

}

 

…생략…

 

void ResultPopup::updateScore(float time){

    _updateCount++;

 

    _tempScore = _tempScore + _tickScore;

    char scoreChar[50];

 

    //60번째호출보다 크면 totalScore를 넣어준다.

    if (_updateCount >= 60){

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

        //스케쥴을 해지한다.

        unschedule(schedule_selector(ResultPopup::updateScore));

    }

    else

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

 

    _labelScore->setString(scoreChar);

}

schedule()을 이용하여 점수를 갱신해주었습니다. 60번째 호출보다 크면 처음에 받은 점수를 그대로 넣어주고 스케쥴을 정지하였습니다. 처음점수를 바로 넣어준 이유는 60으로 나누어 _tickScore를 구하면서 int로 형변환되면서 값이 같지 않을 수 있기 때문입니다.

 

디버거를 실행하고 목적지까지 이동해봅니다.

Figure 69 실행화면

 

점수가 업데이트되어 얻은 점수까지 표시됩니다.

 

점수표시가 완료되면 점수를 약간확대하여 완료된것에 대해 확인할 수 있도록 하겠습니다.

----------ResultPopup.cpp----------

 

…생략…

 

void ResultPopup::updateScore(float time){

    _updateCount++;

 

    _tempScore = _tempScore + _tickScore;

    char scoreChar[50];

 

    //60번째호출보다 크면 totalScore를 넣어준다.

    if (_updateCount >= 60){

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

        //스케쥴을 해지한다.

        unschedule(schedule_selector(ResultPopup::updateScore));

 

        auto scaleTo = ScaleTo::create(0.1f, 1.2);

        _labelScore->runAction(scaleTo);

    }

    else

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

 

    _labelScore->setString(scoreChar);

}

_updateCount가 60이되면 scaleTo를 이용하여 1.2배 확대해주었습니다.

 

디버거를 실행해 스케쥴이 완료되면 스코어의 라벨이 확대되는지 확인합니다.

Figure 70 실행화면

스케쥴이 완료되면 라벨이 확대되는 것을 확인하였습니다.

 

그럼이제 스코어를 보여주고나서 구출한 펭귄을 표시해주도록 하겠습니다.

----------ResultPopup.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class ResultPopup :public Layer

{

public:

    static ResultPopup * create(int score, int penguin);

 

    bool init(int score, int penguin);

 

    virtual void onEnter();

    bool onTouchBegan(Touch* touch, Event* event);

 

    void onClickOK(Ref *object);

 

    //획득한 점수

    int _totalScore;

    //업데이트 해줄 임시로 표시할 점수

    int _tempScore;

    //스케쥴당 업데이트할 점수

    int _tickScore;

    //스케쥴 호출 횟수

    int _updateCount;

 

    LabelTTF *_labelScore;

 

    void updateScore(float time);

 

    int _penguinCount;

    void setPenguinCount();

};

 

----------ResultPopup.cpp----------

 

…생략…

 

bool ResultPopup::init(int score, int penguin){

    //여기에 팝업을 작성한다.

 

    //화면 크기를 가져옴

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

 

    auto fadeBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);

    this->addChild(fadeBack);

    fadeBack->runAction(FadeTo::create(0.5f, 200));

 

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

    back->setTag(1);

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

    this->addChild(back);

 

    //펭귄 스프라이트 추가

    auto pen1 = Sprite::create("score_penguin2.png");

    pen1->setTag(0);

    pen1->setPosition(Point(back->getContentSize().width / 2 - 50, 150));

    back->addChild(pen1);

 

    auto pen2 = Sprite::create("score_penguin2.png");

    pen2->setTag(1);

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

    back->addChild(pen2);

 

    auto pen3 = Sprite::create("score_penguin2.png");

    pen3->setTag(2);

    pen3->setPosition(Point(back->getContentSize().width / 2 + 50, 150));

    back->addChild(pen3);

 

    //Score영역

    auto scoreBg = Sprite::create("box_score.png");

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

    back->addChild(scoreBg);

 

    auto okMenu = MenuItemImage::create("btn_ok.png", "btn_ok_on.png", CC_CALLBACK_1(ResultPopup::onClickOK, this));

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

 

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

    menu->setPosition(Point::ZERO);

    back->addChild(menu);

 

    //스코어 추가

    _labelScore = LabelTTF::create("0", "Arial", 40);

    _labelScore->setPosition(Point(scoreBg->getContentSize().width / 2, 25));

    _labelScore->setColor(Color3B(25, 163, 242));

    scoreBg->addChild(_labelScore);

 

    _totalScore = score;

    _tempScore = 0;

    _tickScore = score / 60;

    _updateCount = 0;

 

    _penguinCount = penguin;

 

    this->schedule(schedule_selector(ResultPopup::updateScore), 3.0f / 60, 59, 0.05f);

 

    return true;

}

 

…생략…

 

void ResultPopup::updateScore(float time){

    _updateCount++;

 

    _tempScore = _tempScore + _tickScore;

    char scoreChar[50];

 

    //60번째호출보다 크면 totalScore를 넣어준다.

    if (_updateCount >= 60){

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

        //스케쥴을 해지한다.

        unschedule(schedule_selector(ResultPopup::updateScore));

 

        auto scaleTo = ScaleTo::create(0.1f, 1.2);

        _labelScore->runAction(scaleTo);

 

        setPenguinCount();

    }

    else

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

 

    _labelScore->setString(scoreChar);

}

 

void ResultPopup::setPenguinCount(){

    auto back = (Sprite *)this->getChildByTag(1);

 

    for (int i = 0; i < _penguinCount; i++){

        auto penguin = (Sprite *)back->getChildByTag(i);

 

        auto getPenguin = Sprite::create("score_penguin1.png");

        getPenguin->setPosition(penguin->getPosition());

        back->addChild(getPenguin);

    }

}

 

구출한 펭귄의 수를 변수에 담고 점수표시가 끝나면 구출한 펭귄 수만큼 반복문을 돌면서 펭귄을 표시해주었습니다.

 

디버거를 실행하고 펭귄 스프라이트를 확인합니다.

Figure 71 실행화면

 

점수가 표시되고 구출한 펭귄수만큼 펭귄이 표시되었습니다.

 

펭귄을 표시하는데 에니메이션을 추가하여 좀더 화려하게 만들어보겠습니다.

----------ResultPopup.cpp-----------

 

…생략…

 

void ResultPopup::setPenguinCount(){

    auto back = (Sprite *)this->getChildByTag(1);

 

    for (int i = 0; i < _penguinCount; i++){

        auto penguin = (Sprite *)back->getChildByTag(i);

 

        auto getPenguin = Sprite::create("score_penguin1.png");

        getPenguin->setPosition(penguin->getPosition());

        back->addChild(getPenguin);

 

        //scale을 0으로 조절

        getPenguin->setScale(0);

 

        //딜레이시간을 0.5초간격으로

        auto delay = DelayTime::create(i * 0.5f);

        //0.1초동안 scale을 1로 에니메이션

        auto ScaleTo = ScaleTo::create(0.1f, 1);

 

        auto action = Sequence::createWithTwoActions(delay, ScaleTo);

 

        getPenguin->runAction(action);

    }

}

시간간격을 두고 펭귄이 표시되도록 하였습니다.

 

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

Figure 72 실행화면

 

시간간격을 두고 펭귄이 표시됩니다.

 

펭귄이 표시될 때 좀더 화려하게 파티클을 추가하도록 하겠습니다.

파티클이란 작은 입자를 나타내며 여러가지 효과를 추가할 수 있습니다.

----------ResultPopup.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class ResultPopup :public Layer

{

public:

    static ResultPopup * create(int score, int penguin);

 

    bool init(int score, int penguin);

 

    virtual void onEnter();

    bool onTouchBegan(Touch* touch, Event* event);

 

    void onClickOK(Ref *object);

 

    //획득한 점수

    int _totalScore;

    //업데이트 해줄 임시로 표시할 점수

    int _tempScore;

    //스케쥴당 업데이트할 점수

    int _tickScore;

    //스케쥴 호출 횟수

    int _updateCount;

 

    LabelTTF *_labelScore;

 

    void updateScore(float time);

 

    int _penguinCount;

    void setPenguinCount();

 

    void setParticle(Node *node);

};

 

----------ResultPopup.cpp----------

 

…생략…

 

void ResultPopup::setPenguinCount(){

    auto back = (Sprite *)this->getChildByTag(1);

 

    for (int i = 0; i < _penguinCount; i++){

        auto penguin = (Sprite *)back->getChildByTag(i);

 

        auto getPenguin = Sprite::create("score_penguin1.png");

        getPenguin->setPosition(penguin->getPosition());

        back->addChild(getPenguin);

 

        //scale을 0으로 조절

        getPenguin->setScale(0);

 

        //딜레이시간을 0.5초간격으로

        auto delay = DelayTime::create(i * 0.5f);

        //0.1초동안 scale을 1로 에니메이션

        auto ScaleTo = ScaleTo::create(0.1f, 1);

 

        auto action = Sequence::create(delay, ScaleTo, CallFuncN::create(CC_CALLBACK_1(ResultPopup::setParticle, this)), NULL);

 

        getPenguin->runAction(action);

    }

}

 

void ResultPopup::setParticle(Node *node){

    //파티클 추가

 

    //30개의 입자를 가진 ParticleExplosion 생성

    auto particle = ParticleExplosion::createWithTotalParticles(30);

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

 

    //텍스쳐를 변경

    particle->setTexture(TextureCache::getInstance()->addImage("star.png"));

    //파티클의 속도설정

    particle->setSpeed(100);

    //파티클시작 색상값 비율 투명도를 80%로 조절하였다.

    particle->setStartColor(Color4F(1, 1, 1, 0.80f));

    //자동으로 없앤다.

    particle->setAutoRemoveOnFinish(true);

 

    node->addChild(particle);

}

펭귄의 scale 에니메이션이 끝나면 setParticle()을 호출하도록 하였습니다. 파티클은 ParticleExplosion을 간단하게 변경하였습니다.

 

이처럼 기본으로 제공하는 파티클을 사용하는 방법도 있고 따로 파티클파일을 추가하여 사용할수도 있습니다. 파티클을 제작하는 방법은 부록을 확인하시기 바랍니다.

 

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

 

Figure 73 실행화면

펭귄이 추가되면서 형형색색의 파티클이 추가되었습니다.

파티클 예제는 cocos2d-x샘플소스에 여러 개가 있으니 참고하시면 좋습니다.

 

8.6.13. 스테이지 UNLOCK 구현하기

스테이지를 완료하면 UNLOCK 되는 기능을 구현하겠습니다.

 

이전에 스테이지 선택화면에서 UserDefault를 이용하여 값을 체크해주었습니다.

스테이지를 완료하면 이 값을 변경해주도록 할 것 입니다.

 

먼저 스테이지 선택화면부터 수정하도록 하겠습니다.

----------StageScene.h----------

#include "cocos2d.h"

 

class StageScene : 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(StageScene);

 

    void onClickBack(Ref *object);

 

    cocos2d::LayerColor *_stageLayer;

    void setStage();

    void onClickStage(Ref *object);

 

    void onEnter();

};

 

----------StageScene.cpp----------

 

…생략…

 

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

bool StageScene::init()

{

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

    // 1. super init first

    if (!Layer::init())

    {

        return false;

    }

 

    //code here

 

    this->addChild(BackgroundLayer::create());

 

    //화면 크기를 가져옴

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

 

    auto deco = Sprite::create("stage_bear.png");

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

    deco->setPosition(Point(winSize.width + deco->getContentSize().width, -deco->getContentSize().height));

    this->addChild(deco);

 

    auto easeAction = EaseInOut::create(MoveBy::create(0.5f, Point(-deco->getContentSize().width, deco->getContentSize().height)), 1);

    deco->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5f), easeAction));

 

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

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

 

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

    menu->setPosition(Point::ZERO);

    this->addChild(menu);

 

    _stageLayer = NULL;

 

    //scene이 생성되면 onEnter()가 자동으로 호출된다.

    //따라서 setStage()를 주석처리하고 onEnter에서 실행해준다.

    //setStage();

 

    return true;

}

 

…생략…

 

void StageScene::onEnter(){

    Layer::onEnter();

 

    setStage();

}

onEnter()에서 setStage()를 호출해주었습니다.

 

GameScene에서 StageScene으로 넘어오면 자동으로 이 onEnter()가 호출됩니다.

 

onEnter()는 해당 씬에 다시 돌아올때에도 실행됩니다. 따라서 처음실행될 때에도 실행되고 게임화면에서 해당 씬으로 돌아와도 다시 실행됩니다.

 

그럼 이제 게임이 완료되면 UserDefault의 값을 변경해주도록 하겠습니다.

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

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

    log("polarbearAnimationFinish");

 

    //tag를 분해하여 col과 row를 구한다.

    int col = (_polarbearCurrentTag - 1) % 5;

    int row = (_polarbearCurrentTag - 1 - col) / 5;

 

    log("col = %d, row = %d", col, row);

 

    int type = getTileType(col, row);

    log("type = %d", type);

 

    switch (type)

    {

    case 0:            //일반 빙하

        _score = _score + TILE;

        break;

    case 2:            //목적지 도착

        _score = _score + TILE;

 

        stopTimer();

        //남은점수에 비례하여 GOAL_BONUS를 추가해주었습니다.

        _score = _score + ((_countdownTimer / TIME) * GOAL_BONUS);

 

        {

            auto UserDefault = UserDefault::getInstance();

 

            if (_stage == 1)

                UserDefault->setBoolForKey("lock2", false);

            else if (_stage == 2)

                UserDefault->setBoolForKey("lock3", false);

 

            //호출해주어야 값이 바뀐다.

            UserDefault->flush();

        }

 

        this->addChild(ResultPopup::create(_score, _rescueCount), 99);

        break;

    case 3:            //펭귄 구출

    {

        _score = _score + RESCUE;

        _rescueCount = _rescueCount + 1;

 

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        _polarbear->setPenguin();

        break;

    }

    case 4:            //금이간 빙하

        stopTimer();

 

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

 

    char c_score[50];

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

    _labelScore->setString(c_score);

 

    char c_rescue[20];

    sprintf(c_rescue, "%d", _rescueCount);

    _labelPenguin->setString(c_rescue);

}

완료 지점에 도착하면 UserDefault의 값을 변경하였습니다.

 

디버거를 실행해 스테이지를 완료해봅니다.

 

 

 

 

스테이지를 완료하면 다음스테이지가 UNLOCK 되었습니다.

 

8.6.14. 배경음악 / 효과음 추가하기

SimpleAudioEngine을 이용하여 배경음악과 효과음에 대한 제어를 해주도록 하겠습니다.

 

간단하게 배경음악은 1개, 효과음은 부분적으로 사용하도록 하겠습니다.

----------StartScene.cpp----------

#include "StartScene.h"

#include "BackgroundLayer.h"

#include "StageScene.h"

#include "RankingPopup.h"

#include "SimpleAudioEngine.h"

 

USING_NS_CC;

 

…생략…

 

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

bool StartScene::init()

{

 

…생략…

 

    auto menu = Menu::create(gameMenu, rankMenu, NULL);

    menu->setPosition(Point::ZERO);

    this->addChild(menu);

 

    CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("sound/bgm.mp3", true);

 

    return true;

}

 

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

#include "GameScene.h"

#include "BackgroundLayer.h"

#include "GameoverPopup.h"

#include "PausePopup.h"

#include "ResultPopup.h"

#include "SimpleAudioEngine.h"

 

…생략…

 

void GameScene::polarbearAnimationFinish(){

 

…생략…

 

    case 3:            //펭귄 구출

    {

        //펭귄 구출시 효과음 추가

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

 

        _score = _score + RESCUE;

        _rescueCount = _rescueCount + 1;

 

        auto ice = (Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1);

        auto penguin = (Sprite *)ice->getChildByTag(1);

        //빙하타일에서 1번 tag를 가진 자식노드는 펭귄 스프라이트

        auto cage = (Sprite *)penguin->getChildByTag(1);

        //펭귄 스프라이트에서 1번을 가진 자식노드는 케이지 스프라이트

 

        int multiplication = 1;

        float random = CCRANDOM_MINUS1_1();

 

        if (random < 0)

            multiplication = -1;

 

        auto move = MoveBy::create(0.5f, Point(200 * multiplication, 100));

 

        auto scale = ScaleTo::create(0.5f, 5);

        auto rotate = RotateBy::create(0.5f, 360);

        auto fade = FadeOut::create(0.5f);

 

        auto action = Spawn::create(move, scale, rotate, fade, NULL);

        cage->runAction(Sequence::createWithTwoActions(action, CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        penguin->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5), CallFuncN::create(CC_CALLBACK_1(GameScene::removeMe, this))));

 

        _polarbear->setPenguin();

        break;

    }

    case 4:            //금이간 빙하

        stopTimer();

 

        runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));

        _polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));

        break;

    }

 

    char c_score[50];

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

    _labelScore->setString(c_score);

 

    char c_rescue[20];

    sprintf(c_rescue, "%d", _rescueCount);

    _labelPenguin->setString(c_rescue);

}

 

----------Polarbear.cpp----------

#include "Polarbear.h"

#include "SimpleAudioEngine.h"

 

…생략…

 

void Polarbear::callCallback(){

    //착지할때 효과음 추가

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

 

    //에니메이션이 완료되면 callback메소드 호출

    if (_listener && _selector)

        (_listener->*_selector)();

}

 

----------GameoverPopup.cpp----------

#include "GameoverPopup.h"

#include "SimpleAudioEngine.h"

 

…생략…

 

bool GameoverPopup::init(){

    //여기에 팝업을 작성한다.

 

    //게임오버 팝업이 출력되면 배경음악을 잠시 멈춘다.

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

    CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/gameover.mp3");

 

    //화면 크기를 가져옴

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

 

    auto fadeBack = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);

    this->addChild(fadeBack);

    fadeBack->runAction(FadeTo::create(0.5f, 200));

 

    auto gameover = Sprite::create("gameover.png");

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

    this->addChild(gameover);

 

    return true;

}

 

…생략…

 

bool GameoverPopup::onTouchBegan(Touch* touch, Event* event){

    //배경음악을 다시 실행시킨다.

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

 

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

    return true;

}

 

----------ResultPopup.cpp----------

#include "ResultPopup.h"

#include "SimpleAudioEngine.h"

 

…생략…

 

bool ResultPopup::init(int score, int penguin){

 

…생략…

 

    _totalScore = score;

    _tempScore = 0;

    _tickScore = score / 60;

    _updateCount = 0;

 

    _penguinCount = penguin;

 

    this->schedule(schedule_selector(ResultPopup::updateScore), 3.0f / 60, 59, 0.05f);

 

    //배경음악을 잠시 멈추고 효과음을 실행시킨다.

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

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

 

    return true;

}

 

…생략…

 

void ResultPopup::onClickOK(Ref *object){

    //배경음악을 다시 실행시킨다.

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

 

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

}

 

…생략…

 

void ResultPopup::setPenguinCount(){

    auto back = (Sprite *)this->getChildByTag(1);

 

    for (int i = 0; i < _penguinCount; i++){

        auto penguin = (Sprite *)back->getChildByTag(i);

 

        auto getPenguin = Sprite::create("score_penguin1.png");

        getPenguin->setPosition(penguin->getPosition());

        back->addChild(getPenguin);

 

        //scale을 0으로 조절

        getPenguin->setScale(0);

 

        //딜레이시간을 0.5초간격으로

        auto delay = DelayTime::create(i * 0.5f);

        //0.1초동안 scale을 1로 에니메이션

        auto ScaleTo = ScaleTo::create(0.1f, 1);

 

        auto action = Sequence::create(delay, ScaleTo, CallFuncN::create(CC_CALLBACK_1(ResultPopup::setParticle, this)), NULL);

 

        getPenguin->runAction(action);

 

        getPenguin->setTag(i);

    }

}

 

void ResultPopup::setParticle(Node *node){

    //파티클 추가

 

    //효과음을 약간 다르게 넣기위해서 tag를 받아 효과음을 출력

    if (node->getTag() == 0)

        CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/firework1.mp3");

    else if (node->getTag() == 1)

        CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/firework2.mp3");

    else if (node->getTag() == 2)

        CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/firework3.mp3");

 

    //30개의 입자를 가진 ParticleExplosion 생성

    auto particle = ParticleExplosion::createWithTotalParticles(30);

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

 

    //텍스쳐를 변경

    particle->setTexture(TextureCache::getInstance()->addImage("star.png"));

    //파티클의 속도설정

    particle->setSpeed(100);

    //파티클시작 색상값 비율 투명도를 80%로 조절하였다.

    particle->setStartColor(Color4F(1, 1, 1, 0.80f));

    //자동으로 없앤다.

    particle->setAutoRemoveOnFinish(true);

 

    node->addChild(particle);

}

위처럼 배경음악과 효과음을 추가하면 상황에 맞게 효과음이 출력됩니다.

 

디버거를 실행하고 효과음을 확인하도록 합니다.

Figure 74 사운드 확인

 

각 시점에 맞는 사운드가 출력되는지 확인하도록 합니다.


Prev | Next