이번 파트에서는 플레이어 케릭터를 추가하고, 터치입력을 받아서 좌우로 움직이는 것을 다루겠다. 


스프라이트 시트 

게임에서는 많은 스프라이트 이미지들이 사용된다. 아무것도 몰랐을 때는 이미지 한 장, 한 장을 불러서 사용했다. 물론 그렇게 해도 가벼운 게임에는 큰 문제가 없다. 아무리 작은 이미지라도 한 장이 차지 하는 최소 공간이 있다고 한다. (정확하게는 잘 모르겠다) 그래서 작은 이미지를 여러장 따로 불러 쓰면 필요 없는 공간이 많이 지므로 효율성도 떨어진다고 한다. 

그래서 게임에는 작은 이미지 파일을 통짜로 묶은 스프라이트 시트를 사용한다. 예를 들면 드래곤 라이드에서 사용한 이미지 시트는 아래 그림과 같다. 

그림은 한 장이지만, plist 파일로 각각 스프라이트의 이미지 정보를 담고 있다. 


개발자가 일일이 만들어 줄 수도 있겠지만, 사용화 툴도 많고 무료 툴도 있긴 하다. 무료 툴은 불편함과 기능이 좀 부족한 것 같아서. 필자는 TexturePacker라는 툴을 구입했다. $29 라는 약간 부담 스러운 가격이지만 맥/리눅스 지원을 하고 cocos2d외에도 다양한 게임 플렛폼을 지원한다. 워터마크가 들어 가긴 하지만 트라이얼로 사용할 수 있으니 다운로드 받아서 사용하면 된다. 

다른 툴로는 zwoptex 가 있고 기능이 떨어지긴 하지만 기본 기능은 다 되는 플레쉬 버젼은 무료다. 


이미지 리소스 파일 

dr.zip


위 이미지 파일을 다운 받아서 모든 이미지 파일을 TexturePacker에 모두 드레그&드롭 해서 넣으면 된다. Texture format은 zlib compr. PVR (.pvr.ccz, Ver.2)로 선택하는게 가장 효율성이 좋다. 그리고 Publish를 하면 dragonRideSprite.pvr.cczdragonRideSprite.plist 파일 두 개가 생성된다. 이 파일 두 개를 xcode 프로젝트에 드래그&드롭으로 추가 한다.

만들어진 스프라이트를 게임에 넣도록 하자. GameLayer.m 으로 이동해서 init 메소드 상단 부분에 아래코드를 추가하도록한다.

- (id)init

{

    self = [super init];

    if (self) {

        ....


        //스프라이트 프레임 케쉬에 스프라이트를 저장한다.

        [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"dragonRideSprite.plist"];


        ....

    }

게임이 실행할 때 스프라이트 케쉬에 넣어서 계속해서 다른 곳에서도 재사용 할 수 있다. 

플레이어 케릭터 추가

Cmd(⌘) + N으로 CCSprite를 상속받은 Player 클래스를 새로 생성한다.

플레이어 케릭터를 몸통과 양 날개를 가진 Sprite 3개로 구성된다. 



플레이어 몸통에 필요한 player.png와 날개에 필요한 player_wing.png를 스프라이트 시트를 통해서 사용할 것이다. player_wing.png는 한 장으로 좌, 우 날개에 사용 될 것이다.


Player.h에 아래 코드를 추가 한다.

@property (nonatomic, weak) CCSprite *leftWing;

@property (nonatomic, weak) CCSprite *rightWing;

왼쪽, 오른쪽 날개를 CCSprite로 프로퍼티를 생성한다. 


Player.minit 메소드에 아래 코드를 추가한다.


- (id)init

{

    //플레이어 몸통의 스프라이트를 생성한다.

    self = [super initWithSpriteFrameName:@"player.png"];

    if (self) {

        //윈도우 크기

        CGSize winSize = [CCDirector sharedDirector].winSize;

        //화면의 가운데 아래측에 위치 시킨다.

        self.position = ccp( winSize.width/2, 50 );

        

        //왼쪽 날개를 만든다.

        _leftWing = [CCSprite spriteWithSpriteFrameName:@"player_wing.png"];

        //날개의 엥커포인트를 우측 상단으로 한다.

        _leftWing.anchorPoint = ccp( 1.0, 1.0 );

        //중간 정도로 위치 시킨다.

        _leftWing.position = ccp( 10, 30 );

        //몸통아래에 위치 하도록 z-order 변경한다.

        [self addChild:_leftWing z:-1];


        //오른쪽 날개를 만든다.

        _rightWing = [CCSprite spriteWithSpriteFrameName:@"player_wing.png"];

        //날개의 엥커포인트를 촤측 상단으로 한다.

        _rightWing.anchorPoint = ccp( 0.0, 1.0 );

        //Flip 회전 시킨다.

        [_rightWing setFlipX:YES];

        //Flip 회전 하면 축으로 회전 된다. 그래서 몸통의 가로만큼 추가해준다.

        _rightWing.position = ccp( self.boundingBox.size.width - 10, 30 );

        //몸통아래에 위치 하도록 z-order 변경한다.

        [self addChild:_rightWing z:-1];

    }

    return self;

}

몸통과 좌우날개를 만들어 주고 자식 노드에 추가한다. 오른쪽 날개는 이미지를 Flip 회전해서 사용한다. 


만들어진 Player 클래스를 GameLayer에 추가하기 위해 GameLayer.h에 아래 코드를 추가한다. 

@class Player;

...

@property (nonatomic, weak) Player *player;

...

Player 객체를 위한 프로퍼티를 추가한다. 


GameLayer.m에 아래 코드를 추가한다. 

- (void)initPlayer {

    //플레이어 케릭터를 생성한다.

    _player = [Player node];

    //가장 위에 위치 시킨다.

    [self addChild:_player z:99];

}

player 객체를 생성하고 자식노드로 상단 레이어에 추가한다. 


그리고 init 메소드 하단에 아래 코드를 추가한다.

        //플레이어 케릭터 초기화

        [self initPlayer];

플레이어를 생성하는 메소드를 init에서 호출한다. 


실행하면 아래 화면과 같이 배경화면은 스크롤 되고 아래쪽 가운데에 플레이어 케릭터가 위치 하게 된다.

역시 그림빨이 있어야 하나보다. ㅠㅠ



플레이어 움직이기

다음으로는 터치 이벤트를 받아서 플레이어 케릭터를 움직이게 만들 것이다. 플레이어 케릭터는 Y축으로는 화면 하단에 고정되어 있고, 좌우로 X축만 움직이게 된다. 사용자가 화면을 좌우로 스와이프 하면 그 움직이는 거리를 계산해서 플레이어 케릭터를 움직인다. 


움직이는 거리계산을 위한 이전 위치값을 저장하는 변수를 GameLayer.h에 추가한다.

@interface GameLayer : CCLayer {

    CGSize winSize;

    CGPoint previousPoint;

}


터치 이벤트를 받기 위해 아래 코드를 GameLayer.m 하단에 추가한다.

- (void)onEnter {

    [super onEnter];

    //터치 이벤트를 받는다.

    [[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];

    //배경 움직임과 충돌을 체크할때 사용하는 메인 스케쥴

    [self scheduleUpdate];

}

GameLayer가 시작되고 onEnter 메소드가 호출 되면 터치 이벤트를 받는다. 


기본적으로 UITouchEvent와 비슷하게 시작 시점에 호출하는 TouchBegan, 움직임이 있을때 호출하는 TouchMoved, 터치가 끝났을 때 호출하는 TouchMoved가 있다. 


아래 코드를 onEnter 메소드 하단에 삽입한다. 

#pragma mark Touch


-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {

    //터치가 시작되면 이전 값과 비교를 위해 저장한다. UI좌표계를 cocos 좌표계로 변환

    previousPoint = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];

    return YES;

}

터치가 시작되면 이전 값과 비교를 위해서 저장한다. iOS에서는 UI좌표계를 사용하므로 cocos2d 좌표계로 변환하는 메소드를 호출한다. 정확하게 말하면 만드는 게임에서는 X축 좌표 이동만 하기 때문에 변환이 필요가 없을 수도 있지만 추후를 위해서 그대로 남겨 두도록 하자. 


Note: 

-(CGPoint)convertToGL:(CGPoint)uiPoint

{

CGSize s = winSizeInPoints_;

float newY = s.height - uiPoint.y;


return ccp( uiPoint.x, newY );

}

터치 포인트를 받아와서 X축 값은 그대로 전달해주고 Y축 값은 화면 크기에서 터치 지점 Y값을 뺀 값이다.

-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {

    //이전 값과 비교를 위한 움직였을때 위치 . UI좌표계를 cocos 좌표계로 변환

    CGPoint location = [[CCDirector sharedDirectorconvertToGL:[touch locationInView:[touch view]]];

    //플레이어 케릭터의 위치를 ( 기존 위치 X  - 움직인 거리 ), Y 값은 동일

    _player.position = ccp_player.position.x - (previousPoint.x - location.x) * 2_player.position.y );

    //왼쪽이나 오른쪽으로 벗어나면 넘어가지 않도록 고정 시킨다.

    if (_player.position.x < 0) {

        _player.position = ccp(0_player.position.y);

    } else if (_player.position.x > winSize.width) {

        _player.position = ccp(winSize.width_player.position.y);

    }

    //현재 위치를 이전 값으로 저장한다.

    previousPoint = location;

}

터치를 한 상태에서 움직이면 그 움직인 거리를 계산해서 플레이어 케릭터에 적용을 한다. 그리고 화면 좌우 영역을 벗어나는 경우를 생각해서 min 값은 0, max 값은 화면 크기의 너비 값으로 한다. 


-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {

    //터치가 끝났을때는 특별한 이벤트가 없다.

}

터치가 끝났을 때는 특별한 이벤트가 없다. 


Cmd(⌘)+R 로 실행을 해보자. 

그리고 터치를 해서 좌우로 스와이프를 하면 케릭터가 좌우로 움직이는 것을 확인 할 수 있다. :-]


하나씩 완성 되어가는 모습을 보니 뿌듯하지 않은가? 다음 파트에서는 적을 만들어 보도록 하겠다.

Posted by KraZYeom

댓글을 달아 주세요

  1. 포풍 2013.01.23 11:26  댓글주소  수정/삭제  댓글쓰기

    잘보고 있습니다~ 4편도 기대하고 있어요~

이 연재글은 필자의 심심함에 따라 작성되고 있는 글입니다. 좀 늦어질 수도 있습니다. 그리고 코드의 내용이 개판일 수도 있으니 나름 유념하고 봐주세요. 소스는 나중에 한 번에 통짜로 github에 올릴테니 버그, 수정 사항이 있으면 알려주시면 반영 하도록 하겠습니다.

만들고 라인수를 살펴보니 주석포함 1200 라인정도 밖에 안되네요. 정말 아무나 할 수 있습니다. 

본 글과 소스의 라이센스는 '만나면 커피 한잔 사주기' 라이센스 입니다. 쿨럭. 


GameScene 만들기 

이전에는 Layer에 Scene을 클래스 메소드를 사용하여 생성하였지만, Scene 하위에 다수의 레이어를 자식노드로 추가하기 위해서 CCScene을 상속받아서 Scene을 만들도록 하자. 

Cmd(⌘) + N으로 새로운 파일을 추가 한다. CCScene을 상속받고 이름을 GameScene으로 짓는다. 그리고 GameLayerHUDLayer도 CCLayer를 상속 받아서 각각의 이름으로 추가한다. 


GameLayer에는 플레이어 케릭터, 적 등등 게임을 하는 데 필수 요소가 포함되고, HUDLayer에는 점수, 거리와 같은 게임의 보조적인 열할을 하는 요소가 포함될 것이다. 


GameScene.h에 생성했던 클래스의 헤더를 추가하고, 각 Layer의 프로퍼티를 설정한다.

#import "GameLayer.h"

#import "HUDLayer.h"


@interface GameScene : CCScene


@property (nonatomic, weak) GameLayer *gameLayer;

@property (nonatomic, weak) HUDLayer *hudLayer;


GameScene.m으로 돌아가서 init 메소드에서 GameLayer와 HUDLayer를 자식노드로 추가한다. 

- (id)init

{

    self = [super init];

    if (self) {

        //HUD 레이어 추가하기

        _hudLayer = [HUDLayer node];

        [self addChild:_hudLayer z:1];

        //Game 레이어 추가하기

        _gameLayer = [GameLayer node];

        [self addChild:_gameLayer z:0];

    }

    return self;

}

GameScene에 GameLayer와 HudLayer 두 개의 레이어를 추가했다. 그리고 [self addChild:_gameLayer z:0];에서 z 는 z-order, 즉 수직으로 겹치는 층의 위치를 나타낸다. 숫자가 작을 수록 먼저 그려지게 된다. 숫자가 커지면 위로 올라오고, 0에 가까우면 아래로 내려간다. 


Note: 

init를 치면 아래 그림과 같이 코드 생성 도우미가 알아서 init 메소드를 생성해준다.`



MenuLayer.m의 상단에 #import "GameScene.h"를 추가하고, init 메소드 안의 CCMenuItem 부분을 아래 코드로 변경한다.

//Start 메뉴 버튼이 눌렸을 경우, GameScene 화면 전환과 함께 호출한다.

CCMenuItem *startItem = [CCMenuItemFont itemWithString:@"Start" block:^(id sender)  {

     [[CCDirector sharedDirector] replaceScene:[GameScene node]];

}];


변경을 하면 init 코드가 아래와 같을 것 이다. 

- (id)init

{

    self = [super init];

    if (self) {

        //다이렉터에서 화면의 크기를 알아온다.

        CGSize size = [[CCDirector sharedDirectorwinSize];

        //제목으로 만들 레이블을 시스템 폰트를 사용해서 만든다.

        CCLabelTTF *label = [CCLabelTTF labelWithString:@"Dragon Rider" fontName:@"HelveticaNeue" fontSize:36];

        //레이블의 위치를 지정한다.

        label.position = ccp( size.width/2, size.height/2 + 100 );

        //레이블을 자식으로 추가한다.

        [self addChild:label];


        CCLabelTTF *label2 = [CCLabelTTF labelWithString:@"Made by @krazyeom" fontName:@"HelveticaNeue" fontSize:30];

        //레이블의 위치를 지정한다.

        label2.position = ccp( size.width/2, size.height/2 + 60 );

        //레이블을 자식으로 추가한다.

        [self addChild:label2];

        

        //메뉴 아이템의 폰트를 변경한다.

        [CCMenuItemFont setFontName:@"AppleSDGothicNeo-Medium"];

        //메뉴 아이템 블럭

        CCMenuItem *startItem = [CCMenuItemFont itemWithString:@"Start" block:^(id sender)  {

        //Start 메뉴 버튼이 눌렸을 경우, GameScene 화면 전환과 함께 호출한다.

            [[CCDirector sharedDirector] replaceScene:[GameScene node]];

        }];

        //메뉴 버튼을 메뉴에 추가한다.

        CCMenu *menu = [CCMenu menuWithItems:startItem, nil];

        //세로 정렬로  메뉴의 사잇값으로 20 준다.

        [menu alignItemsVerticallyWithPadding:20];

        //메뉴의 위치를 지정한다.

        [menu setPosition:ccp( size.width/2, size.height/2 - 50)];

        //메뉴를 자식으로 추가한다.

        [self addChild:menu];

    }

    return self;

}


Cmd(⌘) + R 으로 Run을 해서 잘 진행되고 있는지 체크를 한다. Start 버튼을 누르면 검정 화면만 덩그러니 떠 있다. 아무런 문제가 없으니 걱정하지 마라.


배경화면 스크롤 하기

드래곤 플라이트에서 자세히 보면 게임에서 사용자 케릭터가 앞으로 나아가고 있는 것 처럼 보이지만, 실제적으로는 고정위치에 있고 배경 화면이 아래로 스크롤 됨에 따라서 상대적으로 움직이는 것 처럼 보인다. 배경화면을 더 빠르게 움직이면 또한 사용자 케릭터는 더 빠르게 움직이는 것 처럼 보이게 된다. 


CCParallaxNode를 사용하면 배경화면을 마치 달리는 기차에서 창 밖을 바라보면 가까이 있는 것은 빠르게 움직이고 멀리있는 것은 느리게 움직이는 효과를 줄 수 있다. 하지만 자식 노드의 위치 값을 변경을 아직 지원하지 않는다.


그래서 그냥 동일한 이미지 두 장을 사용해서 무제한으로 도는 것 처럼 보이게 만들 것이다. 우선 배경화면에 필요한 이미지를 xcode 프로젝트로 드레그 앤 드롭을 해서 넣는다.  

(발로 그렸으니 이해바랍니다. 누가 이쁘게 그려주시면 감사! ㅠㅠb)

Destination과 Add to targets 체크 박스는 꼭 체크 하도록 하자. 안하면 나중에 골치 아프다.


GameLayer.h 파일로 이동해서 배경화면에 사용할 이미지 두 개의 프로퍼티를 추가 한다.

@interface GameLayer : CCLayer {

 //화면 싸이즈를 저장할 변수

    CGSize winSize;

}

@property (nonatomic, weak) CCSprite *backgroundImage1;

@property (nonatomic, weak) CCSprite *backgroundImage2;


배경화면 초기화를 위해서  - (void)initBackground(); 메소드를 init 메소드 밑에 추가한다.

- (void)initBackground{

    //배경에 사용할 1 이미지를 생성 , 화면에 차게 이동 시킨다.

    _backgroundImage1 = [CCSprite spriteWithFile:@"01.png"];

    _backgroundImage1.anchorPoint = CGPointZero;

    [self addChild:_backgroundImage1 z:-1];

    

    //배경에 사용할 2 이미지를 생성 , 1 이미지 위로 이동 시킨다.

    _backgroundImage2 = [CCSprite spriteWithFile:@"01.png"];

    _backgroundImage2.anchorPoint = CGPointZero;

    _backgroundImage2.position = ccp(0, [_backgroundImage2 boundingBox].size.height);

    [self addChild:_backgroundImage2 z:-1];

}

1번 이미지를 불러와서 앵커포인트를 (0, 0) 좌측 하단으로 설정한다. 그리고 화면에 노드로 추가한다. 기본위치는 (0, 0)이다. 그리고 1번 이미지와 동일한 2번 이미지를 1번 이미지 바로 위에 위치 시킨다. 


Note: 

iOS 좌표계와 다르게 cocos2d는 (0, 0) 좌표가 좌하단이다. 그래프에서 1사분면이라고 생각하면 쉽다. 


init 메소드를 만들고 아래 코드와 같이 추가 한다. 

- (id)init

{

    self = [super init];

    if (self) {

        //윈도우 화면 크기를 가져온다.

        winSize = [[CCDirector sharedDirector] winSize];

        //배경 초기화

        [self initBackground];

    }

    return self;

}



그리고 배경화면을 움직이게 하기 위해서 아래 코드를 추가 한다.

- (void)update:(ccTime)dt {

    // 배경화면 움직이는 속도, 현재 위치에 이동할 위치를 ccpAdd 더하는 방식

    CGPoint backgroundScrollVel = ccp(0, -100);

    // 현재 이미지1 위치 값을 불러온다.

    CGPoint currentPos = [_backgroundImage1 position];

    // 1 이미지가 스크롤 되서 사라지고, 2 이지미가 1 이미지의 초기 위치에 오면 최초위치로 이동

    if (currentPos.y < -winSize.height) {

        [_backgroundImage1 setPosition: CGPointZero];

        currentPos = ccp(0, [_backgroundImage2 boundingBox].size.height);;

        [_backgroundImage2 setPosition: currentPos];

    //현재 위치에서 backgroundScrollVel 한다.

    } else{

        _backgroundImage1.position = ccpAdd(ccpMult(backgroundScrollVel, dt), _backgroundImage1.position);

        _backgroundImage2.position = ccpAdd(ccpMult(backgroundScrollVel, dt), _backgroundImage2.position);

    }

}

이 - (void)update:(ccTime)dt 메소드는 [self scheduleUpdate]; 호출이 된다. 스케쥴러로써 cocos2d에 정해놓은 config값의 시간 간격에 따라 반복되서 호출된다. 기본 dt는 1/60


1, 2번 이미지가 동시에 아래로 내려가고 1번 이미지가 완전히 사라질 시점에 2번 이미지는 화면에 꽉차게 된다. 그 시점에 다시 1번 2번 이미지 모두 초기 위치로 이동 시킨다. 그렇게 계속해서 반복하면 배경화면이 끝없이 반복해서 움직이는 것 처럼 보인다. 


그리고 - (void)onEnter메소드를 오버라이드해서 아래 코드와 같이 변경한다.

- (void)onEnter {

    [super onEnter];

    //배경 움직임과 충동을 체크할때 사용하는 메인 스케쥴

    [self scheduleUpdate];

}

GameLayer가 시작 시점에 [self scheduleUpdate];를 통해서 - (void)update:(ccTime)dt가 반복적으로 호출 될 것이다.


Cmd(⌘) + R으로 Run을 해서 잘 진행되고 있는지 체크를 한다.  아무런 문제가 없다면 아래 동영상 처럼 배경화면이 무한정으로 반복되는 것을 확인 할 수가 있다. 

 

뭔가 느리지만 하나 하나씩 진행 되고 있다. 다음장에서 계속 하도록 하겠다. 

Posted by KraZYeom

댓글을 달아 주세요

  1. cmh 2013.03.09 07:50  댓글주소  수정/삭제  댓글쓰기

    질문이 있습니다.

    올려주신 이미지 사이즈가 아이폰 사이즈 보다 크네요..
    그래서 배경 그림이 부분만 이동하게 나옵니다.

    배경사이즈를 줄여주는 소스가 누락된것인가요?

이 연재글은 필자의 심심함에 따라 작성되고 있는 글입니다. 좀 늦어질 수도 있습니다. 그리고 코드의 내용이 개판일 수도 있으니 나름 유념하고 봐주세요. 소스는 나중에 한 번에 통짜로 github에 올릴테니 버그, 수정 사항이 있으면 알려주시면 반영 하도록 하겠습니다.

만들고 라인수를 살펴보니 주석포함 1200 라인정도 밖에 안되네요. 정말 아무나 할 수 있습니다. 

본 글과 소스의 라이센스는 '만나면 커피 한잔 사주기' 라이센스 입니다. 쿨럭. 



지난번에는 게임 개발의 환경 구성을 하였다. 이번 장에서는 본론으로 들어갈려고 했으나 게임의 가장 중요한 부분인 메뉴 화면을 구성을 할 것이다. 그리고 화면모드 변경에 대해서도 설명을 할 것이다. :-) 


메뉴 화면

게임을 시작 하면 대부분의 게임은 메뉴로 시작한다. 단순하게 게임 이름과 게임의 시작을 하게 하는 “Start” 버튼으로 구성된 시작 화면을 만들어 본다. 


Cmd(⌘) + N으로 새로운 파일을 추가 한다.  CCLayer를 상속 받고 MenuLayer로 이름을 짓는다. 


MemuLayer.h 파일의 @end 바로 위에 아래와 같이 코드를 추가 한다. 

+(CCScene *)scene;


MemuLayer.m으로 이동해서  @implementation과 @end 사이에 아래 코드를 추가 한다. 

+(CCScene *)scene{

    //scene 오토릴리스 오브젝트이다.

    CCScene *scene = [CCScene node];

    //layer 오토릴리스 오브젝트이다.

    MenuLayer *layer = [MenuLayer node];

    //scene 자식으로 layer 추가한다.

    [scene addChild:layer];

    //scene 리턴한다.

    return scene;

}

CCScene을 상속 받아서 CCLayer를 추가 해도 되지만 MenuLayer 외에 특별하게 자식노드로 추가 될게 없으므로 클래스 메소드로 scene을 만들고 MenuLayer를 자식노드에 추가해서 리턴한다. 


Note: 

`[CCScene node]`에서 node의 정의는 아래와 같이 되어 있다. 


+(id) node

{

return [[[self alloc] init] autorelease];

}


다음은 init 메소드를 작성할 것이다. +(CCScene *)scene 메소드 아래에 아래 코드를 추가 한다. 

- (id)init

{

    self = [super init];

    if (self) {

        //다이렉터에서 화면의 크기를 알아온다.

        CGSize size = [[CCDirector sharedDirector] winSize];

        //제목으로 만들 레이블을 시스템 폰트를 사용해서 만든다.

        CCLabelTTF *label = [CCLabelTTF labelWithString:@"Dragon Rider" fontName:@"HelveticaNeue" fontSize:36];

        //레이블의 위치를 지정한다.

        label.position = ccp( size.width/2, size.height/2 + 100 );

        //레이블을 자식으로 추가한다.

        [self addChild:label];


        CCLabelTTF *label2 = [CCLabelTTF labelWithString:@"Made by @krazyeom" fontName:@"HelveticaNeue" fontSize:30];

        //레이블의 위치를 지정한다.

        label2.position = ccp( size.width/2, size.height/2 + 60 );

        //레이블을 자식으로 추가한다.

        [self addChild:label2];

        

        //메뉴 아이템의 폰트를 변경한다.

        [CCMenuItemFont setFontName:@"AppleSDGothicNeo-Medium"];

        //메뉴 아이템 블럭

        CCMenuItem *startItem = [CCMenuItemFont itemWithString:@"Start" block:^(id sender)  {

        //Start 메뉴 버튼이 눌렸을 경우, GameScene 화면 전환과 함께 호출한다.

        }];

        

        //메뉴 버튼을 메뉴에 추가한다.

        CCMenu *menu = [CCMenu menuWithItems:startItem, nil];

        //세로 정렬로 메뉴의 사잇값으로 20 준다.

        [menu alignItemsVerticallyWithPadding:20];

        //메뉴의 위치를 지정한다.

        [menu setPosition:ccp( size.width/2, size.height/2 - 50)];

        //메뉴를 자식으로 추가한다.

        [self addChild:menu];

    }

    return self;

}

시스템 폰트를 사용해서 게임 제목을 추가하고, 실질적인 메뉴를 추가한다. 메뉴를 추가 하기 위해서는 메뉴 아이템을 생성하고 메뉴에 배열형식으로 하나씩 자식 노드로 추가한다.


AppDelegate에서 바로 MenuLayer를 노드로 추가 해도 되지만, cocos2d의 기본 템플릿인 IntroLayer를 사용해서 화면 전환을 할 것이다. 

이제것 만들어진 코드를 호출하기 위해 IntroLayer.m 파일을 아래와 같이 수정한다. 

//메뉴 레이어를 추가한다.

#import "MenuLayer.h"


//삭제한다

//#import "HelloWorldLayer.h"


-(void) onEnter 메소드를 아래 와 같이 변경한다. 

-(void) onEnter

{

[super onEnter];

[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[MenuLayer scene]]];

}

HelloLayer를 MenuLayer로 변경을 하면된다. 현재 씬을 화면 전환효과를 사용하여 1초동안 MenuLayer의 씬으로 변경한다.


Note:

OnEnter는 객체가 생성이 되고 시작 시점에 호출된다. Exit는 객체가 해제가 될 때 호출된다.


CCNode SceneManagement

-(void) onEnter

-(void) onEnterTransitionDidFinish

-(void) onExitTransitionDidStart

-(void) onExit


이제 앱이 실행되면 AppDelegate에서 IntroLayer를 호출하고, IntroLayer는 MenuLayer를 화면 전환효과로 호출한다. 


여기까지 잘 따라왔으면 Cmd(⌘)+R 눌러서 실행한다. 문제가 없으면 아래 그림과 같이 나올 것이다. 


Note: 

cocos2d에서는 시스템 폰트를 말고 비트맵 폰트를 사용하는 것을 권장한다. 렌더링 과정에서 손실이 크다. 이번 프로젝트에서는 텍스트를 표시 할 것이 많지 않으므로 시스템 폰트를 사용한다. 비트맵 폰트 사용법은 Tutorial: Bitmap Fonts and Hiero를 참조 하길 바란다. 


화면모드 변경

드레곤 플라이트는 기본적으로 세로화면으로 게임을 진행한다. 그런데 이상하게 세로화면이 아닌 가로화면으로 메뉴가 나온다. 원래 생각했었던 메뉴 화면 구성은 아래 그림과 같을 것이다.

이런 이유는 cocos2d의 게임은 기본적으로 가로화면으로 구성되어서 그렇다. 수정을 위해서 왼쪽 Project 판넬에서 Project를 선택하고 Summary 탭으로 이동한다. 


아래 그림과 같이 Supported Interface Orientations에서 Landscape Left와 Landscape Right로 선택되어 있던 것을 Portrait와 Upside Down으로 변경해서 세로 모드로 가능하게 한다. 


다시 Cmd(⌘)+R 눌러서 실행해보자. 세로화면으로는 잘 나오지만, 실행할때 기본 이미지가 메뉴가 나오기 전에 90도로 회전해서 또 나온다. OTL 


IntroLayer.m 파일에서 init 메소드 중간에 보면 background.rotation = 90; 코드가 있다. 첫 Defalut.png 이미지가 호출 되고 Loading 되는 시점에서 깜빡임과 딜레이를 없애기 위해 Defalut.png 그림을 배경화면으로 한 번 더 보여준다. 이 과정에서 그림을 90도로 돌리는 과정때문에 일어난 현상이다. 간단하게 저 코드를 주석 처리 하거나 지우면 된다. 

다시 Cmd(⌘)+R 눌러서 실행해보자. 모든것이 완벽하게 보인다. 

예~! 시작이 반이다. 벌써 반이나 한 것이나 다름이 없다. 이제 본격적으로 게임의 화면을 구성하는 것으로 넘어가 보자.

Posted by KraZYeom

댓글을 달아 주세요

  1. 표독이 2013.01.15 01:02  댓글주소  수정/삭제  댓글쓰기

    감사합니다. 강좌 잛 보고 있습니다 ^^

  2. ken 2014.05.27 03:19  댓글주소  수정/삭제  댓글쓰기

    Landscape Left와 Landscape Right로 선택되어 되었을때에는 문제 없이 되는데요
    Portrait와 Upside Down으로 변경해서 세로 모드로 변경하면 에러가 나는 현상은 무었때문일까요?

    int retVal = UIApplicationMain(argc, argv, nil, @"AppController";);
    여기가 문제 있다고 나오는데요..... ㅠㅠ
    왕초보가 따라 하기도 벅차네요 ㅜㅜ

  3. ken 2014.05.29 12:16  댓글주소  수정/삭제  댓글쓰기

    답변좀 부탁드려요... ㅠㅠ

  4. ken 2014.05.29 12:20  댓글주소  수정/삭제  댓글쓰기

    #import <UIKit/UIKit.h>

    int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"AppController";); <= 요줄에 문제가 있다고 나오는데요 알수가 없네요..
    [pool release];
    return retVal;
    }

    mein에서 에러가 나는데요.....
    처음부터 막히니까 따라하기도 벅차네요 ㅠㅠ 왕초보의 비애 ㅠㅠ

이 연재글은 필자의 심심함에 따라 작성되고 있는 글입니다. 좀 늦어질 수도 있습니다. 그리고 코드의 내용이 개판일 수도 있으니 나름 유념하고 봐주세요. 소스는 나중에 한 번에 통짜로 github에 올릴테니 버그, 수정 사항이 있으면 알려주시면 반영 하도록 하겠습니다.

만들고 라인수를 살펴보니 주석포함 1200 라인정도 밖에 안되네요. 정말 아무나 할 수 있습니다. 


이번화는 본론에 들어가기에 앞서서 기본 적인 준비를 위해서 작성 하도록 한다.


준비물

cocos2d로 게임을 만드는데 필요한 도구들을 알아보도록 하자. 아래 목록만 있으면 충분히 만들 수가 있다.

- Xcode를 돌릴 Mac 한 대. (누가 레티나 맥북 프로 한 대 줬으면 좋겠다. :] )

- iOS 기기 (누가 iPhone 5 한 대 줬으면 좋겠다. :] )

- cocos2d 2.x 

- 대충 objective-c를 쓰고 읽을 수 있는 능력


cocos2d 설치하기 

iOS용으로 많은 게임 제작관련 프레임워크가 있지만, 오픈소스이면서 가장 인기가 있는 cocos2d를 선택하였다. 언어는 Objective-C를 지원한다. 


우선 cocos2d의 stable version의 최신버젼을 cocos2d 홈페이지 다운로드 받자. Download: cocos2d-iphone.2.0.tar.gz


다운로드 받은 파일을 압축을 푼다. terminal 창을 열고 아래 명령어를 입력한다. 

tar -xvzf cocos2d-iphone-2.0.tar.gz


그리고 설치 스크립트를 실행한다. 

./install-templates.sh -f -u


이렇게 하면 기본적인 cocos2d의 설치가 끝난다. 


프로젝트 생성

게임을 만들기 위해서 프로젝트를 생성해보도록 하자. cocos2d 설치후 Xcode를 실행하면 아래 그림과 같이 왼쪽 페널에 cocos2d v2.x가 나타난다. 클릭을 하면 cocos2d관련 템플릿 4종류가 나온다. Box2d와 Chipmunk는 물리엔진를 사용할 수 있는 템플릿 이다. 이번 프로젝트에서는 물리엔진을 사용하지 않으므로 기본 cocos2d iOS를 선택하고 Next 버튼을 눌러서 다음으로 넘어간다.



Product Name, Oraganization Name, Company Indentifier를 자신의 맞게 입력을 하고, Device Family는 iPhone으로 선택하고 Next 버튼 눌러서 다음으로 넘어가자.



저장할 위치를 선택하고 Create 버튼을 눌러서 프로젝트 생성을 마친다. 



ARC 활성화 하기 

iOS SDK 5.0 이후로 ARC가 사용되고 있지만, 아직까지는 cocos2d에는 ARC가 적용되어 있지 않다. ARC를 사용하지 않고 개발 할 수도 있겠지만, 메모리 관리 등의 효율성을 위해서 프로젝트에 ARC를 적용 하도록 한다. 하지만 cocos2d를 ARC 적용을 위해 수정을 하기 위해서는 많은 작업이 필요 하므로, cocos2d 프레임 워크 외에만 적용 하도록 하자. 


Project > Target (Dragon Ride) > Build Phases 탭으로 이동하자. 그리고 확장자가 .m 파일 모두를 선택하자. 쉬프트키나 커멘드 키를 사용해 파일들을 다중 선택한다. 선택한 후 Eenter 키를 누르면 팝업 창이 나타난다. 이 후  -fno-objc-arc를 입력한다. 



Build Settings 탭으로 이동해서 Objective-C Automatic Reference Counting 값을 YES로 변경한다. search 창에서 auto로 검색하면 빨리 찾을 수 있다. 




그리고 ARC는 iOS SDK 이상에서만 가능하기 때문에, Summary 탭에서 Deployment Target 을 5.0 이상으로 변경한다. 



Cmd(⌘) + R으로 Run을 해서 아무런 에러가 없이 아래 화면과 같이 실행되면 ARC를 적용하기 위한 준비는 끝났다.


이상, 게임 만들기를 위한 기본적인 설정이 모두 끝났다. 다음 부터는 본격적으로 게임 만들기에 들어가보도록 하자. 생각보다 어렵지 않다. 정말로...!

Posted by KraZYeom

댓글을 달아 주세요

  1. 포풍 2013.01.15 11:11  댓글주소  수정/삭제  댓글쓰기

    기대 됩니다

  2. 동건 2013.01.23 12:38  댓글주소  수정/삭제  댓글쓰기

    지금부터 시작해요!

동일한 이미지 한 장을 사용해서 무제한으로 도는 것 처럼 보이게 만들 것이다. 


Note:

`CCParallaxNode`를 사용하면 배경화면을 마치 달리는 기차에서 창 밖을 바라보면 가까이 있는 것은 빠르게 움직이고 멀리있는 것은 느리게 움직이는 효과를 줄 수 있다.


header 파일에 아래 프로퍼티를 추가한다. 

@property (nonatomic, weak) CCSprite *backgroundImage1;

@property (nonatomic, weak) CCSprite *backgroundImage2;


implement 파일에 아래 코드를 추가한다. 

- (void)initBackground{

    //배경에 사용할 1 이미지를 생성 , 화면에 차게 이동 시킨다.

    _backgroundImage1 = [CCSprite spriteWithFile:@"01.png"];

    _backgroundImage1.anchorPoint = CGPointZero;

    [self addChild:_backgroundImage1 z:-1];

    

    //배경에 사용할 2 이미지를 생성 , 1 이미지 위로 이동 시킨다.

    _backgroundImage2 = [CCSprite spriteWithFile:@"01.png"];

    _backgroundImage2.anchorPoint = CGPointZero;

    _backgroundImage2.position = ccp(0, [_backgroundImage2 boundingBox].size.height);

    [self addChild:_backgroundImage2 z:-1];

}


- (void)update:(ccTime)dt {

    // 배경화면 움직이는 속도, 현재 위치에 이동할 위치를 ccpAdd 더하는 방식

    CGPoint backgroundScrollVel = ccp(0, -100);

    // 현재 이미지1 위치 값을 불러온다.

    CGPoint currentPos = [_backgroundImage1 position];

    // 1 이미지가 스크롤 되서 사라지고, 2 이지미가 1 이미지의 초기 위치에 오면 최초위치로 이동

    if (currentPos.y < -winSize.height) {

        [_backgroundImage1 setPosition: CGPointZero];

        currentPos = ccp(0, [_backgroundImage2 boundingBox].size.height);;

        [_backgroundImage2 setPosition: currentPos];

    //현재 위치에서 backgroundScrollVel 한다.

    } else{

        _backgroundImage1.position = ccpAdd(ccpMult(backgroundScrollVel, dt), _backgroundImage1.position);

        _backgroundImage2.position = ccpAdd(ccpMult(backgroundScrollVel, dt), _backgroundImage2.position);

    }

}


한 장의 동일한 이미지를 두 개를 위, 아래로 붙여서 세로로 길게 늘여 놓은 다음에 아래 이미지가 스크롤 되어서 화면역영을 벗어나면, 즉 위의 이미지가 아래 이미지 초기 위치에 오게 되면 다시 원래 위치로 되돌린다. 이렇게 계속해서 반복하면 무제한으로 스크롤링 되는 것 처럼 보인다. 


cocos2d에서 뭔가 제공해 줄 것 같은데 내가 잘 못 찾아서 그런지 없어서... 누군가 잘 아시는분이 계시면 리플로 알려주세요~!

Posted by KraZYeom
TAG cocos2d

댓글을 달아 주세요

  1. 아라가람 2013.04.17 16:36  댓글주소  수정/삭제  댓글쓰기

    열심히 배워볼꼐요!