이번 파트에서는 총알을 생성하고 총알과 적의 충돌처리 하는 것을 구현하겠다. 


총알 추가

플레이어 케릭터에서는 특별한 액션이 필요하지 않고 끊임없이 총알이 발사 된다. 레벨에 따라서 총알의 모양, 강도만 다를 뿐이다. 


총알 스프라이트를 구현하기 위해서 Bullet 스프라이트를 CCSprite를 상속받아서 생성한다. 그리고 Bullet.h 이동한다. 아래 코드를 추가한다. 


//총알의 종류와 강도

typedef enum {

    //총알의 강도를 나타낸다.

    kFirst = 20

} BulletType;


@interface Bullet : CCSprite {

}


@property (nonatomic, readwrite) BulletType bulletType;

총알의 종류와 강도를 나타내는 변수와  총알 타입을 설정 할 수 있는 프로퍼티도 설정한다. 


Bullet.m 으로 이동해서 init 메소드를 아래와 같이 작성한다. 

- (id)init

{

    // 번째 총알 이미지

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

    if (self) {

    //총알 타입을 정한다.

        _bulletType = kFirst;

    }

    return self;

}

스프라이트에 이미지를 넣고 총알 강도를 나타내는 타입을 지정한다. 이번 따라하기 시리즈에서는 레벨이 개념이 없으므로 그냥 한 개의 타입 밖에 없다. 


총알이 앞으로 나아가게 하기 위해 - (void)updateBullet:(ccTime)dt 메소드를 추가하고 아래 코드를 추가한다.  

- (void)updateBullet:(ccTime)dt {

    CGSize winSize = [[CCDirector sharedDirector] winSize];

    //총알의 움직임 가속도

    CGPoint bulletVel = ccp(0, 300);

    //총알의 현재위치

    CGPoint currentPostion = self.position;

    //화면 밖으로 넘어가면 총알을 숨긴다.

    if (currentPostion.y > winSize.height){

        self.visible = NO;

    } else{

        //아니면 계속해서 앞으로 보낸다.

        self.position = ccpAdd(ccpMult(bulletVel, dt), self.position);

    }

}

이전 적, 배경과 처럼, 총알의 속도를 정하고 현재 위치를 확인해서 화면 밖으로 넘어가면 숨김 처리한다. 아니면 계속해서 앞으로 나아가게 한다. 


updateBullet: 메소드를 호출을 위해서 onEnter 메소드에 아래 코드를 추가한다.

-(void)onEnter{

    [super onEnter];

    //스케쥴로 호출

    [self schedule:@selector(updateBullet:) interval:0.05];

}

0.05초 마다 updateBullet: 메소드를 호출한다.


기본적인 총알 스프라이트 구성은 완성되었다. GameLayer에 추가하여서 화면에 보이도록 하자.


GameLayer.h 화일로 이동해서 아래 코드를 추가한다. 

    //총알을 담을 배열

    CCArray *bulletsArray;

    //마지막 총알 확인용

    int lastBullet;


@property (nonatomic, weak) CCSpriteBatchNode *batchNode;


총알을 재사용하기 위해 배열을 사용하고 마지막으로 사용된 총알확인을 위한 변수를 하나 추가한다. 그리고 총알의 이미지 하나를 사용해서 여러개의 총알을 생성하기 위해서 배치노드를 사용한다. 배치노드 사용 성능에 관해서는 CCSpriteBatchNode 성능 테스트를 참조하도록 한다. 


GameLayer.h이동해서 상단에 총알 클래스를 임포트하는 코드를 아래와 같이 추가한다.

#import "Bullet.h"


init 메소드로 이동해서 아래 코드를 스프라이트 캐쉬위에 추가한다. 

//마지막 총알 번호를 위해 초기화

lastBullet = 0;


//총알등을 위해 배치노드 사용

_batchNode = [CCSpriteBatchNode batchNodeWithFile:@"dragonRideSprite.pvr.ccz"];

[self addChild:_batchNode];


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

[[CCSpriteFrameCache sharedSpriteFrameCacheaddSpriteFramesWithFile:@"dragonRideSprite.plist"];

마지막 총알 위치를 초기화하고 배치노드를 추가한다. 


총알을 생성하기 위해서 initBullet메소드를 생성하고 아래 코드를 추가한다.

#define kMaxBullet 30


-(void)initBullet {

    //총알 갯수만큼 배열을 만든다.

    bulletsArray = [[CCArray alloc] initWithCapacity:kMaxBullet];

    //총알 갯수만큼 배열에 넣는다.

    for (int i = 0; i < kMaxBullet; i++) {

        //총알 노드를 생성

        Bullet *bullet = [Bullet node];

        //처음에는 보이는 상태로 만든다.

        bullet.visible = NO;

        //총알의 위치는 플레이어 케릭터의 앞에 위치.

        bullet.position = ccp(_player.position.x, _player.position.y + _player.boundingBox.size.height / 2);

        //배치노드에 넣는다.

        [_batchNode addChild:bullet z:99];

        //충돌등 계산을 쉽게 하기 위해 배열에 넣는다

        [bulletsArray addObject:bullet];

    }

}

총알의 갯수는 30개로 하며 배력에 넣고 재사용한다. 이때 GameLayer에 바로 추가하는게 아니라 방금전에 생성했던 배치노드에 추가한다. 그렇게 되면 하나의 동일한 이미지로 효율성 있게 재사용하게 된다. 


init 메소드로 이동해서 아래 코드를 추가한다. 

        //총알 초기화

        [self initBullet];


기본적인 생성은 끝났으니 사용해보도록 구현하겠다. 


-(void)updateBullet:(ccTime)dt메소드를 생성하고 아래 코드를 추가한다. 

-(void)updateBullet:(ccTime)dt {

    //배열에서 하나씩 총알을 꺼낸다.

    Bullet *bullet = (Bullet*)[bulletsArray objectAtIndex:lastBullet];

    //움직일때는 보이게 설정

    bullet.visible = YES;

    //총알의 위치는 플레이어 케릭터의 앞에 위치.

    bullet.position = ccp(_player.position.x, _player.position.y + _player.boundingBox.size.height / 2);

    //마지막 총알이 배열의 마지막이면 다시 초기화 한다.

    if (++lastBullet == kMaxBullet) {

        lastBullet = 0;

    } 

}

배열에서 마지막으로 사용했던 총알위치에서 총알을 꺼내고 보이게 설정한다. 총알의 위치는 플레이어 케릭터의 위이다. 


onEnter메소드에 위에 생성했던 메소드를 스케쥴러를 통해서 호출하기 위해서 아래 코드를 추가한다. 

    [self schedule:@selector(updateBullet:) interval:0.05f];


Cmd(⌘) + R을 해서 Run을 해보자. 그러면 총알이 플레이어 케릭터에서 마구 나가는 모습을 확인 할 수 있다. 





Posted by KraZYeom

댓글을 달아 주세요

  1. 지르미 2013.01.29 16:46  댓글주소  수정/삭제  댓글쓰기

    이걸보고나니 코코스가 이해가 정말 잘되요
    드래곤 플라이트편 끝나고 다른방식의 게임도 풀이 부탁합니다 ^^

* 혹시 안되는것이 있거나 하면 바로 리플로 알려주세요. 구현하면서 글을 작성해야 좋은데, 구현 해놓고 역으로 글을 쓰니까 코드가 살짝 꼬이는것이 있네요. 생각하면서 작성을 하고 있긴 하지만 이상한 곳이 많을 것 같습니다. 



이번 파트에서는 적 케릭터를 추가하고 적이 없어지고 다시 생성되는 것을 구현하겠다.


적 케릭터 만들기 

드래곤 플라이트에서 적은 적의 레벨에 따라서 여러 종류의 색상으로 달라진다. 하얀색이 약한 놈, 노란색은 조금더 강한 놈 ... 점점 강하게 된다. 적의 스프라이트를 상세하게 분석하면 몸통, 기본과 공격 받을때가 달라지는 양쪽 눈, 날개 짓 하는 양쪽 날개 그리고 에너지 상태를 나타내는 에너지 바로 구분 할 수 있다. 

그리고 다섯 마리의 적들이 한 줄로 나란하게 나온다. 아래로 움직여서 내려오고 화면 밖으로 벗어나면 다시 상단에서 생성되어 내려온다. 계속해서 반복한다. 


이런 컨셉으로 적을 만들어 보도록 하겠다. 


새로운 클래스의 이름을 Enemy로 CCSprite를 상속받아서 만든다. 


그리고 Enemy.h 화일에 적의 스프라이트를 생성을 위한 프로퍼티를 아래와 같이 추가한다. 

//두종류의

typedef enum {

    kRed = 0,

    kWhite

} EnemyType;



//적의 상태를 나타내기 위해 

typedef enum {

    kDestoryed = 0,

    kNormal = 1

} State;


@interface Enemy : CCSprite {

    BOOL wingDown;

}


//적의 종류 : 약한놈과 강한놈으로 가지로만 구분

@property (nonatomic) EnemyType enemyType;

//현재 상태 : 죽었는지, 살았는지로 구분

@property (nonatomic) State state;

//에너지

@property (nonatomic) NSInteger energy;

//왼쪽 날개

@property (nonatomic, weak) CCSprite *leftWing;

//오른쪽 날개

@property (nonatomic, weak) CCSprite *rightWing;

//왼쪽

@property (nonatomic, weak) CCSprite *leftEye;

//오른쪽

@property (nonatomic, weak) CCSprite *rightEye;


-(void)reset;

그리고 init 메소드에 다음 코드를 추가 한다. 

- (id)init

{

    //몬스터 스프라이트를 위한 기본 이미지를 추가한다.

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

    if (self) {

        //적의 기본 에너지는 100

        _state = kNormal;

        _energy = 100;

        //에너지 게이지를 만든다.

        wingDown = NO;

        

        //왼쪽 날개 스프라이트

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

        _leftWing.anchorPoint = ccp1.00 );

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

        _leftWing.position = ccp(10self.boundingBox.size.height / 2);

        [self addChild:_leftWing];

        

        //오른쪽 날개 스프라이트

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

        //동일한 이미지를 반대도 회전 시켜서 사용

        _rightWing.flipX = YES;

        _rightWing.anchorPoint = ccp0.00 );

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

        _rightWing.position = ccp(self.boundingBox.size.width - 10self.boundingBox.size.height / 2);

        [self addChild:_rightWing];

        

        //왼쪽  스프라이트

        _leftEye = [CCSprite spriteWithSpriteFrameName:@"dragon_eye_1.png"];

        //왼쪽 눈의 위치를 설정한다

        _leftEye.position = ccp(1030);

        [self addChild:_leftEye];


        //오른쪽  스프라이트

        _rightEye = [CCSprite spriteWithSpriteFrameName:@"dragon_eye_1.png"];

        //왼쪽 눈의 위치를 설정한다

        _rightEye.position = ccp(3030);

        [self addChild:_rightEye];

    }

    return self;

}


적 노드가 생성될때 몸에 해당하는 스프라이트를 추가하고, 상태를 기본상태로 설정하고 에너지는 100으로 설정한다. 에너지는 적의 종류에 따라 추후 변경된다. 그리고 왼쪽, 오른쪽 날개 스프라이트를 추가한다. 나중에 날개짓 에니메이션을 위해서 엥커포인트를 조정한다. 그리고 왼쪽, 오른쪽 눈 스프라이트를 추가한다. 


일정시간 마다 적을 아래로 움직이게 하기 위해서 init 메소드 아래에 다음 코드를 추가한다.

-(void)update:(ccTime)dt {

    //적의 움직임 가속도 

    CGPoint enemyScrollVel = ccp(0, -250);

    //현재 적의 위치 

    CGPoint enemyPos = [self position];

    //화면 아래로 벗어나는지 체크

    if (enemyPos.y + self.boundingBox.size.height / 2 <= 0) {

        //벗어나면 리셋 한다.

        [self reset];

        //아닐 경우

    } else{

        //움직이게 위치값을 아래로 이동

        enemyPos = ccpAdd(ccpMult(enemyScrollVel, dt), enemyPos);

        //위치 시킨다.

        [self setPosition:enemyPos];

    }

}

시작점으로 부터 일정 주기마다 자신의 현재 위치에서 계속 아래로 내려온다. 그리고 자신의 바운딩 박스 크기가 영역을 벗어나면 초기 위치로 이동한다. 


영역을 벗어나면 초기 위치로 이동하고, 적의 레벨을 랜덤으로 변경한다. 아래 코드를 추가하자. 

#define kMaxEnemyType 2


-(void)reset{

    CGSize winSize = [[CCDirector sharedDirectorwinSize];

    //적의 위치를 화면 상단으로 부터 시작한다.

    self.position = ccpself.position.x, winSize.height + self.boundingBox.size.height / 2 - 1 );

    //적의 이미지를 바꿔주기 위해 랜덤  생성

    int random = ( (float)arc4random() / (float)0xffffffff ) * kMaxEnemyType;

    

    switch (random) {

        case kWhite:

            //에너지를 100으로 할당

            _energy = 100;

            //적의 타입설정

            _enemyType = kWhite;

            //이미지 변경을위해 프레임에 파일이름으로 스프라이트 프레임 케쉬로 부터 찾아서 할당

            [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCachespriteFrameByName:@"dragon_01_body.png"]];

            [_leftWing setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCachespriteFrameByName:@"dragon_01_wing.png"]];

            [_rightWing setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCachespriteFrameByName:@"dragon_01_wing.png"]];

            _rightWing.flipX = YES;

            break;

        case kRed:

            //에너지를 200으로 할당

            _energy = 200;

            //적의 타입설정

            _enemyType = kRed;

            //프레임에 파일이름으로 스프라이트 프레임 케쉬로 부터 찾아서 할당

            [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCachespriteFrameByName:@"dragon_02_body.png"]];

            [_leftWing setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCachespriteFrameByName:@"dragon_02_wing.png"]];

            [_rightWing setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCachespriteFrameByName:@"dragon_02_wing.png"]];

            _rightWing.flipX = YES;

            break;

        default:

            break;

    }

}


화면의 상단 밖으로 적의 스프라이트를 위치하고, 랜덤으로 적의 종류를 선택한다. 적의 종류에 따라서 에너지와 관계된 스프라이트의 이미지를 변경을 한다. 


-(void)onEnter{

    [super onEnter];

    //스케쥴러 호출

    [self scheduleUpdate];

}

-(void)update:(ccTime)dt;  스케쥴러 메소드를 호출하기 위해서 onEnter 되었을 때 [self scheduleUpdate];를 호출 한다.


적이 완성되었으니 이제는 적을 게임 화면에 추가 할 차례이다. GameLayer.h로 이동해서 아래 코드를 추가 하도록 하자.

    CCArray *enemysArray; //적을 저장할 배열

적을 배열에 넣고, 재사용을 할 목적의 배열을 생성한다. 


GameLayer.m으로 이동해서 아래 코드를 추가한다. 

#import "Enemy.h"

상단에 아래와 같이 적을 불러올 수 있게 임포트 해준다.


그리고 적을 생성하기 위해서 위해서 아래코드를 추가한다. 

#define kMaxMonster //기본 적의 수는 5마리


- (void)initEnemys {

    //적을 저장할 배열을 생성한다.

    enemysArray = [[CCArray allocinitWithCapacity:kMaxMonster];

    //화면을 균등하게 나눈다.

    float width = winSize.width / kMaxMonster;

    //적의 최대 갯수 만큼 화면에 나타내고배열에 저장

    for ( int i = 0; i < kMaxMonster ; i++ ) {

        // 노드를 생성.

        Enemy *enemy = [Enemy node];

        //화면에 위치 시킨다.

        [self addChild:enemy z:98];

        //균등하게 나눈 화면에서 가운데에 위치 시킨다.

        enemy.position = ccp( i * width + width / 2winSize.height + enemy.boundingBox.size.height / 2);

        //그리고 나중에 충돌 등을 위해 배열에 넣는다.

        [enemysArray addObject:enemy];

    }

}

배열을 생성하고 화면에서 균등하게 나눈 위치에 적을 위치시키고 배열에 저장한다. 배열에 넣는 이유는 재사용하기도 좋고, 나중에 충돌 등 각각을 다루는 것 보다 배열에서 꺼내서 사용하는게 더 편리 하기 때문이다. 


- (void)initEnemys호출을 위해서 init 메소드에서 [self initPlayer];  바로 아래에 다음 코드를 추가한다.

        [self initEnemys];


Cmd(⌘) + R으로 Run을 해보자. 아래 동영상 처럼 적들이 아래로 내려가고, 화면에서 없어지면 위에서 또 생성되서 나온다. :]





다음 파트에서는 플레이어가 총알을 쏘고, 적을 없애는 것을 다루도록 하겠다. 



* 혹시 안되는것이 있거나 하면 바로 리플로 알려주세요. 구현하면서 글을 작성해야 좋은데, 구현 해놓고 역으로 글을 쓰니까 코드가 살짝 꼬이는것이 있네요. 생각하면서 작성을 하고 있긴 하지만 이상한 곳이 많을 것 같습니다. 


Posted by KraZYeom

댓글을 달아 주세요

  1. 표독이 2013.01.24 08:55  댓글주소  수정/삭제  댓글쓰기

    드디어 올라 왔네요!! 감사합니다.

  2. Favicon of https://red-submarine.tistory.com BlogIcon 빨간잠수함。 2013.02.26 23:22 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 뒤늦게 정주행중에 매의눈으로 발견한 오타입니다
    책으로 내실때 수정해주세요 ㅎㅎ

    kDestoryed -> kDestroyed

    //오른쪽 눈 스프라이트
    _rightEye = [CCSprite spriteWithSpriteFrameName:@"dragon_eye_1.png"];
    //왼쪽 눈의 위치를 설정한다 ->오른쪽 눈의 위치를 설정한다
    _rightEye.position = ccp(30, 30);

    잘보고 있습니다 튜토리얼 많이 만들어주세요 ㅎㅎㅎ

  3. Favicon of https://red-submarine.tistory.com BlogIcon 빨간잠수함。 2013.04.13 16:30 신고  댓글주소  수정/삭제  댓글쓰기

    후... 두달전에 cocos2d로 따라할때도 따라하다가 버그 있어서 제잘못인줄 알고... 때려쳤...었는데..
    이번에 cocos2d-x 공부하면서 krazyeom님의 튜토리얼을 cocos2d-x로 포팅하면서 공부하는중에 원인을 발견해서 코멘트합니다
    reset 메서드에서
    self.visible = YES;
    _state = kNormal;
    두줄이 빠져서 한번 죽은자리는 안나오는 현상이 나옵니다ㅎㅎ

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


스프라이트 시트 

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

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

그림은 한 장이지만, 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에서 에러가 나는데요.....
    처음부터 막히니까 따라하기도 벅차네요 ㅠㅠ 왕초보의 비애 ㅠㅠ