지난번 연재 8-1, 8-2, 8-3, 8-4와 이어집니다.
*그림번호가 너무 많이 늘어나 이번 절부터 그림번호를 1부터 리셋합니다.
먼저 게임화면에서 사용해야할 레이어들을 구성해보도록 하겠습니다.
Figure 1 화면구성
게임 화면의 레이어는 기본적으로 위 그림과 같이 추가될 것입니다.
zOrder를 사용하여 레이어를 배치하도록 하겠습니다.
----------GameScene.h----------
#include "cocos2d.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;
};
----------GameScene.cpp----------
…생략…
// on "init" you need to initialize your instance
bool GameScene::init()
{
//////////////////////////////
// 1. super init first
if (!Layer::init())
{
return false;
}
log("stageScene %d, Stage %d", _stageScene, _stage);
//화면 크기를 가져옴
auto winSize = Director::getInstance()->getOpenGLView()->getDesignResolutionSize();
this->addChild(BackgroundLayer::create(), 0);
_mapLayer = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);
this->addChild(_mapLayer, 1);
_menuLayer = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);
this->addChild(_menuLayer, 2);
return true;
}
기존의 백그라운드레이어를 추가한부분에 zOrder를 0으로 _mapLayer를 1로 _menuLayer를 2로 zOrder를 설정하였습니다.
zOrder가 높을수록 위쪽에 배치됩니다. zOrder가 같으면 나중에 추가된 Node가 위쪽에 배치됩니다.
Layer를 생성하였으니 _mapLayer를 구현해보도록 하겠습니다.
_menuLayer는 추후에 구현하도록 할 것 입니다.
----------GameScene.h----------
#include "cocos2d.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]);
LayerColor* getMaptile(int row, int col, int type);
void setMenuLayer();
};
----------GameScene.cpp----------
#include "GameScene.h"
#include "BackgroundLayer.h"
//받은 파라메터를 담은 변수
static Node *_stageScene;
static int _stage;
//스테이지 정보에 대한 값, 0이면 밟을 수 있는빙하, 1이면 start지점, 2이면 goal지점, 3이면 펭귄, 4이면 약한 빙하
//[0][0]이 화면 왼쪽 아래 타일, [3][4]가 화면 오른쪽 위 타일
int STAGE1[4][5] = { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } };
int STAGE2[4][5] = { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } };
int STAGE3[4][5] = { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } };
…생략…
// on "init" you need to initialize your instance
bool GameScene::init()
{
//////////////////////////////
// 1. super init first
if (!Layer::init())
{
return false;
}
log("stageScene %d, Stage %d", _stageScene, _stage);
//화면 크기를 가져옴
auto winSize = Director::getInstance()->getOpenGLView()->getDesignResolutionSize();
this->addChild(BackgroundLayer::create(), 0);
_mapLayer = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);
this->addChild(_mapLayer, 1);
_menuLayer = LayerColor::create(Color4B(0, 0, 0, 0), winSize.width, winSize.height);
this->addChild(_menuLayer, 2);
//Stage에 따라 다른 맵 배열을 이용해 생성한다.
switch (_stage)
{
case 1:
setMapLayer(STAGE1);
break;
case 2:
setMapLayer(STAGE2);
break;
case 3:
setMapLayer(STAGE3);
break;
}
setMenuLayer();
return true;
}
void GameScene::setMapLayer(int mapArr[4][5]){
//2중 for문을 돌면서 해당 배열의 값을 가져온다.
for (int i = 0; i < 4; i++){
for (int j = 0; j < 5; j++){
auto tile = getMaptile(i, j, mapArr[i][j]);
_mapLayer->addChild(tile, 4 - i);
//아래쪽 빙하가 위에 보여야하므로 zOrder를 지정하였습니다.
}
}
}
LayerColor* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 255), 96, 55);
//row와 col을 이용하여 현재위치를 설정하였다.
tile->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
//type가 0이면
if (type == 0){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
}
//좌측 아래부터 tag를 1 우측 위를 20으로 계산하여 넣음
int tag = col + (5 * row) + 1;
log("mapArr[%d][%d], tag = %d", row, col, tag);
//타일의 위치를 tag로 구분하기 위해 tag를 지정해줌
tile->setTag(tag);
return tile;
}
void GameScene::setMenuLayer(){
}
STAGE1, STAGE2, STAGE3 이란 배열을 선언해두었습니다. 맵 정보가 들어갈 배열입니다. 저 배열의 값에 따라 맵이 바뀌도록 구현해볼 것 입니다.
맵정보 같은경우 데이터베이스를 이용하여 구현하여도 괜찮습니다. 서버를 사용하는 게임의 경우 맵데이터 같이 밸런스 조정이 필요한 부분은 서버에서 데이터를 받아 동적으로 구현하는 경우도 있습니다.
_stage에 따라 해당 스테이지의 배열을 파라메터로 추가하여 setMapLayer()를 호출합니다.
setMenuLayer()를 미리 생성해두었습니다.
setMapLayer()에서는 2중 for문을 호출하면서 배열의 값을 호출하여 getMaptile()을 호출하여 맵타일을 가져옵니다.
맵타일을 가져옴에 있어 col과 row를 이용해 위치를 지정하고 tag를 지정하였습니다.
getMaptile()에서 LayerColor에 붉은색을 표시하도록 하여 영역을 확인할 수 있도록 하였습니다.
디버거를 실행해 맵타일이 위치되는지 확인해보도록 합니다.
Figure 2 실행화면
맵타일을 row에 따라 가져왔고 붉은 색의 배경을 확인하니 틈없이 화면에 맞춰 위치되었습니다.
[3][0], tag = 16 |
[3][1], tag = 17 |
[3][2], tag = 18 |
[3][3], tag = 19 |
[3][4], tag = 20 |
[2][0], tag = 11 |
[2][1], tag = 12 |
[2][2], tag = 13 |
[2][3], tag = 14 |
[2][4], tag = 15 |
[1][0], tag = 6 |
[1][1], tag = 7 |
[1][2], tag = 8 |
[1][3], tag = 9 |
[1][4], tag = 10 |
[0][0], tag = 1 |
[0][1], tag = 2 |
[0][2], tag = 3 |
[0][3], tag = 4 |
[0][4], tag = 5 |
배열에 따른 Tile의 위치와 tag는 위 표와같이 위치됩니다.
로그를 확인하여 tag를 확인해봅니다.
Figure 3 배열의 위치와 태그 로그
배열의 위치에 맞추어 tag가 정확히 위 표처럼 지정되었습니다.
타일이 제대로 추가되는 것을 확인했고 딱 맞추어 위치되므로 타일의 배경에 색상이 추가되어있던 부분을 없애주도록 합니다.
----------GameScene.cpp----------
…생략…
LayerColor* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
…생략…
디버거를 실행해 확인합니다.
Figure 4 실행화면
붉은색으로 표시됐던 배경이 사라졌습니다.
스테이지별 배열에 따라 다른 타일이 위치해야 하므로 분기를 추가해주도록 하겠습니다.
----------GameScene.cpp----------
…생략…
LayerColor* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
//row와 col을 이용하여 현재위치를 설정하였다.
tile->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
//type가 0이거나 1이거나 3이면 일반 빙하타일, 1이나 3은 일반 빙하타일위에 오브젝트가 올라가므로
if (type == 0 || type == 1 || type == 3){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
if (type == 1){
//시작점이므로 여기에 북극곰 추가
}
else if (type == 3){
//펭귄 추가
}
}
else if (type == 2){
//type가 2이면 골인 지점
}
else if (type == 4){
//type가 4이면 무너지는 빙하타일, 밟으면 게임오버
}
//좌측 아래부터 tag를 1 우측 위를 20으로 계산하여 넣음
int tag = col + (5 * row) + 1;
log("mapArr[%d][%d], tag = %d", row, col, tag);
//타일의 위치를 tag로 구분하기 위해 tag를 지정해줌
tile->setTag(tag);
return tile;
}
각각 4가지의 분기를 추가해주었습니다.
각 분기별로 타일과 오브젝트들을 추가하도록 하겠습니다.
----------GameScene.h----------
#include "cocos2d.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]);
LayerColor* getMaptile(int row, int col, int type);
void setMenuLayer();
Sprite *getPenguinSprite();
Sprite *getGoalFlagSprite();
};
----------GameScene.cpp----------
…생략…
LayerColor* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
//row와 col을 이용하여 현재위치를 설정하였다.
tile->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
//type가 0이거나 1이거나 3이면 일반 빙하타일, 1이나 3은 일반 빙하타일위에 오브젝트가 올라가므로
if (type == 0 || type == 1 || type == 3){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
if (type == 1){
//시작점이므로 여기에 북극곰 추가
}
else if (type == 3){
//펭귄 추가
auto penguin = getPenguinSprite();
penguin->setAnchorPoint(Point(0.5f, 0));
penguin->setPosition(Point(ice->getContentSize().width / 2, 25));
ice->addChild(penguin);
}
}
else if (type == 2){
//type가 2이면 골인 지점
auto ice = Sprite::create("goal.png");
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
//깃발 추가
auto flag = getGoalFlagSprite();
flag->setAnchorPoint(Point(0.5f, 0));
flag->setPosition(Point(50, 35));
ice->addChild(flag);
}
else if (type == 4){
//type가 4이면 무너지는 빙하타일, 밟으면 게임오버
std::string iceImg;
//row에 따라 약간 다른 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "break1_default.png";
break;
case 1:
iceImg = "break2_default.png";
break;
case 2:
iceImg = "break3_default.png";
break;
case 3:
iceImg = "break4_default.png";
break;
}
auto breakIce = Sprite::create(iceImg.c_str());
breakIce->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(breakIce);
}
//좌측 아래부터 tag를 1 우측 위를 20으로 계산하여 넣음
int tag = col + (5 * row) + 1;
log("mapArr[%d][%d], tag = %d", row, col, tag);
//타일의 위치를 tag로 구분하기 위해 tag를 지정해줌
tile->setTag(tag);
return tile;
}
void GameScene::setMenuLayer(){
}
//펭귄 스프라이트 생성
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]);
//펭귄에 에니메이션 추가
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->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;
}
//깃발 스프라이트 생성
Sprite* GameScene::getGoalFlagSprite(){
//깃발 에니메이션 프레임 추가
SpriteFrame *frame[3];
frame[0] = SpriteFrame::create("flag_1.png", Rect(0, 0, 39, 53));
frame[1] = SpriteFrame::create("flag_2.png", Rect(0, 0, 39, 53));
frame[2] = SpriteFrame::create("flag_3.png", Rect(0, 0, 39, 53));
//깃발 스프라이트 추가
auto flag = Sprite::createWithSpriteFrame(frame[0]);
//깃발에 에니메이션 추가
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.1f);
auto animate = Animate::create(animation);
flag->runAction(RepeatForever::create(animate));
return flag;
}
이번엔 코드가 약간 길지만 모두 이미 사용해보았던 코드들이므로 꼼꼼히 확인하면서 작성하도록합니다.
기존의 스프라이트 에니메이션과 다른점은 파일이 하나하나 따로 저장되어있다는점 입니다.
Figure 5 스프라이트 에니메이션 이미지
flag_1.png, flag_2.png, flag_3.png 이렇게 프레임이 다른파일로 저장되어있어도 사용할 수 있습니다.
텍스쳐에 담는 부분이 필요가 없이 이미지를 받아 프레임에 넣어주었습니다.
스프라이트 에니메이션을 만들때 하나의 파일에 프레임이 들어있는것이 효율이 좀더 좋습니다.
텍스쳐와 관련된 자세한 설명은 부록[오류! 참조 원본을 찾을 수 없습니다.]을 참고하시면 도움이 될 것입니다.
getPenguinSprite()는 펭귄을 스프라이트 에니메이션으로 추가하였고 케이지와 Help메시지를 추가하고 액션을 추가해주었습니다.
getGoalFlagSrpte()는 Goal지점에 있는 깃발을 가져왔습니다.
STAGE의 맵정보를 바꾸어주도록 합니다.
----------gameScene.cpp----------
#include "GameScene.h"
#include "BackgroundLayer.h"
//받은 파라메터를 담은 변수
static Node *_stageScene;
static int _stage;
//스테이지 정보에 대한 값, 0이면 밟을 수 있는빙하, 1이면 start지점, 2이면 goal지점, 3이면 펭귄, 4이면 약한 빙하
//[0][0]이 화면 왼쪽 아래 타일, [3][4]가 화면 오른쪽 위 타일
int STAGE1[4][5] = { { 0, 0, 0, 3, 1 }, { 0, 3, 0, 0, 4 }, { 4, 0, 0, 0, 0 }, { 2, 0, 3, 0, 4 } };
int STAGE2[4][5] = { { 0, 3, 0, 0, 1 }, { 3, 0, 4, 0, 0 }, { 0, 0, 0, 4, 0 }, { 2, 0, 0, 0, 3 } };
int STAGE3[4][5] = { { 0, 0, 4, 0, 1 }, { 4, 3, 0, 3, 4 }, { 0, 0, 0, 0, 0 }, { 2, 0, 3, 0, 0 } };
맵정보에 따라 스테이지가 생성되므로 맵정보를 수정합니다.
스테이지의 맵정보를 수정하였으면 디버거를 실행합니다.
Figure 6 실행화면
수정된 맵정보에 따라 맵타일이 추가되었습니다.
맵을 구현하였고 이번엔 북극곰 캐릭터를 구현해보도록 하겠습니다.
Polarbear라는 이름의 클래스를 Classes 폴더에 생성합니다. [5.2] 참고.
Figure 7 클래스 추가
Polarbear.h 파일과 Polarbear.cpp 파일을 생성하였으면 아래와 같이 수정합니다.
----------Polarbear.h----------
#include "cocos2d.h"
USING_NS_CC;
class Polarbear :public Layer
{
public:
Polarbear();
~Polarbear();
static Polarbear * create();
bool init();
Sprite *_polarbear;
//대기에니메이션
Action *_wait;
//왼쪽으로 뛰는 에니메이션
Action *_jumpLeft;
//위쪽으로 뛰는 에니메이션
Action *_jumpTop;
//아래로 뛰는 에니메이션
Action *_jumpBottom;
//0 : 대기, 1 : 왼쪽 점프, 2 : 오른쪽 점프, 3 : 위쪽 점프, 4 : 아래쪽 점프
void setAnimation(int type);
};
----------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)이다.
}
Polarbear::~Polarbear(void)
{
_wait->release();
}
Polarbear * Polarbear::create(){
Polarbear *ret = new Polarbear();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
bool Polarbear::init(){
{
//대기 에니메이션 생성 / 추가
SpriteFrame *frame[6];
//1->2->3->4->2->1 순으로 이미지 반복
frame[0] = SpriteFrame::create("w_bear01.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear03.png", Rect(0, 0, 43, 70));
frame[3] = SpriteFrame::create("w_bear04.png", Rect(0, 0, 43, 70));
frame[4] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[5] = SpriteFrame::create("w_bear01.png", Rect(0, 0, 43, 70));
//스프라이트 추가
_polarbear = Sprite::createWithSpriteFrame(frame[0]);
_polarbear->setPosition(Point(this->getContentSize().width / 2, this->getContentSize().height / 2));
this->addChild(_polarbear);
//프레임을 Array에 추가
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
AniFrames.pushBack(frame[3]);
AniFrames.pushBack(frame[4]);
AniFrames.pushBack(frame[5]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.3f);
_wait = RepeatForever::create(Animate::create(animation));
_wait->retain();
//대기에니메이션을 적용
_polarbear->runAction(_wait);
}
return true;
}
void Polarbear::setAnimation(int type){
}
북극곰 클래스를 생성하였고 대기 에니메이션만을 추가해놓았습니다.
대기 에니메이션의 경우 retain()을 호출하여 자동으로 메모리 해제가 되지 않으므로 소멸자에서 release()해주도록 하였습니다. retain()을 해주었다면 따로 release()를 해주어야 한다는 것을 알아두도록 합니다.
Cocos2d-x 는 기본적으로 autoRelease pool을 사용합니다. create()를 사용하는 대부분의 클래스는 기본적으로 autoRelease가 적용되어있어 따로 메모리 관리를 하지않아도 메모리에서 해제되어 버립니다. 따라서 메모리에서 해제되지 않기 위해선 retain()을 이용하거나 new를 이용하여 클래스를 생성하면 따로 메모리를 관리해주어야 합니다.
대기 에니메이션은 프레임이 반복되는 경우 하나의 이미지를 다시 호출하여 프레임을 생성하였습니다.
북극곰 클래스에서는 북극곰의 에니메이션에 대해 제어를 하도록 할 것입니다.
그럼 이 북극곰 클래스를 GameScene에 추가해보도록 하겠습니다.
----------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]);
LayerColor* getMaptile(int row, int col, int type);
void setMenuLayer();
Sprite *getPenguinSprite();
Sprite *getGoalFlagSprite();
Polarbear *_polarbear;
};
----------GameScene.cpp----------
…생략…
LayerColor* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
//row와 col을 이용하여 현재위치를 설정하였다.
tile->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
//type가 0이거나 1이거나 3이면 일반 빙하타일, 1이나 3은 일반 빙하타일위에 오브젝트가 올라가므로
if (type == 0 || type == 1 || type == 3){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
if (type == 1){
//시작점이므로 여기에 북극곰 추가
_polarbear = Polarbear::create();
_polarbear->setAnchorPoint(Point(0.5f, 0));
_polarbear->setPosition(Point(tile->getPositionX() + tile->getContentSize().width / 2, tile->getPositionY() + tile->getContentSize().height / 2));
_mapLayer->addChild(_polarbear, 5);
}
타일 타입이 1인경우 해당 타일에 북극곰을 위치하도록 하였습니다.
디버거를 실행하여 북극곰이 위치되었는지 확인해보도록 합니다.
Figure 8 실행화면
우측 하단에 북극곰이 생성되었습니다. 북극곰은 스프라이트 에니메이션으로 대기 에니메이션이 적용되어 있습니다.
타일 부분이 터치가 되도록 구현하도록 할 것 입니다.
리턴 형식이 약간 바뀌므로 형식과 관계를 잘 파악해야 합니다.
----------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);
};
----------GameScene.cpp----------
…생략…
Menu* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
auto tileMenu = MenuItemSprite::create(tile, NULL, CC_CALLBACK_1(GameScene::onClickTile, this));
//row와 col을 이용하여 현재위치를 설정하였다.
tileMenu->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
tileMenu->setAnchorPoint(Point(0, 0));
auto menu = Menu::create(tileMenu, NULL);
menu->setPosition(Point::ZERO);
//type가 0이거나 1이거나 3이면 일반 빙하타일, 1이나 3은 일반 빙하타일위에 오브젝트가 올라가므로
if (type == 0 || type == 1 || type == 3){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
if (type == 1){
//시작점이므로 여기에 북극곰 추가
_polarbear = Polarbear::create();
_polarbear->setAnchorPoint(Point(0.5f, 0));
//북극곰은 mapLayer에 추가되므로 m_tile의 위치를 가져오는 것으로 수정.
_polarbear->setPosition(Point(tileMenu->getPositionX() + tileMenu->getContentSize().width / 2, tileMenu->getPositionY() + tileMenu->getContentSize().height / 2));
_mapLayer->addChild(_polarbear, 5);
}
else if (type == 3){
//펭귄 추가
auto penguin = getPenguinSprite();
penguin->setAnchorPoint(Point(0.5f, 0));
penguin->setPosition(Point(ice->getContentSize().width / 2, 25));
ice->addChild(penguin);
}
}
else if (type == 2){
//type가 2이면 골인 지점
auto ice = Sprite::create("goal.png");
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(ice);
//깃발 추가
auto flag = getGoalFlagSprite();
flag->setAnchorPoint(Point(0.5f, 0));
flag->setPosition(Point(50, 35));
ice->addChild(flag);
}
else if (type == 4){
//type가 4이면 무너지는 빙하타일, 밟으면 게임오버
std::string iceImg;
//row에 따라 약간 다른 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "break1_default.png";
break;
case 1:
iceImg = "break2_default.png";
break;
case 2:
iceImg = "break3_default.png";
break;
case 3:
iceImg = "break4_default.png";
break;
}
auto breakIce = Sprite::create(iceImg.c_str());
breakIce->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
tile->addChild(breakIce);
}
//좌측 아래부터 tag를 1 우측 위를 20으로 계산하여 넣음
int tag = col + (5 * row) + 1;
log("mapArr[%d][%d], tag = %d", row, col, tag);
//타일의 위치를 tag로 구분하기 위해 tag를 지정해줌
menu->setTag(tag);
return menu;
}
…생략…
void GameScene::onClickTile(Ref *object){
auto tile = (MenuItem *)object;
log("tag = %d", tile->getParent()->getTag());
}
getMaptile()의 리턴 타입을 Menu로 변경하고 콜백메소드를 등록하였습니다.
이에 따라 기존에 LayerColor로 생성된 tile을 tileMenu에 추가하여 tileMenu에 위치를 지정하였습니다.
따라서 북극곰 클래스의 위치를 tileMenu에 동적으로 변경하였고 tag를 menu에 추가하였습니다.
onClicktile()에 넘어오는 오브젝트는 MenuItem으로 getParent()를 이용하여 menu를 가져와 tag를 체크하였습니다.
디버거를 실행하고 타일을 클릭하여 tag값을 확인해보도록 합니다.
Figure 9 실행화면
맵타일을 하나씩 눌러 태그가 로그에 나오는지 확인하도록 합니다. 좌측하단이 1이며 우측상단이 20입니다.
Figure 10 로그
모든 타일에 tag가 추가되어 타일틀 클릭하면 로그에 출력됩니다.
타일맵을 선택하면 해당 타일맵이 북극곰이 이동할 수 있는 타일인지 체크한 뒤 이동 가능한 타일이면 북극곰을 이동시키도록 할 것 입니다.
이동 가능한 타일이라는 것은 북극곰이 현재 있는 타일과 맞닿은 타일인지를 체크하는 것입니다.
tag를 이용하여 col과 row를 구해 터치된 타일이 북극곰이 있는 타일의 +1 ~ -1사이에 있는지를 체크합니다
즉 row가 같다면 col + 1과 col - 1이 이동가능한 타일이고 col이 같다면 row + 1과 row - 1이 이동가능한 타일입니다.
----------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);
};
----------GameScene.cpp----------
…생략…
Menu* GameScene::getMaptile(int row, int col, int type){
…생략…
//좌측 아래부터 tag를 1 우측 위를 20으로 계산하여 넣음
int tag = col + (5 * row) + 1;
log("mapArr[%d][%d], tag = %d", row, col, tag);
//타일의 위치를 tag로 구분하기 위해 tag를 지정해줌
menu->setTag(tag);
//type가 1이면 북극곰이 위치하는 곳이므로
if (type == 1)
_polarbearCurrentTag = tag;
return menu;
}
…생략…
void GameScene::onClickTile(Ref *object){
auto tile = (MenuItem *)object;
log("tag = %d", tile->getParent()->getTag());
bool isMove = checkTileMove(tile->getParent()->getTag(), _polarbearCurrentTag);
}
bool GameScene::checkTileMove(int tileTag, int bearTag){
log("tag = %d", tileTag);
//tag를 분해하여 col과 row를 구한다.
int col = (tileTag - 1) % 5;
int row = (tileTag - 1 - col) / 5;
log("col = %d, row = %d", col, row);
//북극곰의 현재 위치의 tag를 분해하여 col과 row를 구한다.
log("polarbear_current_tag = %d", bearTag);
int bearCol = (bearTag - 1) % 5;
int bearRow = (bearTag - 1 - bearCol) / 5;
log("bearCol = %d, bearRow = %d", bearCol, bearRow);
if (tileTag == bearTag){
//북극곰의 현재위치로는 이동이 불가능하다.
log("move false");
return false;
}
else if ((col <= bearCol + 1 && col >= bearCol - 1) && (row <= bearRow + 1 && row >= bearRow - 1)){
//이동 가능한지를 체크한다. 북극곰이 위치한타일의 col +- 1에 위치해야하며 row도 +- 1에 위치한 곳만 이동가능
if (!(col == bearCol - 1 && row == bearRow + 1) && !(col == bearCol - 1 && row == bearRow - 1) && !(col == bearCol + 1 && row == bearRow - 1) && !(col == bearCol + 1 && row == bearRow + 1)){
//대각선 부분이 아니면 이동 가능하다.
log("move true");
return true;
}
else{
//대각선 부분이면 이동이 불가능하다.
log("move false");
return false;
}
}
else{
//이동이 불가능 한 타일이다.
log("move false");
return false;
}
}
북극곰의 현재위치의 tag를 변수에 저장하고 클릭된 tag와 비교하여 checkMoveTile()메소드에서 이동 가능한 타일인지 확인해주었습니다.
디버거를 실행하고 타일을 클릭하고 로그를 살펴봅니다.
Figure 11 이동 체크 로그
로그를 살펴보면 북극곰의 위치tag와 클릭한 타일의 tag가 나오며 이동이 가능한지 불가능한지 체크되었습니다.
그럼 이제 이동이 가능한 타일을 선택했을 경우 북극곰을 이동시켜보도록 하겠습니다.
북극곰 클래스에 setMove() 메소드를 생성하여 이동시키도록 하겠습니다.
----------Polarbear.h----------
#include "cocos2d.h"
USING_NS_CC;
class Polarbear :public Layer
{
public:
Polarbear();
~Polarbear();
static Polarbear * create();
bool init();
Sprite *_polarbear;
//대기에니메이션
Action *_wait;
//왼쪽으로 뛰는 에니메이션
Action *_jumpLeft;
//위쪽으로 뛰는 에니메이션
Action *_jumpTop;
//아래로 뛰는 에니메이션
Action *_jumpBottom;
//0 : 대기, 1 : 왼쪽 점프, 2 : 오른쪽 점프, 3 : 위쪽 점프, 4 : 아래쪽 점프
void setAnimation(int type);
void setMoveTo(Point moveTo);
};
----------Polarbear.cpp----------
…생략…
void Polarbear::setMoveTo(Point moveTo){
this->setPosition(moveTo);
}
Point를 입력받아 Polarbear 클래스에서 해당 Point에 setPosition()을 해주었습니다.
setMoveTo() 메소드를 호출해보도록 하겠습니다.
----------GameScene.cpp----------
…생략…
void GameScene::onClickTile(Ref *object){
auto tile = (MenuItem *)object;
log("tag = %d", tile->getParent()->getTag());
bool isMove = checkTileMove(tile->getParent()->getTag(), _polarbearCurrentTag);
//이동이 불가능한 타일을 선택하면 return 해버린다.
if (isMove == false)
return;
//북극곰 tag갱신
_polarbearCurrentTag = tile->getParent()->getTag();
//북극곰에 에니메이션 실행
_polarbear->setMoveTo(Point(tile->getPositionX() + tile->getContentSize().width / 2, tile->getPositionY() + tile->getContentSize().height / 2));
}
isMove가 false이면 return 시켰고, polarbearCurrentTag에 클릭한 tile의 tag를 추가해주었습니다.
그리고 onClickTile()에서 클릭된 타일의 위치를 받아 setMoveTo에 Point를 넘겨주었습니다.
디버거를 실행하여 타일을 클릭해봅니다.
Figure 12 실행화면
북극곰이 있는 인접한 타일을 선택하면 북극곰이 해당 타일위로 이동되었습니다.
북극곰이 이동되긴 하지만 순간이동 하듯이 이동되므로 에니메이션을 추가하도록 하겠습니다.
JumpTo 에니메이션을 사용하여 점프해서 이동하는 것처럼 보이도록 하겠습니다.
----------Polarbear.cpp----------
…생략…
void Polarbear::setMoveTo(Point moveTo){
auto action = JumpTo::create(1, moveTo, 20, 1);
this->runAction(action);
}
JumpTo는 에니메이션 동작 시간과 이동거리, 점프할 높이 그리고 점프 횟수를 입력받아 에니메이션을 실행시킵니다.
에니메이션을 추가하였으니 디버거를 실행해 확인해보도록 합니다.
Figure 13 실행화면
JumpTo에니메이션으로 북극곰이 이동되었습니다.
이동하는 도중에 Sprite를 변경하여 좀더 다이나믹하게 이동되도록 수정하겠습니다.
에니메이션은 Polarbear 클래스를 생성할 때 이미 추가해두었으므로 정의하여 setAnimaiton()함수 내부에서 사용하도록 하겠습니다.
----------Polarbear.h----------
#include "cocos2d.h"
USING_NS_CC;
class Polarbear :public Layer
{
public:
Polarbear();
~Polarbear();
static Polarbear * create();
bool init();
Sprite *_polarbear;
//대기에니메이션
Action *_wait;
//왼쪽으로 뛰는 에니메이션
Action *_jumpLeft;
//위쪽으로 뛰는 에니메이션
Action *_jumpTop;
//아래로 뛰는 에니메이션
Action *_jumpBottom;
//0 : 대기, 1 : 왼쪽 점프, 2 : 오른쪽 점프, 3 : 위쪽 점프, 4 : 아래쪽 점프
void setAnimation(int type);
void setMoveTo(Point moveTo);
//wait 에니메이션을 동작시키는 메소드
void setWait();
};
----------Polarbear.cpp----------
…생략…
Polarbear::~Polarbear(void)
{
_wait->release();
_jumpLeft->release();
}
…생략…
bool Polarbear::init(){
{
//대기 에니메이션 생성 / 추가
SpriteFrame *frame[6];
//1->2->3->4->2->1 순으로 이미지 반복
frame[0] = SpriteFrame::create("w_bear01.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear03.png", Rect(0, 0, 43, 70));
frame[3] = SpriteFrame::create("w_bear04.png", Rect(0, 0, 43, 70));
frame[4] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[5] = SpriteFrame::create("w_bear01.png", Rect(0, 0, 43, 70));
//스프라이트 추가
_polarbear = Sprite::createWithSpriteFrame(frame[0]);
_polarbear->setPosition(Point(this->getContentSize().width / 2, this->getContentSize().height / 2));
this->addChild(_polarbear);
//프레임을 Array에 추가
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
AniFrames.pushBack(frame[3]);
AniFrames.pushBack(frame[4]);
AniFrames.pushBack(frame[5]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.3f);
_wait = RepeatForever::create(Animate::create(animation));
_wait->retain();
//대기에니메이션을 적용
_polarbear->runAction(_wait);
}
{
//좌측 점프 에니메이션 생성 / 추가
SpriteFrame *frame[5];
//2->5->6->7->5 순으로 1번만 실행
frame[0] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear05.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear06.png", Rect(0, 0, 43, 70));
frame[3] = SpriteFrame::create("w_bear07.png", Rect(0, 0, 43, 70));
frame[4] = SpriteFrame::create("w_bear05.png", Rect(0, 0, 43, 70));
//프레임을 Array에 추가
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
AniFrames.pushBack(frame[3]);
AniFrames.pushBack(frame[4]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.2f);
_jumpLeft = Animate::create(animation);
_jumpLeft->retain();
}
return true;
}
void Polarbear::setAnimation(int type){
//기존 스프라이트 에니메이션을 멈춘다.
_polarbear->stopAllActions();
switch (type)
{
case 0:
_polarbear->runAction(_wait);
break;
case 1:
_polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));
break;
}
}
void Polarbear::setMoveTo(Point moveTo){
auto action = JumpTo::create(1, moveTo, 20, 1);
this->runAction(action);
setAnimation(1);
}
void Polarbear::setWait(){
setAnimation(0);
}
_jumpLeft를 정의하고 setAnimation에서 type에 따라 에니메이션을 동작시킵니다.
jump에니메이션이 완료되면 setWait()메소드를 호출하여 wait에니메이션을 다시 동작시켰습니다.
디버거를 실행하여 점프 스프라이트가 완료된후 대기 스프라이트가 실행되는지 확인해보도록 합니다.
Figure 14 실행화면
점프 스프라이트가 실행되고 대기 스프라이트가 반복 실행되는 것을 확인 할 수 있습니다.
하지만 에니메이션이 동작중에 다른 타일이 눌려 이상한 곳으로 에니메이션이 동작됩니다.
Figure 15 실행화면
이것을 막기위해 에니메이션 동작중인지 체크할 수 있는 변수를 추가하도록 하겠습니다.
----------Polarbear.h----------
#include "cocos2d.h"
USING_NS_CC;
class Polarbear :public Layer
{
public:
Polarbear();
~Polarbear();
static Polarbear * create();
bool init();
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;
};
----------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;
}
…생략…
void Polarbear::setAnimation(int type){
//기존 스프라이트 에니메이션을 멈춘다.
_polarbear->stopAllActions();
switch (type)
{
case 0:
_polarbear->runAction(_wait);
_isAnimation = false;
break;
case 1:
_polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));
break;
}
}
bool Polarbear::setMoveTo(Point moveTo){
if (_isAnimation)
return false;
_isAnimation = true;
auto action = JumpTo::create(1, moveTo, 20, 1);
this->runAction(action);
setAnimation(1);
return true;
}
setMoveTo의 return형식을 bool으로 변경하였습니다.
_isAnimation이란 변수를 추가하고 _isAnimation이 true이면 setMoveTo에서 false를 return 하도록 추가하였습니다.
setMoveTo()를 호출하는데 false가 호출되면 에니메이션이 동작중이라 setMoveTo()에 실패했다고 볼 수 있습니다.
에니메이션이 끝나면 대기에니메이션이 호출되므로 setAnimation()에서 type이 0인경우 에니메이션이 종료됐다고 볼 수 있습니다.
그럼 GameScene에서 setMoveTo()의 결과를 체크하여 로직을 수정하도록 합니다.
----------GameScene.cpp----------
…생략…
void GameScene::onClickTile(Ref *object){
auto tile = (MenuItem *)object;
log("tag = %d", tile->getParent()->getTag());
bool isMove = checkTileMove(tile->getParent()->getTag(), _polarbearCurrentTag);
//이동이 불가능한 타일을 선택하면 return 해버린다.
if (isMove == false)
return;
//북극곰에 에니메이션 실행
bool result = _polarbear->setMoveTo(Point(tile->getPositionX() + tile->getContentSize().width / 2, tile->getPositionY() + tile->getContentSize().height / 2));
if (result){
//북극곰 tag갱신
_polarbearCurrentTag = tile->getParent()->getTag();
}
}
setMoveTo()의 return값을 result에 받아 result가 true이면 북극곰의 tag를 갱신해주었습니다.
디버거를 실행하여 에니메이션 동작중에 다른 타일이 눌리는지 체크해봅니다.
Figure 16 실행화면
에니메이션이 끝나야 다른타일이 눌리는 것을 확인할 수 있습니다.
이번에는 오른쪽으로 뛰는 점프와 위 아래로 뛰는 점프를 구분하여 스프라이트를 추가하도록 하겠습니다.
먼저 오른쪽으로 뛰는 점프를 추가하도록 하겠습니다.
오른쪽으로 뛰는 점프는 왼쪽으로 뛰는 점프를 flip하여 사용할 것 입니다.
----------Polarbear.cpp----------
…생략…
void Polarbear::setAnimation(int type){
//기존 스프라이트 에니메이션을 멈춘다.
_polarbear->stopAllActions();
switch (type)
{
case 0:
_polarbear->runAction(_wait);
_isAnimation = false;
break;
case 1:
_polarbear->setFlipX(false);
_polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));
break;
case 2:
_polarbear->setFlipX(true);
_polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));
break;
}
}
bool Polarbear::setMoveTo(Point moveTo){
if (_isAnimation)
return false;
_isAnimation = true;
auto action = JumpTo::create(1, moveTo, 20, 1);
this->runAction(action);
Point beforePoint = this->getPosition();
if (beforePoint.x > moveTo.x)
setAnimation(1);
else if (beforePoint.x < moveTo.x)
setAnimation(2);
return true;
}
setFlipX(true)를 호출하면 sprite가 좌우가 반전됩니다. 따라서 왼쪽으로 점프하는 스프라이트를 접으면 오른쪽으로 점프하는 스프라이트가 됩니다.
setFlipX(false)를 호출하면 반전된 스프라이트는 원상태로 돌아옵니다. 따라서 왼쪽점프 일때는 false를 오른쪽으로 점프할때는 true를 호출해주었습니다.
setMoveTo()에서 원래 있던 Position을 받아 비교하여 왼쪽으로 점프했는지 오른쪽으로 점프했는지를 구분하였습니다.
디버거를 실행하여 왼쪽 오른쪽 점프가 제대로 동작하는지 확인합니다.
Figure 17 실행화면
좌우 에니메이션이 제대로 동작되었습니다. 위 아래로 이동할경우 조건문에 걸리지 않기 때문에 지금은 정상 동작하지 않습니다.
이제 위 아래로 이동하는 에니메이션도 추가하도록 하겠습니다.
----------Polarbear.cpp----------
…생략…
Polarbear::~Polarbear(void)
{
_wait->release();
_jumpLeft->release();
_jumpTop->release();
_jumpBottom->release();
}
…생략…
bool Polarbear::init(){
{
//대기 에니메이션 생성 / 추가
SpriteFrame *frame[6];
//1->2->3->4->2->1 순으로 이미지 반복
frame[0] = SpriteFrame::create("w_bear01.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear03.png", Rect(0, 0, 43, 70));
frame[3] = SpriteFrame::create("w_bear04.png", Rect(0, 0, 43, 70));
frame[4] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[5] = SpriteFrame::create("w_bear01.png", Rect(0, 0, 43, 70));
//스프라이트 추가
_polarbear = Sprite::createWithSpriteFrame(frame[0]);
_polarbear->setPosition(Point(this->getContentSize().width / 2, this->getContentSize().height / 2));
this->addChild(_polarbear);
//프레임을 Array에 추가
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
AniFrames.pushBack(frame[3]);
AniFrames.pushBack(frame[4]);
AniFrames.pushBack(frame[5]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.3f);
_wait = RepeatForever::create(Animate::create(animation));
_wait->retain();
//대기에니메이션을 적용
_polarbear->runAction(_wait);
}
{
//좌측 점프 에니메이션 생성 / 추가
SpriteFrame *frame[5];
//2->5->6->7->5 순으로 1번만 실행
frame[0] = SpriteFrame::create("w_bear02.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear05.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear06.png", Rect(0, 0, 43, 70));
frame[3] = SpriteFrame::create("w_bear07.png", Rect(0, 0, 43, 70));
frame[4] = SpriteFrame::create("w_bear05.png", Rect(0, 0, 43, 70));
//프레임을 Array에 추가
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
AniFrames.pushBack(frame[3]);
AniFrames.pushBack(frame[4]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.2f);
_jumpLeft = Animate::create(animation);
_jumpLeft->retain();
}
{
//위로 점프 에니메이션 생성 / 추가
SpriteFrame *frame[3];
//11->12->13 순으로 1번만 실행
frame[0] = SpriteFrame::create("w_bear11.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear12.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear13.png", Rect(0, 0, 43, 70));
//프레임을 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);
_jumpTop = Animate::create(animation);
_jumpTop->retain();
}
{
//아래로 점프 에니메이션 생성 / 추가
SpriteFrame *frame[3];
//8->9->10 순으로 1번만 실행
frame[0] = SpriteFrame::create("w_bear08.png", Rect(0, 0, 43, 70));
frame[1] = SpriteFrame::create("w_bear09.png", Rect(0, 0, 43, 70));
frame[2] = SpriteFrame::create("w_bear10.png", Rect(0, 0, 43, 70));
//프레임을 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);
_jumpBottom = Animate::create(animation);
_jumpBottom->retain();
}
return true;
}
void Polarbear::setAnimation(int type){
//기존 스프라이트 에니메이션을 멈춘다.
_polarbear->stopAllActions();
switch (type)
{
case 0:
_polarbear->runAction(_wait);
_isAnimation = false;
break;
case 1:
_polarbear->setFlipX(false);
_polarbear->runAction(Sequence::createWithTwoActions((ActionInterval *)_jumpLeft, CallFunc::create(CC_CALLBACK_0(Polarbear::setWait, this))));
break;
case 2:
_polarbear->setFlipX(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;
}
}
bool Polarbear::setMoveTo(Point moveTo){
if (_isAnimation)
return false;
_isAnimation = true;
auto action = JumpTo::create(1, moveTo, 20, 1);
this->runAction(action);
Point beforePoint = this->getPosition();
if (beforePoint.x > moveTo.x)
setAnimation(1);
else if (beforePoint.x < moveTo.x)
setAnimation(2);
else if (beforePoint.y < moveTo.y)
setAnimation(3);
else if (beforePoint.y > moveTo.y)
setAnimation(4);
return true;
}
_jumpTop과 _jumpBottom 을 추가하였습니다.
y값을 체크하여 _jumpTop인지 _jumpBottom인지 체크하여 에니메이션을 동작시켰습니다.
디버거를 실행해 에니메이션을 확인해보도록 합니다.
Figure 18 실행화면
에니메이션이 제대로 동작되는 것을 확인할 수 있습니다.
북극곰이 이동하면 북극곰이 있던 빙하는 사라져야 합니다.
먼저 빙하가 fadeOut되는 에니메이션을 우선 구현해보도록 하겠습니다.
----------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;
};
----------GameScene.cpp----------
…생략…
Menu* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
auto tileMenu = MenuItemSprite::create(tile, NULL, CC_CALLBACK_1(GameScene::onClickTile, this));
//row와 col을 이용하여 현재위치를 설정하였다.
tileMenu->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
tileMenu->setAnchorPoint(Point(0, 0));
auto menu = Menu::create(tileMenu, NULL);
menu->setPosition(Point::ZERO);
//type가 0이거나 1이거나 3이면 일반 빙하타일, 1이나 3은 일반 빙하타일위에 오브젝트가 올라가므로
if (type == 0 || type == 1 || type == 3){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
ice->setTag(1);
tile->addChild(ice);
if (type == 1){
//시작점이므로 여기에 북극곰 추가
_polarbear = Polarbear::create();
_polarbear->setAnchorPoint(Point(0.5f, 0));
//북극곰은 mapLayer에 추가되므로 m_tile의 위치를 가져오는 것으로 수정.
_polarbear->setPosition(Point(tileMenu->getPositionX() + tileMenu->getContentSize().width / 2, tileMenu->getPositionY() + tileMenu->getContentSize().height / 2));
_mapLayer->addChild(_polarbear, 5);
//현재위치인 시작점의 타일을 담아주었다.
_beforeIceTile = ice;
_beforeIceTile->setTag(1);
}
else if (type == 3){
//펭귄 추가
auto penguin = getPenguinSprite();
penguin->setAnchorPoint(Point(0.5f, 0));
penguin->setPosition(Point(ice->getContentSize().width / 2, 25));
ice->addChild(penguin);
}
}
else if (type == 2){
//type가 2이면 골인 지점
auto ice = Sprite::create("goal.png");
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
ice->setTag(1);
tile->addChild(ice);
//깃발 추가
auto flag = getGoalFlagSprite();
flag->setAnchorPoint(Point(0.5f, 0));
flag->setPosition(Point(50, 35));
ice->addChild(flag);
}
else if (type == 4){
//type가 4이면 무너지는 빙하타일, 밟으면 게임오버
std::string iceImg;
//row에 따라 약간 다른 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "break1_default.png";
break;
case 1:
iceImg = "break2_default.png";
break;
case 2:
iceImg = "break3_default.png";
break;
case 3:
iceImg = "break4_default.png";
break;
}
auto breakIce = Sprite::create(iceImg.c_str());
breakIce->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
breakIce->setTag(1);
tile->addChild(breakIce);
}
//좌측 아래부터 tag를 1 우측 위를 20으로 계산하여 넣음
int tag = col + (5 * row) + 1;
log("mapArr[%d][%d], tag = %d", row, col, tag);
//타일의 위치를 tag로 구분하기 위해 tag를 지정해줌
menu->setTag(tag);
//type가 1이면 북극곰이 위치하는 곳이므로
if (type == 1)
_polarbearCurrentTag = tag;
return menu;
}
…생략…
void GameScene::onClickTile(Ref *object){
auto tile = (MenuItem *)object;
log("tag = %d", tile->getParent()->getTag());
bool isMove = checkTileMove(tile->getParent()->getTag(), _polarbearCurrentTag);
//이동이 불가능한 타일을 선택하면 return 해버린다.
if (isMove == false)
return;
//북극곰에 에니메이션 실행
bool result = _polarbear->setMoveTo(Point(tile->getPositionX() + tile->getContentSize().width / 2, tile->getPositionY() + tile->getContentSize().height / 2));
if (result){
//북극곰이 있던 타일의 sprite에 fadeout에니메이션을 추가해주었다.
_beforeIceTile->runAction(FadeOut::create(1));
//MenuItem의 tag 1은 normal, 2는 select, 3은 disable이미지로 되어있다.
//따라서 tile에 1번 tag를 가진 자식을 가져와 normal 이미지의 노드를 가져왔다.
//이 노드에서 1번 tag를 가진 자식노드를 다시가져와 빙하 sprite를 가져왔다.
_beforeIceTile = (Sprite *)tile->getChildByTag(1)->getChildByTag(1);
//북극곰 tag갱신
_polarbearCurrentTag = tile->getParent()->getTag();
}
}
_beforeIceTile라는 Sprite 변수에 북극곰이 있는 타일의 스프라이트를 담아 북극곰이 타일을 이동하게 되면 북극곰이 있던자리의 빙하 스프라이트에 fadeOut에니메이션을 주어 빙하가 사라지도록 하였습니다.
디버거를 실행해 북극곰을 이동시켜보도록 합니다.
Figure 19 실행화면
북극곰이 이동하면서 이전에 있던 빙하 이미지가 사라지는 것을 볼 수 있습니다.
그러나 이미 사라진 자리로 북극곰이 이동할 수 있습니다.
Figure 20 실행화면
이부분에 대한 예외처리를 하도록 해보겠습니다.
----------GameScene.cpp----------
…생략…
void GameScene::onClickTile(Ref *object){
auto tile = (MenuItem *)object;
log("tag = %d", tile->getParent()->getTag());
bool isMove = checkTileMove(tile->getParent()->getTag(), _polarbearCurrentTag);
//이동이 불가능한 타일을 선택하면 return 해버린다.
if (isMove == false || tile->getParent()->getTag() < 0)
return;
//북극곰에 에니메이션 실행
bool result = _polarbear->setMoveTo(Point(tile->getPositionX() + tile->getContentSize().width / 2, tile->getPositionY() + tile->getContentSize().height / 2));
if (result){
//북극곰이 있던 타일의 sprite에 fadeout에니메이션을 추가해주었다.
_beforeIceTile->runAction(FadeOut::create(1));
//tag를 -1로 지정하였습니다. menu > tile > tile의 normal 이미지 > beforeIceTile 순으로 자식노드가 구성되어있다.
//따라서 menu의 tag를 변경해야 하므로 beforeIceTile->getParent()->getParent()->getParent()를 호출해 menu를 가져왔다.
_beforeIceTile->getParent()->getParent()->getParent()->setTag(-1);
//MenuItem의 tag 1은 normal, 2는 select, 3은 disable이미지로 되어있다.
//따라서 tile에 1번 tag를 가진 자식을 가져와 normal 이미지의 노드를 가져왔다.
//이 노드에서 1번 tag를 가진 자식노드를 다시가져와 빙하 sprite를 가져왔다.
_beforeIceTile = (Sprite *)tile->getChildByTag(1)->getChildByTag(1);
//북극곰 tag갱신
_polarbearCurrentTag = tile->getParent()->getTag();
}
}
북극곰이 있던 타일의 menu를 가져와 tag를 -1로 지정하였고 선택된 타일의 tag가 0보다 작으면 return이 되도록 하였습니다.
디버거를 실행해 빙하가 사라진 타일로 북극곰이 이동할 수 없는지 확인합니다.
Figure 21 실행화면
사라진 타일로 이동할 수 없고 고립되어 아무곳으로도 이동할 수 없습니다.
금이가있는 빙하를 밟으면 게임오버가 되야합니다.
먼저 게임오버 팝업을 구현하기전에 밟은 타일이 금이가있는 빙하인지 체크하는 로직을 만들어 보도록 하겠습니다.
북극곰 클래스에서 GameScene으로 에니메이션이 끝나면 콜백메소드를 호출해보도록 하겠습니다.
북극곰 클래스의 create() 메소드를 수정하도록 합니다..
----------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();
};
----------Polarbear.cpp----------
…생략…
Polarbear * Polarbear::create(Node *listener, SEL_CallFunc selector){
Polarbear *ret = new Polarbear();
if (ret && ret->init(listener, selector))
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
bool Polarbear::init(Node *listener, SEL_CallFunc selector){
_listener = listener;
_selector = selector;
…생략…
void Polarbear::setWait(){
setAnimation(0);
callCallback();
}
void Polarbear::callCallback(){
//에니메이션이 완료되면 callback메소드 호출
if (_listener && _selector)
(_listener->*_selector)();
}
create()메소드에서 listener와 selector를 입력받아 지정된 콜백메소드를 호출하도록 구현하였습니다.
에니메이션이 동작후 setWait()가 호출되므로 해당 메소드 내부에서 콜백메소드를 호출하도록 하였습니다.
Polarbear의 클래스를 수정하였으므로 GameScene에서 Polarbear를 생성하는 부분을 수정하고 콜백메소드를 지정하도록 하겠습니다.
----------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();
};
----------GameScene.cpp----------
…생략…
Menu* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
auto tileMenu = MenuItemSprite::create(tile, NULL, CC_CALLBACK_1(GameScene::onClickTile, this));
//row와 col을 이용하여 현재위치를 설정하였다.
tileMenu->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
tileMenu->setAnchorPoint(Point(0, 0));
auto menu = Menu::create(tileMenu, NULL);
menu->setPosition(Point::ZERO);
//type가 0이거나 1이거나 3이면 일반 빙하타일, 1이나 3은 일반 빙하타일위에 오브젝트가 올라가므로
if (type == 0 || type == 1 || type == 3){
std::string iceImg;
//row에 따라 약간 다른 빙하 이미지를 불러온다.
switch (row)
{
case 0:
iceImg = "ice_1.png";
break;
case 1:
iceImg = "ice_2.png";
break;
case 2:
iceImg = "ice_3.png";
break;
case 3:
iceImg = "ice_4.png";
break;
}
auto ice = Sprite::create(iceImg.c_str());
ice->setPosition(Point(tile->getContentSize().width / 2, tile->getContentSize().height / 2));
ice->setTag(1);
tile->addChild(ice);
if (type == 1){
//시작점이므로 여기에 북극곰 추가
_polarbear = Polarbear::create(this, callfunc_selector(GameScene::polarbearAnimationFinish));
_polarbear->setAnchorPoint(Point(0.5f, 0));
//북극곰은 mapLayer에 추가되므로 m_tile의 위치를 가져오는 것으로 수정.
_polarbear->setPosition(Point(tileMenu->getPositionX() + tileMenu->getContentSize().width / 2, tileMenu->getPositionY() + tileMenu->getContentSize().height / 2));
_mapLayer->addChild(_polarbear, 5);
//현재위치인 시작점의 타일을 담아주었다.
_beforeIceTile = ice;
_beforeIceTile->setTag(1);
}
…생략…
void GameScene::polarbearAnimationFinish(){
log("polarbearAnimationFinish");
}
Polarbear를 생성하면서 listener와 selector를 지정하였고 콜백메소드를 생성하고 로그를 출력하도록 하였습니다.
디버거를 실행하고 북극곰이 이동하고 에니메이션이 끝나면 해당 콜백메소드가 호출되는지 확인하도록 합니다.
Figure 22 실행화면
에니메이션이 끝나고 로그를 살펴보도록 합니다.
Figure 23 로그
polarbearAnimationFinish가 정상적으로 출력되는 것을 확인할 수 있습니다.
그럼이제 polarbearAnimationFinish()에서 밟은 타일의 타입을 체크하도록 하겠습니다.
먼저 이동한 타일의 col과 row를 구하도록 합니다.
----------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);
}
_polarbearCurrentTag를 분해하여 col과 row를 알아냈습니다.
이동한 타일의 col과 row가 맞는지 디버거를 실행하여 북극곰을 이동시키고 확인해보도록 합니다.
Figure 24 실행화면
Figure 25 로그
이동한곳의 col이 3, row가 0으로 출력됐습니다. 제대로 출력되는 것을 확인 할 수 있습니다.
이번엔 분해한 col과 row를 이용하여 이동한 타일의 타입을 알아보도록 합니다.
col과 row를 이용하여 type을 리턴하는 메소드를 하나 생성하도록 합니다.
----------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);
};
----------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);
}
int GameScene::getTileType(int col, int row){
int type;
switch (_stage)
{
case 1:
type = STAGE1[row][col];
break;
case 2:
type = STAGE2[row][col];
break;
case 3:
type = STAGE3[row][col];
break;
}
return type;
}
col과 row를 입력받아 type을 return 하는 메소드를 생성하였고 이 메소드를 이용하여 type을 알아냈습니다.
디버거를 실행해 빙하 타일에 맞게 type이 출력되는지 확인합니다.
Figure 26 실행화면
Figure 27 펭귄이 있는 빙하는 type이 3
Figure 28 실행화면
Figure 29 일반 빙하는 type이 0
Figure 30 실행화면
Figure 31 금이간 빙하는 type이 4
Figure 32 실행화면
Figure 33 목적지는 type이 2
type이 정상적으로 출력되는 것을 알 수 있습니다.
그럼이제 이 type을 이용하여 금이간 타일을 밟았을 경우 에니메이션과 게임오버가 되도록 구현하겠습니다.
먼저 swich구문을 이용해 type을 구분해줍니다.
----------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: //펭귄 구출
break;
case 4: //금이간 빙하
break;
}
}
swich구문을 이용해 type을 구분해주었습니다.
type이 4가 나오면 빙하가 깨지는 에니메이션을 추가하도록 하겠습니다.
----------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);
};
----------GameScene.cpp----------
…생략…
Menu* GameScene::getMaptile(int row, int col, int type){
auto tile = LayerColor::create(Color4B(255, 0, 0, 0), 96, 55);
auto tileMenu = MenuItemSprite::create(tile, NULL, CC_CALLBACK_1(GameScene::onClickTile, this));
//row와 col을 이용하여 현재위치를 설정하였다.
tileMenu->setPosition(Point(col * tile->getContentSize().width, row * tile->getContentSize().height));
tileMenu->setAnchorPoint(Point(0, 0));
//타일의 메뉴를 __mapLayer에서 구분하여 가져오기위해 tag를 지정하였다.
tileMenu->setTag(1);
auto menu = Menu::create(tileMenu, NULL);
menu->setPosition(Point::ZERO);
…생략…
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: //펭귄 구출
break;
case 4: //금이간 빙하
runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));
break;
}
}
…생략…
//빙하 무너지는 에니메이션을 실행시키는 메소드
void GameScene::runIceBreakAnimation(Sprite *ice){
std::string iceImg;
int row = (ice->getParent()->getParent()->getParent()->getTag() - 1) / 5;
//지정한 tag를 이용하여 row를 계산해주었다.
switch (row)
{
case 0:
iceImg = "break1.png";
break;
case 1:
iceImg = "break2.png";
break;
case 2:
iceImg = "break3.png";
break;
case 3:
iceImg = "break4.png";
break;
}
auto texture = TextureCache::getInstance()->addImage(iceImg.c_str());
float textureWidth = texture->getContentSize().width / 5;
float textureHeight = texture->getContentSize().height;
SpriteFrame *frame[5];
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));
frame[3] = SpriteFrame::createWithTexture(texture, Rect(textureWidth * 3, 0, textureWidth, textureHeight));
frame[4] = SpriteFrame::createWithTexture(texture, Rect(textureWidth * 4, 0, textureWidth, textureHeight));
Vector<SpriteFrame*> AniFrames;
AniFrames.pushBack(frame[0]);
AniFrames.pushBack(frame[1]);
AniFrames.pushBack(frame[2]);
AniFrames.pushBack(frame[3]);
AniFrames.pushBack(frame[4]);
auto animation = Animation::createWithSpriteFrames(AniFrames);
animation->setDelayPerUnit(0.1f);
auto animate = Animate::create(animation);
ice->runAction(animate);
}
tileMenu에 tag를 1로 지정해주었습니다.
따라서 해당 타일의 스프라이트를 가져올려면
_maplayer에서 _polarbearCurrentTag를 가져오면 menu가 나오고 menu에서 1을 가져오면 tileMenu가 나오고 tileMenu에서 1번을 가져오면 tile이 나오고 이 타일에서 1을 가져오면 빙하인 ice 스프라이트가 나옵니다.
이 스프라이트를 runIceBreakAnimation()에 넘겨주면 해당 스프라이트에 에니메이션이 동작하도록 한 것 입니다.
디버거를 실행해 북극곰이 깨진 빙하를 밟으면 빙하가 깨지는지 확인하도록 합니다.
Figure 34 실행화면
깨진 빙하를 밟았더니 빙하가 깨지는 에니메이션이 동작되었습니다.
빙하가 깨지면서 북극곰도 바다에 빠지는 에니메이션을 만들어 보도록 하겠습니다.
----------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);
};
----------Polarbear.cpp----------
…생략…
void Polarbear::fallSea(Node *listener, SEL_CallFunc selector){
_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);
}
fallSea()메소드는 콜백메소드를 받아 에니메이션이 끝나면 해당 콜백메소드를 호출합니다.
MoveBy에니메이션에 EaseAction을 적용했습니다. EaseAction은 일정한 동작시간을 좀더 다이나믹하게 만들어 낼 수 있습니다.
SineIn을 MoveBy에 적용하였습니다.
MoveBy에니메이션과 FadeOut에니메이션을 동시에 적용했습니다.
그러면 이제 콜백메소드를 생성하고 fallSea를 호출하도록 하겠습니다.
----------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();
};
----------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: //펭귄 구출
break;
case 4: //금이간 빙하
runIceBreakAnimation((Sprite *)_mapLayer->getChildByTag(_polarbearCurrentTag)->getChildByTag(1)->getChildByTag(1)->getChildByTag(1));
_polarbear->fallSea(this, callfunc_selector(GameScene::fallSeaCallBack));
break;
}
}
…생략…
void GameScene::fallSeaCallBack(){
log("fallSeaCallBack");
}
콜백메소드를 생성하고 Polarbear에서 fallSea()를 호출해주었습니다.
디버거를 실행해 에니메이션이 동작되고 콜백메소드가 호출되는지 확인해보도록 하겠습니다.
Figure 35 실행화면
Figure 36 실행화면
에니메이션이 동작되고 fallSeaCallBack이 동작되는 것을 확인할 수 있습니다.
Figure 37 실행화면
하지만 금이간 빙하를 밟고 북극곰이 바다에 빠졌지만 다른 타일이 선택되는 것을 확인할 수 있습니다.
그럼 북극곰이 바다에 빠졌는지를 체크하여 다른 타일이 눌리지 않도록 변수를 생성하여 체크하도록 하겠습니다.
----------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;
};
----------Polarbear.cpp----------
…생략…
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;
}
…생략…
bool Polarbear::setMoveTo(Point moveTo){
if (_isAnimation || _isFall)
return false;
_isAnimation = true;
auto action = JumpTo::create(1, moveTo, 20, 1);
this->runAction(action);
Point beforePoint = this->getPosition();
if (beforePoint.x > moveTo.x)
setAnimation(1);
else if (beforePoint.x < moveTo.x)
setAnimation(2);
else if (beforePoint.y < moveTo.y)
setAnimation(3);
else if (beforePoint.y > moveTo.y)
setAnimation(4);
return true;
}
…생략…
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);
}
_isFall 변수를 생성하여 _isFall이 true이면 setMoveTo()에서 false를 return하도록 하였습니다.
디버거를 실행하여 빙하에 빠지고나면 다른 타일로 이동이 되지않는지 확인합니다.
Figure 38 실행화면
빙하가 깨지고 바다에 빠지면 다른 타일로 이동이 되지않는 것을 확인할 수 있습니다.
북극곰이 바다에 빠져 게임이 끝났으므로 이 게임오버 팝업을 생성하고 게임오버가 되면 스테이지 선택화면으로 이동하도록 구현하겠습니다.
GameoverPopup이라는 이름의 클래스를 Classes 폴더에 생성합니다. [5.2] 참고.
Figure 39 클래스 추가
GameoverPopup.h 파일과 GameoverPopup.cpp 파일을 생성하였으면 아래와 같이 수정합니다.
----------GameoverPopup.h----------
#include "cocos2d.h"
USING_NS_CC;
class GameoverPopup :public Layer
{
public:
static GameoverPopup * create();
bool init();
virtual void onEnter();
bool onTouchBegan(Touch* touch, Event* event);
};
----------GameoverPopup.cpp----------
#include "GameoverPopup.h"
GameoverPopup * GameoverPopup::create(){
GameoverPopup *ret = new GameoverPopup();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
bool GameoverPopup::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 gameover = Sprite::create("gameover.png");
gameover->setPosition(Point(winSize.width / 2, winSize.height / 2));
this->addChild(gameover);
return true;
}
void GameoverPopup::onEnter(){
Layer::onEnter();
setTouchEnabled(true);
setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
}
bool GameoverPopup::onTouchBegan(Touch* touch, Event* event){
Director::getInstance()->popScene();
return true;
}
게임오버 팝업 클래스를 생성하였습니다.
그럼 GameScene에서 북극곰이 바다에 빠지는 에니메이션이 끝나면 호출되는 메소드에서 팝업을 추가하도록 합니다.
---------GameScene.cpp----------
#include "GameScene.h"
#include "BackgroundLayer.h"
#include "GameoverPopup.h"
//받은 파라메터를 담은 변수
static Node *_stageScene;
static int _stage;
…생략…
void GameScene::fallSeaCallBack(){
log("fallSeaCallBack");
this->addChild(GameoverPopup::create(), 99);
}
헤더파일에 추가하고 fallSeaCallBack()에서 GameoverPopup을 생성하여 추가해주었습니다.
디버거를 실행하여 금이간 빙하를 밟으면 빙하가 깨지면서 북극곰이 바다에 빠지는 에니메이션이 실행되고 게임오버 팝업이 호출되는지 확인해봅니다.
Figure 40 실행화면
게임오버가 호출되고 화면을 터치하면 스테이지 선택화면으로 이동됩니다.
게임오버 기능까지 구현이 완료되었습니다.