5가지 실전 게임으로 배우는 코코스2d-x(cocos2d-x) 모바일 2D 게임 개발 두번째 예제(2)

     

지난 시간에 이어 스카이디펜스 게임을 만들어 보겠습니다. cocos3.x버전에 맞게 말이죠.



 1. 배경 오브젝트 추가



저번시간에서 배경화면을 그렸는데, 이젠 배경 오브젝트를 그립니다.


다음과 같은 코드를 입력합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    auto sprite=Sprite::create();
    for (int i=0; i<2; i++) {
        sprite = Sprite::createWithSpriteFrameName("city_dark.png");
        sprite->setPosition(Point(winSize.width*(0.25f+i*0.5f),sprite->boundingBox().size.height*0.5f));
        _gameBatchNode->addChild(sprite,kForeground);
        
        sprite=Sprite::createWithSpriteFrameName("city_light.png");
        sprite->setPosition(Point(winSize.width*(0.25f+i*0.5f),sprite->boundingBox().size.height*0.9f));
        _gameBatchNode->addChild(sprite,kBackground);
        
    }
    
    for (int i=0; i<3; i++) {
        sprite=Sprite::createWithSpriteFrameName("trees.png");
        sprite->setPosition(Point(winSize.width*(0.2f+i*0.3f),sprite->boundingBox().size.height*0.5f));
        _gameBatchNode->addChild(sprite,kForeground);
    }



책에 있는 것과 다른점이라면 sprite를 CCSprite로 선언하지않고 auto로 선언한다는 점입니다. auto를 사용해서 좋은점은 메모리를 자동으로 관리해준다는 점입니다. 


위 코드를 지난시간 배경화면을 그려주었던 코드 아래 작성하고 실행시키면 다음과 같이 실행됩니다.




게임 만드는 것에 있어서 몇가지 배울점은, 같은 건물그림임에도 그림의 명도를 이용해서 입체감을 주었다는 점입니다. 가까이 있는 건물들은 진하게하고 멀리있는 건물들을 흐리게하여 원근감을 주었죠. 공부가 되네요 ^^


for문을 이용한 이유는 같은 이미지를 반복했기 때문입니다. 자세히 보면 건물의 경우 같은 그림이 두번 반복되어 있는것을 알 수있습니다. 화면을 반으로 나누었을대 왼쪽과 오른쪽이죠.


나무의 경우는 3번 반복했기때문에 다른 for문을 사용했습니다.


또 그림을 배치할때 좌우의 경우는 화면사이즈를 사용했지만 높이의 경우는 이미지 자체의 크기를 가지고 배열했습니다. 이미지 자체 사이즈는 boundingBox()메소드를 이용합니다. 


하지만 boundingBox()는 이제 안쓴다고 나오더라구요. 그래서 찾아보니 사이즈는 다음과 같은 방법으로도 사용할 수 있습니다.


sprite->getContentSize().height (width)


addChilde의 두번째 인자값을 이용해 어떤 스프라이트가 가장 위에 표시되는지 설정합니다.



2. 비트맵 폰트 레이블 생성



스마트폰 게임들을 하다보면 점수를 표시하는 것이 일반 폰트가 아니라 이미지로 되있는 폰트로 되있는 것을 볼 수 있습니다. 


이 경우 하나하나 이미지를 띄우는 것이 아니라 비트맵폰트 기능을 이용하여 실제 폰트출력을 이미지로 대체할 수 있습니다.


createGameScreen 아래에 다음과 같은 코드를 추가합니다.


1
2
3
4
5
6
7
8
9
10
11
12
auto _scoreDisplay = CCLabelBMFont::create("0", "font.fnt",winSize.width*0.3f);
    _scoreDisplay->setAnchorPoint(Point(1,0.5));
    _scoreDisplay->setPosition(winSize.width*0.8f,winSize.height*0.94f);
    this->addChild(_scoreDisplay);
    
    auto _energyDisplay = CCLabelBMFont::create("100%", "font.fnt",winSize.width*0.1f,TextHAlignment::RIGHT);
    _energyDisplay->setPosition(Point(winSize.width*0.3f,winSize.height*0.94f));
    this->addChild(_energyDisplay);
    
    auto icon = Sprite::createWithSpriteFrameName("health_icon.png");
    icon->setPosition(Point(winSize.width*0.15f,winSize.height*0.94f));
    _gameBatchNode->addChild(icon,kBackground);


2.x와 다른점은 폰트역시 auto로 선언한다는 점입니다. auto가 참 편하네요 ^^


CCLabelBMFont::create의 인자값으로는 처음 초기값으로 들어가는 문자, 폰트의 이름, 레이아웃의 크기, 정렬값입니다. 


2.x와 다른점은 텍스트 정렬이 TextHAlignment::Right로 바꼇다는 점입니다.


위와 같은 코드를 입력하고 실행하면 다음과 같은 결과가 나옵니다.




점수와 아이콘, 체력이 표시됩니다~



 3. 기타 화면 스프라이트 추가



이제 나머지 스프라이트 들도 추가합니다.


일단 구름을 추가하는데 책에서는 CCArray를 사용합니다. 하지면 3.x 에서는 CCArray사용을 권장하지 않더군요. 그래서 3.x 버전으로 한번 바꿔봤습니다. 


일단 결론부터 말하면 Array대신 Vector를 사용합니다. 헤더 파일에 다음과 같이 선언합니다.


Vector<Sprite*> _clouds;


그리고 cpp파일 init에 다음 코드를 추가합니다.


_clouds.clear();


벡터를 사용하기전에 항상 초기화하는 습관을 길러야합니다.


구름을 추가하는 코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
    auto cloud=Sprite::create();
    float cloud_y;
    
    for (int i=0; i<4; i++) {
        cloud_y= i%2==0? winSize.height*0.4f:winSize.height*0.5f;
        cloud =Sprite::createWithSpriteFrameName("cloud.png");
        cloud->setPosition(Point(winSize.width*0.1f+i*winSize.width*0.3f,cloud_y));
        _gameBatchNode->addChild(cloud,kBackground);
        _clouds.pushBack(cloud);
    }


일단 구름 스프라이트를 auto cloud로 선언합니다. cloud_y의 경우 구름이 지그재그로 나타나도록 하는 효과입니다.


중요한건 벡터에 스프라이트를 집어넣는 부분입니다. _clouds.pushBack()메소드를 통해 벡터에 차곡차곡 스프라이트를 집어넣습니다. for문이 완료되면 벡터는 4개의 cloud스프라이트를 가지고 있습니다.



다음은 폭탄과 게임메인화면, 게임오버화면을 만듭니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
auto _bomb = Sprite::createWithSpriteFrameName("bomb.png");
    _bomb->getTexture()->generateMipmap();
    _bomb->setVisible(false);
    
    Size size = _bomb->getContentSize();
    
    auto sparkle = Sprite::createWithSpriteFrameName("sparkle.png");
    sparkle->setPosition(Point(size.width*0.72f,size.height*0.72f));
    _bomb->addChild(sparkle,kMiddleground,kSpriteSparkle);
    
    auto halo = Sprite::createWithSpriteFrameName("halo.png");
    halo->setPosition(Point(size.width*0.4f,size.height*0.4f));
    _bomb->addChild(halo,kMiddleground,kSpriteHalo);
    _gameBatchNode->addChild(_bomb,kForeground);
    
    auto _shockWave = Sprite::createWithSpriteFrameName("shockwave.png");
    _shockWave->getTexture()->generateMipmap();
    _shockWave->setVisible(false);
    _gameBatchNode->addChild(_shockWave);
    
    auto _introMessage = Sprite::createWithSpriteFrameName("logo.png");
    _introMessage->setPosition(Point(winSize.width*0.5,winSize.height*0.6));
    _introMessage->setVisible(true);
    this->addChild(_introMessage,kForeground);
    
    auto _gameOverMessage = Sprite::createWithSpriteFrameName("gameover.png");
    _gameOverMessage->setPosition(Point(winSize.width*0.5f,winSize.height*0.65f));
    _gameOverMessage->setVisible(false);
    this->addChild(_gameOverMessage,kForeground);
    


Mipmap이라는 것은 스프라이트를 늘릴때 안티엘리어싱이 된다고 합니다. 안티엘리어싱이란 화면을 늘렸을때 늘어난 픽셀 공간 사이에 사이값을 집어넣는 방식입니다. 보통 "경계를 모호하게 한다" 라는 뜻으로 사용됩니다.


또 여러 스프라이트를 자식으로 삼으면, 부모 스프라이트를 키웠을때 같이 커진다고 합니다. 따라서 bomb에 여러개의 스프라이트를 자식으로 추가해서 한꺼번에 변하도록 설정하였습니다.


addChild의 3번째 인자값은 Tag로 나중에 참조할때 쓰입니다.


 4. 오브젝트 풀 생성



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void GameLayer::createPools()
{
    auto sprite=Sprite::create();
    int i;
    _meteorPoolIndex =0;
    auto _gameBatchNode = this->getChildByTag(BATCH_TAG);
    for(i=0;i<50;i++){
        sprite = Sprite::createWithSpriteFrameName("meteor.png");
        sprite->setVisible(false);
        _gameBatchNode->addChild(sprite,kMiddleground,kSpriteMeteor);
        _meteorPool.pushBack(sprite);
    }
    
    _healthPoolIndex = 0;
    
    for (i=0; i<20; i++) {
        sprite = Sprite::createWithSpriteFrameName("health.png");
        sprite->setVisible(false);
        sprite->setAnchorPoint(Point(0.5f,0.8f));
        _gameBatchNode->addChild(sprite,kMiddleground,kSpriteHealth);
        _healthPool.pushBack(sprite);
    }
    
}


일단 풀이라고 하면 뭔가 작업을 하는 단체를 뜻한다. 쓰레드 풀을 알고 있다면 이해하기 쉬울 것이다. 


이번 코드는 좀 많은 수정이 있었다. 일단 배열이 사용된것을 이전과 마찬가지로 벡터로 수정하였고, _ gameBatchNode를 사용하기 위해 getChildeByTag를 이용하여 다른 메소드에서 선언한 gameBatchNode를 불러왔다. 선언부분도 약간 바꿔주었다.


this->addChild(_gameBatchNode,BATCH_TAG);


BATCH_TAG는 헤더파일에서 아무 숫자로 #define BATCH_TAG 111;로 정의했다.

이것으로 다른 메소드에서도 BATCH_TAG를 이용해 _gameBatchNode를 불러올 수 있다.


이번 포스팅은 여기까지 하고...


다음 포스팅에서 액션 추가와 애니메이션추가 및 게임 실행에 필요한 update()메소드를 구현함으로써 마무리를 하도록 한다.

반응형

댓글

Designed by JB FACTORY