본문 바로가기

게임개발팁

cocos2d-X 게임 만들기 3편


coos2d-x 게임 만들기 2편에 이어서 3편에서는 적 스프라이트를 추가해 보고 적 스프라이트가 움직이도록 해 보겠습니다. 적 스프라이트를 화면에 넣는 메서드를 만들고 게임 루프에서 해당 메서드를 주기적으로 호출해서 여러 명의 적이 출현 하도록 만듭니다.

1. 적을 추가하는 메서드 만들기
적을 추가하는 메서드 이름을 addTarget()이라 하겠습니다. GameScene.h 와 GameScene.cpp 파일을 열어 아래와 같이 코드를 추가합니다.

GameScene.h
//: 적 스프라이트를 랜덤 위치와 속도로 화면에 추가합니다.
void addTarget();

GameScene.cpp
void GameScene::addTarget()
{
}

이제 addTarget() 함수에 코드를 추가해 보겠습니다. 코드의 설명은 주석을 참고해 주세요.
GameScene.cpp
void GameScene::addTarget()
{
    //: 적 스프라이트를 만든다.
    CCSprite *target = CCSprite::spriteWithFile("Target.png", CCRectMake(0, 0, 27, 40));
    
    //: 적 스프라이트의 Y 좌표를 랜덤으로 하여 위치를 잡는다
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    int minY = target->getContentSize().height/2;
    int maxY = winSize.height - target->getContentSize().height/2;
    int rangeY = maxY - minY;
    int actualY = ( rand() % rangeY ) + minY;
    target->setPosition( ccp(winSize.width + (target->getContentSize().width/2),
                             actualY) );
    this->addChild(target);
}

적의 위치의 x좌표는 오른쪽 화면 끝이고 y좌표는 화면 높이에서 랜덤 값을 정하여 스프라이트의 위치를 잡습니다. 이제 적 스프라이트를 움직여 봅시다.

2. 적이 움직이게 메서드 수정하기
적이 움직이게 하기 위해서 CCMove 액션을 사용할 것입니다. 아이폰에서 앱을 작성해 보신 분이면 셀렉터 개념에 익숙할 것입니다. 일종의 함수 포인터인데요.
cocos2d-x는 C++로 만들어져서 SelectorProtocol 클래스를 만들고 해당 클래스의 메서드를 가리키도록 셀렉터를 구현하였습니다. 아래는 SelectorProtocol의 클래스 선언파일입니다.

#ifndef __COCOA_SELECTOR_PROTOCOL_H__
#define __COCOA_SELECTOR_PROTOCOL_H__

#include "ccTypes.h"
#include "CCObject.h"

namespace   cocos2d {
	class CCNode;
	class CCEvent;

	class CC_DLL SelectorProtocol
	{
	public:
		virtual void update(ccTime dt) {CC_UNUSED_PARAM(dt);};
		virtual void tick(ccTime dt){CC_UNUSED_PARAM(dt);};
		virtual void callfunc(){};
		virtual void callfunc(CCNode* pSender){CC_UNUSED_PARAM(pSender);};
		virtual void callfunc(CCNode* pSender, void* pData){CC_UNUSED_PARAM(pSender);CC_UNUSED_PARAM(pData);};
		virtual void menuHandler(CCObject* pSender){CC_UNUSED_PARAM(pSender);};
		virtual void eventHandler(CCEvent* pEvent) {CC_UNUSED_PARAM(pEvent);};

		// the child call responding retain/release function
		virtual void selectorProtocolRetain(void) {};
		virtual void selectorProtocolRelease(void) {};
	};

	class CCNode;
	typedef void (SelectorProtocol::*SEL_SCHEDULE)(ccTime);

	typedef void (SelectorProtocol::*SEL_CallFunc)();
	typedef void (SelectorProtocol::*SEL_CallFuncN)(CCNode*);
	typedef void (SelectorProtocol::*SEL_CallFuncND)(CCNode*, void*);
	typedef void (SelectorProtocol::*SEL_CallFuncO)(CCObject*);
	typedef void (SelectorProtocol::*SEL_MenuHandler)(CCObject*);
	typedef void (SelectorProtocol::*SEL_EventHandler)(CCEvent*);

#define schedule_selector(_SELECTOR) (SEL_SCHEDULE)(&_SELECTOR)
#define callfunc_selector(_SELECTOR) (SEL_CallFunc)(&_SELECTOR)
#define callfuncN_selector(_SELECTOR) (SEL_CallFuncN)(&_SELECTOR)
#define callfuncND_selector(_SELECTOR) (SEL_CallFuncND)(&_SELECTOR)
#define callfuncO_selector(_SELECTOR) (SEL_CallFuncO)(&_SELECTOR)
#define menu_selector(_SELECTOR) (SEL_MenuHandler)(&_SELECTOR)
#define event_selector(_SELECTOR) (SEL_EventHandler)(&_SELECTOR)
}//namespace   cocos2d 

#endif // __COCOA_SELECTOR_PROTOCOL_H__

위의 코드를 보면 셀렉터를 지정하는 매크로가 여러개임을 알 수 있습니다. 그것은 액션에 전해지는 인자의 타입 때문에 나뉘어 진 것 입니다. 그 종류를 나열해 보면 아래와 같습니다.

  • schedule_selector
    CCNode 의 schedule 메서드로 스케쥴을 걸 때 스케쥴 로직을 수행할 함수의 포인터를 정할 때 사용합니다.
  • callfunc_selector
    cocos2d에서 액션이 끝난 다음 함수를 호출할 때 호출할 함수의 포인터를 정할 때 사용합니다.
  • callfuncN_selector
    callfunc_selector 와 같지만 셀렉터를 호출한 CCNode의 포인터도 같이 전달해 줍니다.
  • callfuncND_selector
    callfunc_selector 와 같지만 셀렉터를 호출한 CCNode의 포인터와 추가적인 인자의 포인터도 같이 전달해 줍니다. 인자는 void *로 구조체, 문자열, 변수, 객체 등을 전달할 수 있습니다.
  • callfuncO_selector
    callfunc_selector 와 같지만 CCObject를 인자로 전달합니다.
  • menu_selector
    메뉴의 셀렉터입니다.
  • event_selector
    터치 이벤트와 관련있는 셀렉터입니다.

이제 위의 함수에 move 액션을 만들고 move액션이 끝난 다음에 적 스프라이트를 화면에서 지우는 CCCallFuncN 액션을 만들겠습니다. 아래와 같이 코드를 추가해 주세요.

//GameScene.cpp
void GameScene::addTarget()
{
    ...
    
    //: 속도를 랜덤으로 결정한다.
    int minDuration = (int)2.0;
    int maxDuration = (int)4.0;
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = ( rand() % rangeDuration ) + minDuration;
    
    //: move액션을 만든다
    CCFiniteTimeAction *actionMove = CCMoveTo::actionWithDuration((ccTime)actualDuration,
                                                                  ccp(0-target->getContentSize().width/2, 
                                                                      actualY));
    
    //: 적 스프라이트가 화면 밖에 나간 후에 호출될 콜백 액션
    CCFiniteTimeAction *actionMoveDone = 
CCCallFuncN::actionWithTarget(this,
callfuncN_selector(GameScene::spriteMoveFinished)); target->runAction(
CCSequence::actions(actionMove, actionMoveDone, NULL)); } void GameScene::spriteMoveFinished(CCNode *node) { this->removeChild(node, true); }

CCCallFuncN액션에서 액션에 설정한 셀렉터가 호출될 때 이 셀렉터를 호출한 CCNode가 같이 전달 되므로 화면을 벗어난 적 스프라이트가 전달 되는 것입니다. 따라서 해당 node포인터를 this(현재 GameScene)에서 removeChild() 를 해 주면 화면에서 지워지게 됩니다.

3. 게임 루프 메서드 설치하기
적이 주기적으로 계속 나오게 하기 위해서 게임 로직을 수행하는 gameLogic 메서드를 추가하겠습니다.

// GameScene.h
//: 게임 로직을 수행합니다.
//: addTarget()을 주기적으로 호출합니다.
void gameLogic(cocos2d::ccTime dt);

아래와 같이 구현합니다.

// GameScene.cpp
void GameScene::gameLogic(cocos2d::ccTime dt)
{
    this->addTarget();
}

이제 이 GameLogic 메서드가 주기적으로 호출될 수 있도록 스케줄링을 걸어 줍니다. GameScene의 init() 메서드에 아래와 같이 추가해 줍니다.

// GameScene.cpp
// 레이어를 초기화합니다.
bool GameScene::init()
{
    // CCLayerColor 의 initWithColor 초기자를 호출해 
    // 배경화면을 하얀색으로 변경한다.
    // 색상 설정은 ccc4(R, G, B, A) 매크로를 사용한다.
    if( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255)) )
    {
        // 초기화에 실패하면 false를 반환합니다.
        return false;
    }
    
    //: 플레이어 스프라이트를 화면에 추가합니다.
    this->setPlayerSprite();
    
    //: 게임 로직을 만든다
    this->schedule(schedule_selector(GameScene::gameLogic), 1.0);
    
    return true;
}

이제 코드의 작성이 모두 끝났습니다. 편의를 위해서 GameScene의 모든 코드를 올립니다.

//
//  GameScene.h
//  NinjaGame
//
//  Created by SUNG CHEOL KIM on 11. 10. 12..
//  Copyright 2011 individual. All rights reserved.
//

#ifndef NinjaGame_GameScene_h
#define NinjaGame_GameScene_h

#include "cocos2d.h"

//: 배경화면 색상 변경을 위해서 CCLayerColor를 상속받는다
class GameScene : public cocos2d::CCLayerColor
{
public:
    //: 초기자
    //: 아이폰용 cocos2d에서는 id를 반환하지만 cocos2d-x에서는 bool을 반환해야 합니다.
    virtual bool init();
    
    //: CCScene을 반환하는 클래스 메서드
    //: 아이폰에서 cocos2d로 게임을 만들 때 CCLayer를 상속받은 커스텀레이어에서 
    //: 이런 식으로 클래스 메서드를 만들어서 씬을 많이 반환해 보셨을 겁니다.
    static cocos2d::CCScene* scene();
    
    
    //: static node() 메서드를 구현합니다.
    LAYER_NODE_FUNC(GameScene);
    
private:
    //: 플레이어의 스프라이트를 추가합니다.
    void setPlayerSprite();
    //: 적 스프라이트를 랜덤 위치와 속도로 화면에 추가합니다.
    void addTarget();
    //: 적 스프라이트가 화면에서 사라진 후 스프라이트를 씬과 메모리에서 제거합니다.
    void spriteMoveFinished(CCNode *node);
    //: 게임 로직을 수행합니다.
    //: addTarget()을 주기적으로 호출합니다.
    void gameLogic(cocos2d::ccTime dt);
};
#endif

//
//  GameScene.cpp
//  NinjaGame
//
//  Created by SUNG CHEOL KIM on 11. 10. 12..
//  Copyright 2011 individual. All rights reserved.
//

#include "GameScene.h"

//using namespace cocos2d 의 약자 매크로입니다.
USING_NS_CC; 

CCScene* GameScene::scene()
{
    // GameScene레이어를 담아 반환할 씬을 하나 만듭니다.
    // 자동 메모리 해제 되는 인스턴스입니다.
    CCScene *scene = CCScene::node();
    
    // 레이어를 생성합니다.
    GameScene *layer = GameScene::node();
    
    // 레이어를 자식으로 씬에 추가합니다.
    scene->addChild(layer);
    
    return scene;
}

// 레이어를 초기화합니다.
bool GameScene::init()
{
    // CCLayerColor 의 initWithColor 초기자를 호출해 
    // 배경화면을 하얀색으로 변경한다.
    // 색상 설정은 ccc4(R, G, B, A) 매크로를 사용한다.
    if( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255)) )
    {
        // 초기화에 실패하면 false를 반환합니다.
        return false;
    }
    
    //: 플레이어 스프라이트를 화면에 추가합니다.
    this->setPlayerSprite();
    
    //: 게임 로직을 만든다
    this->schedule(schedule_selector(GameScene::gameLogic), 1.0);
    
    return true;
}

void GameScene::setPlayerSprite()
{
    //: 화면 좌측, 중간에 놓기 위해서 화면 크기를 이용합니다.
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    
    //: 플레이어 이미지를 불러와 스프라이트를 만듭니다.
    //: CCRectMake는 스프라이트의 영역을 만듭니다.
    //: 이미지 크기와 동일하게 만들어 줍니다.
    CCSprite *player = CCSprite::spriteWithFile("Player.png", 
                                                CCRectMake(0, 0, 27, 40));
    //: 플레이어의 위치를 정해줍니다.
    player->setPosition( ccp(player->getContentSize().width/2, 
                             winSize.height/2) );
    
    //: GameScene에 스프라이트를 추가합니다.
    this->addChild(player);
}

void GameScene::addTarget()
{
    //: 적 스프라이트를 만든다.
    CCSprite *target = CCSprite::spriteWithFile("Target.png", CCRectMake(0, 0, 27, 40));
    
    //: 적 스프라이트의 Y 좌표를 랜덤으로 하여 위치를 잡는다
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    int minY = target->getContentSize().height/2;
    int maxY = winSize.height - target->getContentSize().height/2;
    int rangeY = maxY - minY;
    int actualY = ( rand() % rangeY ) + minY;
    target->setPosition( ccp(winSize.width + (target->getContentSize().width/2),
                             actualY) );
    this->addChild(target);
    
    //: 속도를 랜덤으로 결정한다.
    int minDuration = (int)2.0;
    int maxDuration = (int)4.0;
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = ( rand() % rangeDuration ) + minDuration;
    
    //: move액션을 만든다
    CCFiniteTimeAction *actionMove = CCMoveTo::actionWithDuration((ccTime)actualDuration,
                                                                  ccp(0-target->getContentSize().width/2, 
                                                                      actualY));
    
    //: 적 스프라이트가 화면 밖에 나간 후에 호출될 콜백 액션
    CCFiniteTimeAction *actionMoveDone = 
CCCallFuncN::actionWithTarget(this,
callfuncN_selector(GameScene::spriteMoveFinished)); target->runAction(CCSequence::actions(actionMove, actionMoveDone, NULL)); } void GameScene::spriteMoveFinished(CCNode *node) { this->removeChild(node, true); } void GameScene::gameLogic(cocos2d::ccTime dt) { this->addTarget(); }

아래 화면은 결과화면입니다.



이번 튜토리얼은 여기서 마치고 다음 튜토리얼에서는 총알을 화면에 추가해 보겠습니다 :)

'게임개발팁' 카테고리의 다른 글

cocos2d-X 게임 만들기 5편  (7) 2011.11.18
cocos2d-X 게임 만들기 4편  (4) 2011.11.09
cocos2d-X 게임 만들기 2편  (8) 2011.10.13
cocos2d-X 게임 만들기 1편  (14) 2011.10.12
iOS OpenGL | ES 튜토리얼 3편  (3) 2011.10.02