본문 바로가기

cocos2d-x/pyocopang

cocos2d-x 포코팡 류 게임만들기!! - 6 - 게임을 좀 더 제대로! (1)

터치 동작도 확인했으니, 이제 좀 더 게임스럽게 동작하도록 만들어봐야겠습니다.

처음 터치된 위치에서부터 같은 모양이 세 개 이상 연속되는지를 확인하려고 하는데요.

1. 처음 터치된 위치의 코인을 확인
2. 터치 상태에서 움직이는 동안 선택된 코인이 1에서 선택된 것과 같은지 확인
3. 터치를 떼는 순간 선택된 코인이 3개 이상이면, 선택된 코인을 모두 제거

이러한 과정을 거치면 포코팡과 비슷한 동작을 할 것 같습니다. 게임 메인동작과 관련된 부분은 내용이 많을 것 같아서 나누어 포스팅할 예정입니다. 

- 6 - 게임을 좀 더 제대로! (1)

1. 처음 터치된 위치의 코인을 확인


지난 시간에 터치된 위치를 확인하여 코인을 제거하는(보이지 않게 하는) 부분을 구현해봤는데요. 이번에는 터치를 떼는 순간 일괄적으로 제거할 것이기 때문에, 이전에 구현한 코인 visible과 관련된 부분을 삭제하고, 선택된 코인 index를  class 변수에 저장하도록 변경하겠습니다.

checkPushGameCoin() 함수 중 아래 첫 문자 '-'로 표시된 9~13번째 줄을 삭제해서 visible 설정 부분을 동작에서 제외시킵니다.

/Classes/HelloWorldScene.cpp

int HelloWorld::checkPushGameCoin(CCPoint &touchPos)                                     
{   
    int index;
    GameCoin *tmpCoin;
  
    for (index = 0; index < _gameCoins->count(); index++) {
        tmpCoin = (GameCoin*)_gameCoins->objectAtIndex(index);
        if (tmpCoin->boundingBox().containsPoint(touchPos) == true) {
-           if (tmpCoin->isVisible() == true)
-               tmpCoin->setVisible(false);
-           else
-               tmpCoin->setVisible(true);
-
                return index;
        }
}

마지막 코인 위치를 저장하기 위한 변수를 추가하여 초기화하고, checkPushGameCoin()에서 return 하는 index 값을 저장합니다.

/Classes/HelloWorldScene.h
class HelloWorld : public cocos2d::CCLayer
{
private:
    CCSize _screenSize;

    CCAction* _enemyMove;
    CCAction* _treeMove;

    CCArray* _gameCoins;

    int _lastCoin;

public:
/Classes/HelloWorldScene.cpp
void HelloWorld::initGameCoin()
{
    ...

    _lastCoin = -1;
}
void HelloWorld::ccTouchesBegan(CCSet* touches, CCEvent* event)
{
    CCTouch* touch = (CCTouch*)( touches->anyObject() );
    CCPoint location = touch->getLocation();

    _lastCoin = checkPushGameCoin(location);
}
코인 번호가 0부터 시작하기 때문에 '-1'로 초기화하고, ccTouchesBegan() 안에 _lastCoin에 선택된 코인 번호를 저장합니다.



2. 터치 상태에서 움직이는 동안 선택된 코인이 1에서 선택된 것과 같은지 확인


처음이야 무식하게 전체 중에 선택된 코인을 찾더라도, 이제는 매번 움직일 때마다 터치 이벤트가 발생할 것을 생각하면, 조금 찾는 범위를 줄여야할 것 같습니다. 다음과 같이 선택된 코인 주변의 것들만 체크하면 될 것 같습니다.

그림과 같이 7번 코인이 선택되었다면, 인접한 코인인 [0, 1, 6, 8, 12, 13] 번만 확인하면 되는 것이죠. 어차피 이 것들을 넘어서 다른 코인을 선택하는 것은 게임 동작 조건에 맞지 않기 때문입니다.


각 위치의 코인 별로 인접 코인들을 배열에 저장해보았습니다. 이차원 배열 형태로 저장하여 선택된 코인에 대해 인접한 6개 코인이 어떤 것들인지 확인할 수 있습니다. [ 0, 1, 2, ... ]번 코인이나 [6, 12, 18, ... ] 과 같이 끝부분에 위치하여 일부 코인이 없는 경우, 해당 위치를 '-1'로 표시하여 인접 코인이 없음을 확인할 수 있도록 하겠습니다.

/Classes/HelloWorldScene.h

class HelloWorld : public cocos2d::CCLayer
{
private:
    enum {
        BOARDX = 7,
        BOARDY = 6,
        TOTALCOINTYPE = 4,
        TOTALADJCOIN = 6,
    };  

    CCSize _screenSize;

    CCAction* _enemyMove;
    CCAction* _treeMove; 

    CCArray* _gameCoins; 
    CCArray* _selectMask;
    CCArray* _selectCoins;

    int _lastCoin;
    static int _adjCoin[BOARDX * BOARDY][TOTALADJCOIN];

public: 

새로 추가하는 _adjCoin 배열에 사용할 수 있도록 enum 변수의 위치를 변경하고, TOTALADJCOIN으로 인접코인 개수 '6'를 설정합니다.


/Classes/HelloWorldScene.cpp

#include "HelloWorldScene.h"

USING_NS_CC;

int HelloWorld::_adjCoin[BOARDX * BOARDY][TOTALADJCOIN] = { 
    {-1, -1,  1,  7,  6, -1},   // 0, line 1
    {-1, -1,  2,  8,  7,  0}, 
    {-1, -1,  3,  9,  8,  1}, 
    {-1, -1,  4, 10,  9,  2}, 
    {-1, -1,  5, 11, 10,  3}, 
    {-1, -1, -1, -1, 11,  4}, 
    {-1,  0,  7, 12, -1, -1},   // 6, line 2
    { 0,  1,  8, 13, 12,  6}, 
    { 1,  2,  9, 14, 13,  7}, 
    { 2,  3, 10, 15, 14,  8}, 
    { 3,  4, 11, 16, 15,  9}, 
    { 4,  5, -1, 17, 16, 10},
    { 6,  7, 13, 19, 18, -1},   // 12, line 3
    { 7,  8, 14, 20, 19, 12},
    { 8,  9, 15, 21, 20, 13},
    { 9, 10, 16, 22, 21, 14},
    {10, 11, 17, 23, 22, 15},
    {11, -1, -1, -1, 23, 16},
    {-1, 12, 19, 24, -1, -1},   // 18, line 4
    {12, 13, 20, 25, 24, 18},
    {13, 14, 21, 26, 25, 19},
    {14, 15, 22, 27, 26, 20},
    {15, 16, 23, 28, 27, 21},
    {16, 17, -1, 29, 28, 22},
    {18, 19, 25, 31, 30, -1},   // 24, line 5
    {19, 20, 26, 32, 31, 24},
    {20, 21, 27, 33, 32, 25},
    {21, 22, 28, 34, 33, 26},
    {22, 23, 29, 35, 34, 27},
    {23, -1, -1, -1, 35, 28},
    {-1, 24, 31, 36, -1, -1},   // 30, line 6
    {24, 25, 32, 37, 36, 30},
    {25, 26, 33, 38, 37, 31},
    {26, 27, 34, 39, 38, 32},
    {27, 28, 35, 40, 39, 33},
    {28, 29, -1, 41, 40, 34},
    {30, 31, 37, -1, -1, -1},   // 36, line 7
    {31, 32, 38, -1, -1, 36},
    {32, 33, 39, -1, -1, 37},
    {33, 34, 40, -1, -1, 38},
    {34, 35, 41, -1, -1, 39},
    {35, -1, -1, -1, -1, 40},
};

이제 이 정보를 바탕으로 ccTouchesMoved() 함수에서 움직이는 이벤트를 받을 때마다 처음 선택된 코인과 인접 코인을 비교해보겠습니다. 기본적으로 다음과 같은 아이디어를 가지고 만들어볼 생각입니다.

1) 이동한 위치의 코인이 처음 코인과 다른 경우, (같으면 비교할 필요가 없음)
2) 이동한 위치의 코인이 처음 코인의 인접 코인인지 확인
3) 처음 코인과 인접 코인의 종류가 같으면,
4) 선택된 코인, 코인 개수 등의 정보를 업데이트하고,
5) 선택된 인접 코인을 기준으로 위의 과정을 반복

일단, 이 과정을 만들기 전에!

지금 상태로는 어떤 코인이 선택되었는지 알기 어려워서, 선택된 코인이 어떤 것인지 확인할 수 있도록 표시를 하겠습니다. 각 코인 위에 반투명한 이미지를 배치하고, 초기에는 안보이도록 설정했다가 선택되면 보이도록 하겠습니다. 덤으로 선택된 코인을 저장할 공간도 마련하도록 하겠습니다.

아래 반투명 이미지를 프로젝트에 추가하고, 코드를 작성합니다.

여기 위를 '클릭' 해주세요!!

/Classes/HelloWorldScene.h

class HelloWorld : public cocos2d::CCLayer                                              
{   
private:
    enum {
        BOARDX = 7,
        BOARDY = 6,                                                                     
        TOTALCOINTYPE = 4,                                                              
    };
    ...                                                      
    CCAction* _treeMove;                                                                
    
    CCArray* _gameCoins;
    CCArray* _selectMask;                                                               
    CCArray* _selectCoins;                                                              
    
    int _lastCoin;                  

/Classes/HelloWorldScene.cpp

HelloWorld::~HelloWorld()
{
    CC_SAFE_RELEASE(_enemyMove);
    CC_SAFE_RELEASE(_treeMove);
    CC_SAFE_RELEASE(_gameCoins);
    CC_SAFE_RELEASE(_selectCoins);
    CC_SAFE_RELEASE(_selectMask);
} 
void HelloWorld::initGameCoin()
{
    ...
    // init array
    _gameCoins = CCArray::createWithCapacity(BOARDX * BOARDY);
    _gameCoins->retain();

    _selectCoins = CCArray::createWithCapacity(BOARDX * BOARDY);
    _selectCoins->retain();

    _selectMask = CCArray::createWithCapacity(BOARDX * BOARDY);
    _selectMask->retain();

    ...
    // selectMask setup
    CCSprite* selectMask;

    for(int xIndex = 0; xIndex < BOARDX; xIndex++) {
        coinXPos = initCoinXPos + (xIndex * diffX);
        coinYPos = initCoinYPos;
        if(xIndex % 2 == 0)
            coinYPos -= diffY/2;

        for(int yIndex = 0; yIndex < BOARDY; yIndex++) {
            selectMask = CCSprite::create("selectTile.png");
            selectMask->setVisible(false);
            selectMask->setPosition(ccp(coinXPos, coinYPos));
            selectMask->setScale(0.4f);
            this->addChild(selectMask);
            _selectMask->addObject(selectMask);

            coinYPos -= diffY;
        }
    }
   
    ...
}

CCArray 변수들의 크기를 설정하고, 선택된 코인을 표시할 _selectMask을 설정합니다. 실수로 이미지를 너무 크게 만드는 바람에 setScale(0.4f)로 원본 크기의 40% 크기로 줄여서 사용하겠습니다.

다시 게임동작으로 돌아가서, 아까 설명했던 내용을 다음과 같은 함수들로 구현해보겠습니다.

1) inLastCoin() : 이동한 위치의 코인이 처음 코인과 다른 경우, (같으면 비교할 필요가 없음)
2) checkAdjacentCoin() : 이동한 위치의 코인이 처음 코인의 인접 코인인지 확인
3) compareCoinType() : 처음 코인과 인접 코인의 종류가 같으면,
4) addSelectCoins() : 선택된 코인, 코인 개수 등의 정보를 업데이트하고,
5) 선택된 인접 코인을 기준으로 위의 과정을 반복

각 함수들을 차례로 구현해보고, 최종적으로 ccTouchesMoved()에서 조립해서 쓰도록 할 계획입니다.

/Classes/HelloWorldScene.h

    int checkAdjacentCoin(CCPoint &touchPos);
    bool inLastCoin(CCPoint &touchPos);
    int addSelectCoins(int index);
    bool compareCoinType(int index1, int index2);
};

#endif // __HELLOWORLD_SCENE_H__

위와 같이 헤더파일에 함수를 추가합니다.


1) inLastCoin()

간단하게 터치된 위치가 현재 선택된 코인의 영역 안에 포함되면 true, 포함안되면 false를 반환하도록 합니다.

/Classes/HelloWorldScene.cpp

bool HelloWorld::inLastCoin(CCPoint &touchPos)
{
    if(_lastCoin == -1) {
        return false;
    }

    GameCoin* lastCoin = (GameCoin*)_gameCoins->objectAtIndex(_lastCoin);

    if(lastCoin->boundingBox().containsPoint(touchPos) == true) {
        return true;
    }

    return false;
}


2) checkAdjacentCoin()

여기서 아까 만들었던 인접코인 리스트를 활용하겠습니다. 현재 선택된 코인 기준으로 인접코인 여섯 개를 확인하면서, 터치된 위치가 이 인접코인 영역 내부에 포함되는지를 확인합니다. 만약 포함되는 코인을 확인하면 그 코인의 번호를 반환합니다. 없으면 '-1' 를 반환해서 선택된 인접코인이 없음을 표시합니다.

/Classes/HelloWorldScene.cpp

int HelloWorld::checkAdjacentCoin(CCPoint &touchPos)
{
    int index;
    GameCoin* tmpCoin;

    if(_lastCoin < 0)
        return -1;

    for(index = 0; index < 6; index++) {
        if(_adjCoin[_lastCoin][index] == -1) {
            continue;
        }
        tmpCoin = (GameCoin*)_gameCoins->objectAtIndex(_adjCoin[_lastCoin][index]);

        if(tmpCoin->boundingBox().containsPoint(touchPos) == true) {
            return _adjCoin[_lastCoin][index];
        }
    }

    return -1;
}

인접코인이 없는 위치는 '-1'로 표시하기로 했기 때문에, '-1'인 경우 continue로 다음 인접코인을 확인합니다. 


3) compareCoinType()

이 함수도 간단하게 두 개의 코인의 종류가 같으면 true, 다르면 false를 반환합니다.

/Classes/HelloWorldScene.cpp

bool HelloWorld::compareCoinType(int index1, int index2)
{
    if (index1 < 0 || index2 < 0)
        return false;

    GameCoin* tmpCoin1 = (GameCoin*)_gameCoins->objectAtIndex(index1);
    GameCoin* tmpCoin2 = (GameCoin*)_gameCoins->objectAtIndex(index2);

    if(tmpCoin1->getType() == tmpCoin2->getType()) {
        return true;
    }

    return false;
}


4) addSelectCoins()

이 함수에서는 선택된 코인을 _selectCoins 배열에 추가하고, 해당 위치의 코인이 선택된 것을 시각적으로 표시하기 위해서 아까 전에 추가한 _selectMask의 선택된 위치 이미지를 visible로 설정합니다.

/Classes/HelloWorldScene.cpp

int HelloWorld::addSelectCoins(int index)
{
    if(index < 0) {
        return -1;
    }

    GameCoin* tmpCoin = (GameCoin*)_gameCoins->objectAtIndex(index);
    CCSprite* selectMask = (CCSprite*)_selectMask->objectAtIndex(index);

    if(tmpCoin->getState() != GameCoin::SELECT) {
        tmpCoin->setState(GameCoin::SELECT);
        _selectCoins->addObject(tmpCoin);

        selectMask->setVisible(true);
    }

    return 0;
}


5) ccTouchesMoved()에 합체!!!

이제 이렇게 만들어 놓은 함수들을 ccTouchesMoved() 안에 잘 조립하겠습니다.

/Classes/HelloWorldScene.cpp

void HelloWorld::ccTouchesMoved(CCSet* touches, CCEvent* event)
{
    CCTouch* touch = (CCTouch*)( touches->anyObject() );
    CCPoint location = touch->getLocation();
    int newCoin = -1;

    if (inLastCoin(location) == false) {
        newCoin = checkAdjacentCoin(location);

        if (compareCoinType(_lastCoin, newCoin)) {
            _lastCoin = newCoin;
            addSelectCoins(_lastCoin);
        }
    }
}

백 번 글로 설명하는 것보다 아무래도 코드가 깔끔한 것 같습니다. ccTouchesMoved() 내의 코드만 봐도, 앞서 설명한 동작을 어떻게 구현하려고 하는지 이해하실 것으로 생각합니다.

이제 잘 동작하는지 빌드를 하고, 게임을 확인해봅니다.

저는 물론 우여곡절 끝에 많은 fail 항목을 수정했기에... 잘 돌아가는군요. 하나 이상 동일한 코인들이 쭉 선택된 것을 볼 수 있습니다.


혹시나 계속 제 블로그를 보고 계시는 분들께...

글 업데이트가 너무 늦어서 죄송합니다. 3월 내에 끝내는 것이 목표였는데 어느새 4월도 중반이 되었네요. 가능하면 4월 내에 마무리하도록 노력하겠습니다!

다음 글에서는 세 개 이상 선택된 코인들을 없애도록 하겠습니다. 코드가 많아서 글이 길어보이지만 실제적인 동작은 많지 않습니다. 혹시 궁금한 점이 있으면 언제든지 댓글 남겨주세요. 자주 들어와서 확인합니다.

읽어주셔서 감사합니다!!

... 끝.