나머지 Scene

그 외의 Scene은 Title Scene과 거의 비슷하고 특별한 기법은 없기 때문에 생략한다.


마치며…

Hello Space 실행 스크린샷 (첫 실행 버전)


그래픽 향상 버전


 

이것으로, Hello World로 시작하여 확장시킨 Hello Space의 테스트 버전은 모두 끝났다.

처음 만든 아이폰 게임이어서 게임답지 않고 많이 허전한 느낌이다. 그래도 혼자 스터디 하면서 이 정도는 만들 수 있었다는 것이 뿌듯했다. 개인 사정으로 그래픽이나 사운드, 인공 지능, 완성도 측면에서 조금 더 시간 투자를 하기가 어려웠던 것이 아쉬웠다. 그래도 스터디 기간을 포함해서, 한 달 안에 마쳤다는 것은 다행이다.

여건이 된다면 조금 더 작업을 해서 완성도를 높여보고 싶다.

개선해야 할 사항

• 그래픽 개선: 가장 시급한 사항. 누가 봐도 게임 같다고 할 만한 그래픽/애니메이션 작업이 필요하다.

• 적기의 종류: 현재 1개 뿐이라 많이 허전하다.

• 적기의 인공 지능: 화면 하단부로 이동만 한다. 다양한 방향으로 움직임이 필요하고, 적기가 발사하는 미사일도 있으면 좋을 것이다.

• 에너지 요소의 도입: 적기마다 에너지(체력)를 적용하여, 강한 기체의 경우 미사일을 여러 번 맞춰야 격추시킬 수 있다.

• 보스의 존재: 에너지 요소로부터 파생된 개념이다. 여기서부터 다양한 움직임과 인공 지능으로 무장한 보스 기체를 만들어 볼 수 있을 것이다.

• 플레이어 기체: 현재는 플레이어 기체에 충돌 체크를 넣지 않아, 적기와 충돌해도 아무런 반응이 없다. 충돌 시 폭발한다거나 에너지의 개념이 있어야 한다.

• 조작 방법: 여기에서 사용한 터치 방식은 일반적인 게임과는 많은 괴리가 있다. 가상 패드같은 UI가 아니더라도, 보다 더 정교하고 쉬운 조작 방법을 고민해 봐야 할 것이다.

• 스테이지 도입: 단순한 무한 반복은 지루할 수 있으므로, 여러 단계의 스테이지를 추가하면 좋을 것이다. 단순한 스테이지가 아니라, 거꾸로 스크롤링이 된다거나, 함정 요소 같은 것들도 넣으면 재미있을 것 같다.

• 사운드: 현재는 사운드에 관련된 어떤 부분도 구현하지 않았다. 추후 그럴 듯한 사운드를 구현해야 할 것이다.

'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #04  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #03  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #02  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #01  (0) 2012.11.01
cocos2d study #16  (0) 2012.10.26

Play Scene Class

1. PlayScene.h

추후 수정 가능성이 있거나 필요할 것으로 예상되는 부분을 define으로 정의해 두었다.


SCREENX: 스크린의 너비, 아이폰을 세로로 들었을 때의 너비이다.

SCREENY: 스크린의 높이

ENEMYMAKE_INTERVALTIME: 적 기체가 생성되는 시간의 간격

TAG: 각종 상황에서의 관리 및 처리를 위한 꼬리표

DEPTH: TAG와 같은 목적으로 정의한 각 스프라이트 객체의 레이어 층

SCORE_PER_ENEMY1: 적 기체를 파괴했을 때 점수

2. PlayScene.m

(1) init

init에서는 각 스프라이트 객체를 생성하며 스케줄러를 등록한다.


① self.isTouchEnables

터치 이벤트를 활성화한다.


② 플레이어 기체(player) 스프라이트 작업

터치한 곳에 따라 이동하는 플레이어 기체이다.

스프라이트의 이름은 알아보기 쉽게 player로 결정했다.

최대한 빠르고 간단하게 만들기 위해 그래픽 작업 대신 삼각형 모양의 png 이미지를 쓰기로 했다. 플레이어는 정삼각형으로, 적기는 역삼각형으로 했다.

플레이어 기체의 현재 위치를 기준으로, 터치한 곳의 x 좌표를 판단하여 그 쪽으로 이동하게 된다.


③ 게임 스코어 레이블

화면에 스코어를 표시하는 간단한 코드


④ 배열 초기화

플레이어 기체는 화면에 하나만 존재하지만, 적기와 총알은 매우 많이 생성될 수 있다. 이것들의 관리는 배열로 할 것이고, init에서 초기화한다.


⑤ 스케줄러

적 기체의 생성, 충돌 체크는 스케줄러로 등록하여 따로 처리한다.

(2) ccTouchesBegan

터치 이벤트가 발생했을 때 처리할 것들이다.

첫 번째로는 플레이어를 터치한 방향으로 이동시킨다.

두 번째는 미사일을 발사한다.


① UIKit 좌표를 OpenGL 좌표로 변환

OpenGL 좌표로의 변환 공식은 스터디에서 익혔던 내용을 그대로 사용하면 된다.

-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)withEvent {
    /// ---------- 이 코드를 수정할 일은 거의 없다.

    // UITouch는 좌상단이 (0,0)이다.
    UITouch *touch=[touches anyObject];

    CGPoint touchPoint;

    // 터치가 발생한 좌표를 얻는다.
    touchPoint = [touch locationInView:[touch view]];

    //GL 좌표로 변환. 좌하단을 (0,0)으로 변환한다.
    touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint];

    /// ----------
}

OpenGL 좌표 변환 코드로부터 touchPoint(터치한 곳의 좌표)를 얻을 수 있다.

이렇게 얻은 touchPoint 좌표를 플레이어 기체의 이동 액션에 활용하게 된다.


② 플레이어 기체의 x 좌표 수정

터치한 좌표를 기준으로 플레이어 기체를 왼쪽으로 이동시키거나 오른쪽으로 이동시킨다.

이 때 터치한 좌표가 아이폰의 화면 가장자리일 경우, 기체가 화면 밖을 벗어날 수 있는데, x 좌표를 확인하고 있다가 이런 상황이 생길 경우 x 좌표를 수정하면 된다.


왼쪽 스크린샷의 상황이 생기지 않도록 player의 x 좌표를 수정한다.

// SCREENX = 320
// SCREENY = 480

// 터치좌표 x가 (SCREENX-플레이어 너비/2)보다 크면
if (touchPoint.x > SCREENX-player.contentSize.width) {
    // SCREENX - (플레이어 너비/2)
    touchPoint.x = SCREENX-player.contentSize.width/2;
}
// 터치한 좌표가 (플레이어 너비/2)보다 작으면 [예]20
if (touchPoint.x < player.contentSize.width/2) {
    // 현재 플레이어 x좌표를 (플레이어 너비/2)로 수정
    touchPoint.x = player.contentSize.width/2;
}

플레이어 기체의 크기는 임의로 정수 45를 사용했지만 추후에 바뀔 가능성도 있어서, contentSize로 변경했다. 이렇게 하면 크기가 변해도 이 코드를 수정하지 않아도 된다.

터치한 좌표 x가 오른쪽 화면 끝에 가까운 경우, (기체의 우측 부분이 화면 밖으로 삐져 나오는 경우) 가로 화면 크기인 320에서 기체 크기의 반만큼을 뺀 크기를 x 좌표로 수정한다. 이렇게 하면 x 좌표는 항상 화면 내부에 위치하게 된다. 단, 기체의 크기를 너무 크지 않게 해야 뚝뚝 끊기는 느낌이 없을 것이다.

반대로 터치한 곳의 x가 왼쪽 화면에 가까운 곳이라면, 기체 너비의 반 길이를 x로 수정한다. 이렇게 하면 x가 화면 왼쪽을 빠져 나가지 않게 된다.


③ 기체 이동 액션
// 터치한 좌표로 move&action
// y 좌표는 변하지 않아야 함. y 좌표는 (플레이어 높이/2)로 설정
id testPlayerMove = [CCMoveTo actionWithDuration:0.7
                          position:ccp(touchPoint.x,player.contentSize.width/2)];
	
[player runAction:testPlayerMove];

터치했을 때 x 좌표가 지나치게 크거나 작은 경우(화면 밖으로 삐져 나가는 경우)는 앞에서 처리했으므로, 터치한 곳의 x 좌표로 이동하도록 moveTo 액션만 실행해주면 된다.


④ 미사일(총알) 처리

터치할 때마다 미사일을 발사하는 작업이다. 소스 코드에서는 친숙한 표현으로, 총알(bullet)로 명명했다.

// ################## 터치하면, 총알 생성과 액션
bullet = [CCSprite spriteWithFile:@"bullet.png"];

// 총알의 초기 위치는 플레이어의 x좌표
// 플레이어의 높이에 총알 높이를 더한 만큼
bullet.position = ccp(player.position.x, 
                      player.contentSize.height+bullet.contentSize.height);
bullet.tag = TAG_BULLET;
[self addChild:bullet];

// 총알 배열 추가
[_bullets addObject:bullet];	
	
// 총알의 이동은 화면을 벗어나게
id testBulletMove = [CCMoveTo actionWithDuration:0.5 
                              position:ccp(player.position.x,
                                          SCREENY+bullet.contentSize.height)];

// removeBullet 콜백 함수 참조
id callbackBullet = [CCCallFuncN actionWithTarget:self 
                                         selector:@selector(removeBullet:)];

id totalMove = [CCSequence actions:testBulletMove,callbackBullet,nil];
	
[bullet runAction:totalMove];

터치하면 총알 스프라이트가 생성되고, 이동하는 것이 총알 처리의 기본 뼈대가 된다. 그러나 약간의 처리가 더 필요하다.


총알이 생성되는 위치의 공식은 다음과 같다.

• 총알의 x 좌표
Player.position.x (플레이어 기체의 x 좌표와 동일)
• 총알의 y 좌표
player.contentSize.height + bullet.contentSize.height
(플레이어 기체의 높이) + (총알의 높이)

위의 코드로 인해 총알이 생성되는 곳은 플레이어 기체의 삼각형 상단 꼭지점에서 총알 크기만큼 더 높은 위치에서 발사되는 것처럼 보이게 된다.

총알이 날아가는 곳은 화면 최상단이므로, CCMoveTo에서 y 좌표는 화면의 높이인 SCREENY에 총알의 높이를 더해주면 화면 밖으로 벗어날 것이다.

(3) removeBullet

총알의 경우 한 가지 더 신경 써야 할 것이라면, 화면 바깥을 벗어난 것은 제거를 해야 한다는 것이다. 이것은 콜백 함수 removeBullet로 구현해서 따로 제거를 해 주었다.

콜백 함수에서는 node와 함께 총알 스프라이트를 넘겨 받았으므로 바로 삭제해주면 된다.

-(void)removeBullet:(CCNode *)node {
    [_bullets removeObject:node];		// 배열에서 총알 삭제
    [self removeChild:node cleanup:YES];	// 총알 삭제
}

그 외에 총알에 대한 배열이 있는데, 여러 개의 총알 관리를 위해 나중에 구현해 넣은 것이다.

(4) sechduleEnemyMove
① 적 기체의 생성과 이동

적 기체의 생성은 일정 시간 간격마다 행할 예정이므로, 스케줄로 만들었다.

init에서 아래 부분을 이미 추가했었다.

[self schedule:@selector(scheduleEnemyMove) interval:ENEMYMAKE_INTERVALTIME];

ENEMYMAKE_INTERVALTIME이 바로 적기가 생성될 시간 간격이다. #define으로 정의해 두었으므로 수정이 필요하다면 헤더 파일에서 수정하면 된다.


scheduleEnemyMove에서 적기를 본격적으로 생성, 이동하게 된다.

-(void)scheduleEnemyMove {
    // ############### 적 기체 스프라이트 생성
    CCSprite *enemy1 = [CCSprite spriteWithFile:@"enemy1.png"];
    enemy1.anchorPoint = ccp(0,0);
    enemy1.position = ccp(50,490);
    enemy1.tag = TAG_ENEMY1;
    [_enemies addObject:enemy1];    // 적 기체 배열 추가
    [self addChild:enemy1];
    
    // ############### 적 기체 runAction!
    // MoveBy는 현재 좌표를 0,0으로 간주하고 position으로 움직인다.
    id enemyMove = [CCMoveBy actionWithDuration:2 position:ccp(100,-550)];
    id callbackEnemy = [CCCallFuncN actionWithTarget:self
                                            selector:@selector(removeEnemy:)];
    id totalEnemyMove = [CCSequence actions:enemyMove,callbackEnemy,nil];
    [enemy1 runAction:totalEnemyMove];
}

스프라이트를 생성하고 초기 위치는 화면 바깥으로 설정했다. 정상적인 동작을 확인하기 위해 생성 위치를 ccp(50, 490)으로 해두었는데, 추후에 랜덤하게 바꿀 예정이다.

x 좌표를 랜덤으로 바꾸면 화면 상단에서부터 적기들이 생성되어 화면 아래 방향(100,-550)으로 내려올 것이다. 내려오는 방향도 화면 내부의 랜덤한 좌표로 변경할 예정이다.

변경 사항: 실제 소스 코드에는 약간이지만 랜덤한 움직임이 되도록 수정했다. 매우 간단한 코드이므로 설명은 생략한다.

(5) removeEnemy

적기가 등장한 이후 화면을 벗어나면 제거해야 하기 때문에, 콜백 함수 removeEnemy를 만들었다.

-(void)removeEnemy:(CCNode *)node {
    [_enemies removeObject:node];	 // 적 기체를 배열에서 제거
    [self removeChild:node cleanup:YES]; // 적 제거
}

콜백 함수에서는 removeChild를 통해 적 기체를 제거하는 일만 담당한다. 그래서 이름도 removeEnemy이다.

적기에 대한 배열은 여러 개의 적기 관리를 위해 나중에 추가한 것이다.

(6) scheduleEnemyMeetBullet

스프라이트를 생성하는 것까지는 좋았지만, 제거를 해야 할 순간이 언제가 될지 신경 써야 한다. 충돌 체크 자체는 아이폰 SDK에서 제공하는 영역 비교 메소드(CGRectIntersectsRect) 를 사용했다.

미사일과 적 기체가 만났는지 항상 체크해야 하기 때문에 init에서 스케줄로 등록했다.

[self schedule:@selector(scheduleEnemyMeetBullet)];


스케줄 메소드에서 실제 충돌 체크를 하게 된다.

-(void)scheduleEnemyMeetBullet {
/*
총알과 적기의 영역에 해당하는 Rect를 생성하고
CGRectIntersectsRect를 사용해서 교차되었는지를 확인하는 일을 반복한다. 
만약 충돌이 발생하면 그것들을 scene과 배열에서 삭제한다.
이 때 반복 처리되는 동안에는 배열에서 그것들을 제거할 수 없기 때문에 
오브젝트에 toDelete 배열을 추가한다.
*/
    
    NSMutableArray *bulletsToDelete = [[NSMutableArray alloc] init];
    
    for (CCSprite *tempBullet in _bullets)
    {        
        CGRect bulletRect = CGRectMake(tempBullet.position.x,
                                       tempBullet.position.y,
                                       tempBullet.contentSize.width,
                                       tempBullet.contentSize.height);
        
        NSMutableArray *enemiesToDelete = [[NSMutableArray alloc] init];
                
        for (CCSprite *tempEnemy in _enemies) {
            CGRect enemyRect = CGRectMake(tempEnemy.position.x,
                                          tempEnemy.position.y,
                                          tempEnemy.contentSize.width,
                                          tempEnemy.contentSize.height);
            
            // 충돌했다면 임시 적 배열에 적을 넣는다.
            if(CGRectIntersectsRect(bulletRect, enemyRect))
                [enemiesToDelete addObject:tempEnemy];
        }
    
        // 적 배열에서 적을 삭제, 스프라이트도 제거한다.  
        for (CCSprite *tempEnemy in enemiesToDelete) {
            [_enemies removeObject:tempEnemy];
            [self removeChild:tempEnemy cleanup:YES];
        }
    
        if (enemiesToDelete.count > 0) {
            [bulletsToDelete addObject:tempBullet];
        }
    
        [enemiesToDelete release];
    }
    
    // 총알 배열에서 총알 삭제, 스프라이트 제거
    for (CCSprite *tempBullet in bulletsToDelete) {
        [_bullets removeObject:tempBullet];
        [self removeChild:tempBullet cleanup:YES];
    }
        
    [bulletsToDelete release];
}

미사일과 적기의 영역을 각각 bulletRect, enemyRect로 만든 후, 두 영역이 겹치는지 비교를 하게 된다. 스터디에서 익혔던 CGRectIntersectsRect 메소드를 사용했다.

적기와 총알은 여러 개 생성될 것이기 때문에, 배열에 넣어 관리를 하게 된다.

적기나 미사일이 생성되면 배열에 넣고, 충돌했으면 배열에서 삭제하고 스프라이트도 제거한다.

배열은 alloc으로 만들었으므로, 다 쓴 후에는 release로 메모리에서 해제한다.

(7) onEnterTransitionDidFinish

이 메소드를 오버라이드 해 두었으므로 Scene의 로딩이 끝난 직후에 자동으로 호출된다. 특별한 의미가 있다기보다는, 스터디 했던 내용의 복습을 위해 넣은 것이다.

여기에서는 단순히 화면에 HELLO SPACE PROTOTYPE이라는 레이블을 배경으로 출력한다.

(8) dealloc

alloc으로 만든 것들을 반드시 해제해야 한다. 여기서는 메모리에 할당한 것이 배열 뿐이므로 배열만 해제하면 된다.

-(void)dealloc
{
    // ############## 메모리 해제할 것들

    // alloc으로 만든 배열 release
	
    [_enemies release];
    _enemies = nil;
	
    [_bullets release];
    _bullets = nil;
	
    [super dealloc];
}


'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #05  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #03  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #02  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #01  (0) 2012.11.01
cocos2d study #16  (0) 2012.10.26

TitleScene Class

TitleScene 클래스는 메뉴 화면을 생성하여 보여주고, 플레이어의 터치 입력을 처리한다.

기본적으로 게임 시작(start), 옵션(option), 크레디트(credit) 3개의 메뉴 버튼을 보여주고 각각의 버튼을 누르면 그에 해당하는 동작을 한다.

1. TitleScene.h

매우 간단한 클래스이기 때문에 헤더 파일에 특별한 내용은 없다. 구현 파일에서 사용할 스프라이트의 레이어 두 개만을 #define으로 정해 두었다.

2. TitleScene.m

(1) init

레이블 스프라이트(testLabel) 생성, 배경 이미지 스프라이트(titleBackground) 생성, 메뉴 생성 및 버튼 터치가 되었을 때 호출될 메소드와 위치 설정을 한다.

각각의 메뉴는 cocos2d의 menu 기능을 이용하여 구현하였으며, 각 메뉴에는 각각의 메소드(startButtonTouched, optionButtonTouched, creditButtonTouched)를 연결했다.

(2) onExit

학습 및 테스트용으로 실제 사용은 하지 않는 메소드이다.

(3) startButtonTouched

플레이 장면으로의 전환을 처리한다.

start 버튼을 누르면 PlayScene으로 전환하면서 본격적으로 게임을 시작하게 된다. 가장 많은 작업을 필요로 하므로 우선은 Scene만 생성했다.

(4) optionButtonTouched

옵션 장면으로 전환할 때 호출된다. Option은 개발 계획에 포함되지 않았으므로 아무런 코드도 넣지 않았다. 따라서 메뉴에서 터치를 하더라도 아무런 반응이 없다.

(5) creditButtonTouched

크레디트 장면으로 전환을 처리한다. 크레디트는 제작자인 나에 대한 정보를 보여주는 화면이다. 단순하게 하나의 배경 파일로 처리가 가능하므로, 바로 Scene을 만들고 배경파일 하나를 넣어 작업을 끝냈다.

(6) dealloc

현재까지는 메모리에서 해제할 객체가 없으므로 비워 두었다.

image메뉴를 보여주는 Title Scene

 

정상적으로 동작하는지 까지만 확인하면 되므로 세밀한 그래픽 작업은 하지 않았다. 단순한 버튼만 그려 넣은 수준이다. 추후에 폰트나 배경을 조금 더 화려하게 할 예정이다.

(이 부분은 두 번째 버전에서 업데이트했다.)

'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #05  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #04  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #02  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #01  (0) 2012.11.01
cocos2d study #16  (0) 2012.10.26

Scene Class

게임을 진행할 수 있는 Play Scene은 반드시 있어야 한다. 대부분의 조작과 이벤트가 일어나는 장면이므로 메인 작업이 되었다.

게임의 제목이나 분위기를 표현해주는 배경 화면을 보여주는 Title Scene도 만들어 보기로 했다. 앱을 실행하자마자 게임이 실행되어 적기가 날아들면, 플레이어 입장에서 매우 당황할 수 있으므로, 마음의 준비를 할 시간을 주어야 할 것이다.

제작자인 나에 대한 간단한 Credit Scene도 만들기로 했다.

RootViewController.m

세로 모드를 사용할 예정이기 때문에 RootViewController.m 파일을 수정해야 한다.

 

return ( UIInterfaceOrientationIsLandscape( interfaceOrientation ) );을

return ( UIInterfaceOrientationIsPortrait(interfaceOrientation) );으로 수정하여 세로 모드를 사용한다.

만약 다시 가로 모드로 쓰고 싶다면 Landscape으로 다시 바꾸면 된다.

'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #04  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #03  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #01  (0) 2012.11.01
cocos2d study #16  (0) 2012.10.26
cocos2d study #15  (0) 2012.10.26

Hello Space

개요

그 동안 iPhone SDK와 cocos2d로 연습했던 내용을 토대로 본격적인 게임을 제작에 착수했다.

1. 개발 계획

Objective-C를 공부하면서 어떤 게임을 만들지 고민하다가, 횡스크롤 액션이나 RPG 게임을 생각해 보았는데 확실히 멋지긴 하지만 캐릭터나 맵 디자인 같은 시간이 소요되는 작업이 많을 것 같았다. 팀 작업이라면 역할을 분담하기 때문에 상관 없겠지만, 1인 개발에서 이런 작업은 개발의 집중도를 떨어뜨리고 피로도를 가중시켜 중도에 포기할 확률이 올라가게 된다. 따라서 단기간에 만들 수 있는 슈팅 게임을 구상했다. 복잡한 기능은 추후에 더 구상하기로 하고, 그 동안 스터디 했던 내용을 총동원하여 매우 간단한 게임을 만들어 보기로 했다.

전반적인 게임의 내용은, 화면 밖의 우주 공간으로부터 날아오는 UFO를 격추시키는 것으로 정했다. 이런 형태의 슈팅 게임이 친숙하기 때문이다. 그리고 많은 프로그래밍 관련 서적에서 가장 처음 만드는 예제의 이름이 Hello World이기 때문에, 처음 만드는 게임이라는 의미로 이 프로젝트의 이름을 Hello Space로 정했다.

아이폰은 물리적인 키보드나 입력 장치가 없기 때문에 조작 UI를 터치 방식으로 만들어야 한다. 가상 패드를 만드는 방법이 일반적이겠지만, 간단한 게임이기 때문에 화면을 터치하면 터치한 방향으로 플레이어 기체를 이동시키고, 미사일도 동시에 발사하는 방식으로 결정했다.

2. 제작 기간

약 2주. cocos2d나 iOS 스터디 기간까지 약 3~4주

3. 개발 인원

1명

4. 개발 환경

최초 버전 빌드 및 테스트

Mac OS X 10.7 Lion, Xcode 3.2.6, cocos2d 1.0.1, Paintbrush

최종 버전 빌드 및 테스트

Mac OS X 10.8.2 Mountain Lion, Xcode 4.5.1, cocos2d 2.0

'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #03  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #02  (0) 2012.11.01
cocos2d study #16  (0) 2012.10.26
cocos2d study #15  (0) 2012.10.26
cocos2d study #14  (0) 2012.10.26

cocos2d study #16

충돌 체크


충돌 체크가 필요한 이유

충돌 체크는 게임 내의 캐릭터 또는 물체들의 영역이 서로 겹치는지 확인하는 것이다.

왜 이런 확인이 필요할까? 슈팅 게임을 예로 들어보겠다.

플레이어가 미사일을 발사한 상황을 가정하자. 플레이어가 정확하게 조준을 하고 발사했다면, 미사일은 적 기체를 향해 이동할 것이고 결국 적기와 미사일은 서로 겹치는 순간이 오게 된다. 아무런 처리를 하지 않았다면 미사일은 적기를 관통해 버릴 것이다.

미사일이 적기와 닿은 순간을 확인하는 것이 바로 충돌 체크라고 할 수 있다. 이 순간을 확인하여 미사일과 적기를 폭발(제거)시키거나, 적기의 에너지를 줄이는 등의 처리가 필요한 것이다.

충돌 체크 방법

사각형 영역 검사

여러 가지 방법이 있겠지만 간단히 사각형(Rectangle)의 영역을 비교하는 메소드를 이용하면 충돌 체크를 쉽게 확인할 수 있다. 단, 이 방법에는 심각한 단점이 있는데, 물체의 크기가 커질수록 충돌 여부가 애매한 상황이 생긴다. 예를 들어 300x300 크기를 갖는 삼각형 형태의 비행체가 있을 때, 삼각형을 감싸는 사각형의 빈 영역과 다른 커다란 물체가 근접했을 뿐 실제로는 겹치지 않았는데도 충돌한 상황이 생긴다. 그렇기 때문에 사각형 영역 검사 방법은 각 물체의 크기를 너무 크지 않게 하는 것이 중요하다.


CGRectIntersectsRect

CGRectIntersectsRect를 이용하여 두 객체의 rect를 비교하면 된다.

rect가 겹칠 경우 TRUE를 리턴한다.

// 영역 겹침 검사 ////////////////////////
-(void)testCollision {

    if (CGRectIntersectsRect(playerRect, enemyRect)) {
        // playerRect, enemyRect 두 영역을 검사해서 겹치면 TRUE, 아니면 FALSE 리턴
    }

    // 비슷한 메소드로 r1, r2가 겹쳐진 부분의 영역을 리턴하는 CGRectIntersection이 있다.
    // CGRectIntersection(CGRect r1, CGRect r2)
}

CGRectContainesPoint

터치를 한 좌표가 rect 영역 안에 있는지 확인한다. 영역 안이라면 TRUE를 리턴한다.

// 터치 포인트가 영역 안에 속하는가 /////////////
-(void)testTouch {

    if (CGRectContainsPoint)(playerRect, touchPoint) {
        // touchPoint가 playerRect 영역 안에 있는지 검사. 있으면 TRUE 리턴
    }
}


'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #02  (0) 2012.11.01
아이폰 게임 Hello Space 개발 #01  (0) 2012.11.01
cocos2d study #15  (0) 2012.10.26
cocos2d study #14  (0) 2012.10.26
cocos2d study #13  (0) 2012.10.26

cocos2d study #15

스케줄러


스케줄러 사용 방법

정말 별 거 없다. selector에 호출할 메소드만 쓰면 끝이다.

-(id) init
{
    if ( (self=[super init]) )
    {
        // 매 프레임마다 test를 호출하려면 아래 코드를 사용한다.
        // [self schedule:@selector(test)];

        // 0.1초 마다 testSchedule을 호출한다.
        [self schedule:@selector(testSchedule) interval:0.1];
    }
    return self;
}

-(void)testSchedule
{
    // … 스케줄 할 내용을 작성한다.
}

'개발 > Cocos2d' 카테고리의 다른 글

아이폰 게임 Hello Space 개발 #01  (0) 2012.11.01
cocos2d study #16  (0) 2012.10.26
cocos2d study #14  (0) 2012.10.26
cocos2d study #13  (0) 2012.10.26
cocos2d study #12  (0) 2012.10.26

cocos2d study #14

사운드


사운드 처리

cocos2d의 사운드는 SimpleAudioEngine 객체를 만들면 쉽게 사용할 수 있다. sae는 싱글톤 객체이므로 원하는 곳에서는 언제든지 사용 가능하다. SimpleAudioEngine.h 헤더 파일을 임포트 하는 것 외에 특별히 주의할 부분은 없다.

#import "SimpleAudioEngine.h"
 
-(id) init
{
    if ( (self=[super init]) )
    {
        SimpleAudioEngine *sae;    
 
        // 심플 오디오 엔진 로딩. 싱글톤이다.
        sae = [SimpleAudioEngine sharedEngine]; 
 
        // 볼륨. 크기 범위는 0.0부터 1.0까지
        sae.backgroundMusicVolume = 0.4;    
 
        // 배경음 재생
        [sea playBackgroundMusic:@"backgroundmusic.mp3" loop:YES];    
 
        // 배경음 멈춤. 배경음이 멈춰야 할 때 사용하자.
        [sae stopBackgroundMusic];    
 
        // 효과음 재생
        [sae playEffect:@"effect.wav"]; 
 
    }
    return self;
}

'개발 > Cocos2d' 카테고리의 다른 글

cocos2d study #16  (0) 2012.10.26
cocos2d study #15  (0) 2012.10.26
cocos2d study #13  (0) 2012.10.26
cocos2d study #12  (0) 2012.10.26
cocos2d study #11  (0) 2012.10.26

cocos2d study #13

애니메이션


애니메이션 구현 기법

애니메이션의 구현 기법은 이미지를 어떻게 처리하는지에 따라 크게 2가지 기법이 있다. 첫 번째는 여러 개의 그림 파일을 로드하여 애니메이션으로 만드는 방법이고, 두 번째는 여러 개의 프레임이 담긴 1개의 통짜 이미지를 잘라 사용하는 법이다.

첫 번째 방법은 이미지의 크기를 계산할 필요가 없는 장점이 있으나, 프레임 수가 많아지면 로드해야 하는 이미지 파일의 개수도 늘어나므로 코딩량이 많아진다는 단점이 있다.

두 번째 방법은 통으로 된 1개의 이미지 파일을 각 프레임 크기에 맞게 적절히 잘라내야 하기 때문에 귀찮아질 수 있고, 프레임 크기를 잘못 계산한 경우 의도한 대로 출력이 되지 않는다.

여러 개의 이미지 파일 사용

-(id) init
{
    if ( (self=[super init]) )
    {
        self.isTouchEnabled = YES;
 
        // CCAnimation은 action이 아니라 프레임만 담는다.
        CCAnimation *animation = [[CCAnimation alloc] init];
 
        // 한 프레임에 0.1초
        [animation setDelay:0.1];
 
        // 모든 이미지를 프레임으로 추가한다.
        [animation addFrameWithFilename:@"fire1.png"];
        [animation addFrameWithFilename:@"fire2.png"];
        [animation addFrameWithFilename:@"fire3.png"];
        [animation addFrameWithFilename:@"fire4.png"];
 
        // 아무 동작이 없을 때 보여주는 첫 스프라이트
        CCSprite *sprite = [CCSprite spriteWithFile:@"fire1.png"];
        sprite.position = ccp(240,160);
        [self addChild:sprite];
        
        // CCAnimate는 액션이다. 위에서 만든 animation을 넣어준다.
        CCAnimate *animate = [CCAnimate actionWithAnimation:animation];
        animate = [CCRepeatForever actionWithAction:animate];
        [sprite runAction:animate];
 
        // 애니메이션과 동시에 움직일 수 있다.
        id move = [CCMoveBy actionWithDuration:3 position:ccp(100,100)];
        [sprite runAction:move];
    }
    return self;
}

애니메이션도 액션과 비슷하게 사용하면 되는데, 먼저 addFrameWithFilename을 이용해 CCAnimaion에 프레임들을 담은 후, CCAnimate에 CCAnimation을 담아 액션으로 보내면 된다.

CCAnimation과 CCAnimate를 잘 구분해야 한다.

주의 사항으로는, 아무 동작이 없을 때 보여주는 첫 스프라이트를 추가해야 한다. 이것을 액션의 주체로서 사용하게 된다. (runAction)

하나의 이미지 파일 사용

-(id) init
{
    if ( (self=[super init]) )
    {
        self.isTouchEnabled = YES;
 
        // 걷는 장면이 들어있는 애니메이션 파일
        // 스프라이트가 아니라 텍스처이므로 addChild 하지 않는다.
        CCSprite *sprite = [CCSprite spriteWithFile:@"animation.png"];
 
        // CCAnimation은 action이 아니므로, 프레임만 담는다.
        CCAnimation *animation = [[CCAnimation alloc] init];
 
        // 1 프레임에 0.1초
        [animation setDelay:0.1];
        
        // 프레임 잘라내기
        for(int i=0;i<6;i++) {
            // 주의: 텍스처는 좌상단이 (0,0)이다.
            // 예제 이미지는 600x120의 이미지 파일을 사용한다고 가정한다.
            // 총 6프레임이므로, x좌표를 6번*100 증가시키며 프레임을 잘라낸다.
            [animation addFrameWithTexture:sprite.texture
                                      rect:CGRectMake(i*100, 0, 100, 120)];
        }
        
        // 아무 동작이 없을 때 보여주는 첫 스프라이트
        CCSprite *original = [CCSprite spriteWithFile:@"sprite1.png"];    
        original.position = ccp(240,160);
        original.tag = 1;    // 태그 1
        [self addChild:original];
 
        CCAnimate *animate = [CCAnimate actionWithAnimation:animation];
        animate=[CCRepeatForever actionWithAction:animate];    
        animate.tag = 2;    // 태그 2
        [original runAction:animate];
 
        id move = [CCMoveBy actionWithDuration:3 position:ccp(100,100)];
 
        // 콜백 함수 aniStop 호출
        id callback = [CCCallFunc actionWithTarget:self
                                          selector:@selector(aniStop)];
        
        // move, callback을 차례로 실행, nil 주의
        id total = [CCSequence actions:move, callback, nil];
 
        [original runAction:total];
 
    }
    return self;
}
 
// 애니메이션 멈추기
-(void)aniStop 
{
    // 태그 1을 가진 스프라이트를 얻는다.
    CCSprite *original = (CCSprite *)[self getChildByTag:1];
 
    // 태그 2를 가진 액션을 멈춘다.
    [original stopActionByTag:2];
}

첫 번째 예제와는 다르게 하나의 이미지만을 사용했으므로, for 루프를 통해 프레임을 잘라냈다. 프레임을 잘라내는 방법으로는 스프라이트의 텍스처 속성으로부터 하나의 프레임을 얻는 addFrameWithTexture를 사용하면 된다. (sprite.texture)

이 때 텍스처의 좌표는 아이폰처럼 좌측 상단이 (0,0)이므로 주의해야 한다.

영역의 크기는 CGRectMake로 알아내면 되는데, 예제에서는 각 프레임이 가로로 길게 늘어진 600x120 크기의 이미지라고 가정하고, x좌표를 프레임의 총 개수에 해당하는 여섯 번 반복 증가시켜 얻어냈다. y좌표는 변하지 않으므로 0을 써준다.

스프라이트와 액션에 태그를 준 이유는 콜백 함수에서 이것들을 호출하기 위해서이다. 콜백 함수를 호출하여 추가적인 동작을 실행할 수 있으며, 이 예제에서는 stopActionByTag를 이용하여 애니메이션의 동작을 멈추도록 했다.

즉, 총 6장의 애니메이션을 보여주면서 지정한 (100,100) 위치로 이동한 후 동작을 멈추게 된다. 최종 액션은 CCSequence를 썼기 때문에 모든 동작은 한 번만 실행하게 된다. 물론 CCRepeat 등을 이용하여 반복적인 동작을 구현할 수 있다.

'개발 > Cocos2d' 카테고리의 다른 글

cocos2d study #15  (0) 2012.10.26
cocos2d study #14  (0) 2012.10.26
cocos2d study #12  (0) 2012.10.26
cocos2d study #11  (0) 2012.10.26
cocos2d study #10  (0) 2012.10.26

cocos2d study #12

장면 (Scene)


장면(Scene)은 말 그대로 화면 내에서 어떠한 장면을 보여준다. 굳이 예를 들자면 게임 오버 장면, 앱을 실행할 때 가장 먼저 뜨는 인트로 장면을 들 수 있다. 그러나 꼭 이런 상황에서만 쓸 수 있는 것은 아니고, 적절한 순간에 Scene을 이용하여 현재 사용자가 보고 있는 화면을 바꿀 수 있다.

Scene 전환

replaceScene

CCDirector 객체로 여러 Scene을 관리하고 장면 전환을 하게 된다.

일반적으로 장면 전환은 replaceScene을 이용한다.

Scene이 전환되면, 현재의 Scene은 메모리에서 해제되고 당연하지만 dealloc이 호출된다.

화면을 터치하면 Scene이 전환되는 예제
// TestScene.m
 
// 다음 Scene 클래스
// 헤더 파일에서 CCLayer를 상속받도록 해준다.
// Scene도 수정할 일이 거의 없으므로
// cocos2d 프로젝트로부터 기본으로 만들어지는 Scene에서
// +(id) scene, -(id)init를 복사하여 사용하면 된다.
 
#import "TestScene.h"
#import "HelloWorldScene.h"
 
@implementation TestScene
 
+(id) scene
{
    CCScene *scene = [CCScene node];
    TestScene *layer = [TestScene node];
    [scene addChild: layer];
    return scene;
}
 
-(id) init
{
    if ( (self = [super init]) )
    {
        // 터치 이벤트 처리 활성화
        self.isTouchEnabled = YES;
 
        CCSprite *background = [CCSprite spriteWithFile:@"scene1.png"];
        background.position = ccp(240,160);
        [self addChild:background];
    }
    return self;
}
 
// 터치가 발생했을 때 처리
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 소스 코드 선두에 새로운 Scene의 헤더 파일을 #import 해야 한다.
    // 여기서는 HelloWorldScene.h가 된다.
    [ [CCDirector sharedDirector] replaceScene:[HelloWorld scene] ];    
}
 
-(void) dealloc
{
    // Scene이 전환되면 메모리에서 해제된다.
    [super dealloc];
}
 
@end

스택을 이용한 장면 전환

장면을 전환할 때 스택을 이용하려면 replaceScene 대신 pushScene으로 바꿔준다.

Scene을 받아주는 클래스에서는 장면을 다시 되돌릴 때 popScene을 이용하면 된다. 이 때는 replaceScene과 달리 스택에서 꺼내기만 하면 되므로 popScene에 인수는 불필요하다.

// 현재 Scene.m에서는 pushScene으로 장면 전환
[[CCDirector sharedDirector] pushScene:[TestScene scene] ];

// 다음 Scene.m에서는 popScene
[[CCDirector sharedDirector] popScene];

장면 전환 효과 (CCTransition)

장면을 전환할 때 여러 가지 효과를 줄 수 있다.

CCTransitionScene 클래스를 상속받은 여러 개의 클래스를 이용하여 화면 뒤집힘, 슬라이드, 천천히 어두워짐 등의 여러 효과를 주게 된다.

각 효과에 해당하는 클래스 목록은 입력할 때 ESC 키를 눌러 확인할 수 있다.

장면 전환 효과 예제

-(id) init
{
    if ( (self=[super init]) )
    {
        // 터치 이벤트를 받기 위해!
        self.isTouchEnabled = YES;
 
        CCSprite *background = [CCSprite spriteWithFile:@"scene1.png"];
        background.position = ccp(240,160);
        [self addChild:background];
    }
    return self;
}
 
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 페이지 전환 효과 CCTransitionPageTurn
    // transitionWithDuration:3을 주었으므로 3초 동안 장면이 전환된다.
    CCScene *scene = [CCTransitionPageTurn transitionWithDuration:3 scene:[TestScene scene]];
    [ [CCDirector sharedDirector] replaceScene:scene ];    
}

장면이 전환될 때 어떤 일이 생기는가?

clip_image002

장면이 전환될 때 현재 Scene에서는 onExit, dealloc이 호출되고, 다음 Scene에서는 init, onEnter, onEnterTransitionDidFinish 순서대로 호출된다.

이런 사실을 이용해서 장면이 완전히 전환되기 전에 어떠한 처리를 해 줄 수 있다.

즉, onEnter, onExit, onEnterTransitionDidFinish와 같은 메소드를 오버라이드하면 되는데, 이 때는 super를 반드시 호출해야 한다.

앞의 예제에서는 CCTransitionPageTurn의 duration을 3초 주었는데, 이 시간 동안 NSLog를 출력해 보고 싶다면 현재 Scene에서 onExit를 오버라이드하여 메소드를 만들면 된다.

만약 다음 Scene이 전환되기 직전에 어떤 처리를 하고 싶다면, 다음 Scene의 onEnter 메소드를 만들면 된다.

'개발 > Cocos2d' 카테고리의 다른 글

cocos2d study #14  (0) 2012.10.26
cocos2d study #13  (0) 2012.10.26
cocos2d study #11  (0) 2012.10.26
cocos2d study #10  (0) 2012.10.26
cocos2d study #09  (0) 2012.10.26

+ Recent posts