게임에 있어서 가장 중요한 것들 하나가 배경음악이라고 생각이 든다. 애니팡도 그렇고 드래곤 플라이트도 그렇고 단순하면서 반복적인 배경음악이 게임에 몰입을 하고 중독을 일으키게 할 만큼 엄청나게 중요하다. 이번 파트에서는 배경음악과 사운드 효과의 재생을 구현해 보도록 하자. 


배경음악과 사운드 효과

cocos2d에서 배경음악과 사운드 효과는 SimpleAuidoEngine을 사용한다. 즉각 즉각 필요할 때 마다 메모리에 올려서 재생을 해도 상관은 없지만, 사운드의 경우에는 용량도 크고 해서 게임 시점에 메모리에 올릴경우 지연이 발생한다. 그래서 게임 초반에 preload를 사용하여 미리 메모리에 올려 놓고 필요할 때는 재생만 해서 사용한다. mp3, wav 등 다양한 형식의 음악 파일을 지원한다. 단, 너무 파일 크기가 크면 게임 구동에 문제가 있을수 있으니 삼가하도록 하자. 


게임 시작 시점에 preload를 하기 위해 AppDelegate.m 로 이동한다. application:didFinishLaunchingWithOptions: 메소드의 가장 아래부분에 아래와 같이 코드를 추가한다.  

(* 배경 음악 및 효과음은 저작권 문제로 첨부하지 못하였습니다. 적절한 음악 및 효과음을 찾으면 추가하도록 하겠습니다.)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

...

    // 배경음악을 위한 음악

    [[SimpleAudioEngine sharedEngine] preloadBackgroundMusic:@"background_music.mp3"];

    // 적이 죽을 경우을 위한 사운드 효과

    [[SimpleAudioEngine sharedEngine] preloadEffect:@"mon_die.wav"];

    // 적이 피해를 입었을 경우을 위한 사운드 효과

    [[SimpleAudioEngine sharedEngine] preloadEffect:@"mon_damage.wav"];

  return YES;

}

각각의 상황에 따른 음악 및 효과 파일을 싱글톤을 사용하여 preload 한다. 배경음악의 경우에는 preloadBackgroundMusic를 사용하여 preload를 하고, 사윤드 효과의 경우에는 preloadEffect를 사용하여 preload 한다.


배경음악 및 효과음 재생을 구현하기 위해서 GameLayer.m 으로 이동한다. 그리고 SimpleAudioEngine을 사용하기 위해서 아래와 같이 헤더 파일을 임포트한다. 

#import "SimpleAudioEngine.h"


게임이 실행되면 바로 배경음악을 재생하기 위해서 onEnter 메소드의 마지막에 아래와 같이 코드를 추가한다.

- (void)onEnter {

    [super onEnter];

...

    //시작 되면 배경 백그라운드 음악이 재생

    [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background_music.mp3" loop:YES];

}

싱글톤으로 SimpleAudioEngine이 만들어져서 어디서나 접근을 할 수가 있다. 음악 재생을 위해서 playBackgroundMusic 메소드를 사용하여 배경음악을 재생하고 계속적으로 반복을 위해서 loop값을 YES로 설정한다. 

그리고 충돌시 일어나는 효과음을 구현하기 위해서 update: 메소드의 충돌이 일어나는 부분에 아래 코드를 추가한다. 

- (void)update:(ccTime)dt {

...

            //총알과 적이 충돌이 나는지를 체크

            if (!isCollision && CGRectIntersectsRect(bullet.boundingBox, enemy.boundingBox)){

...

                //싸운드 효과를 재생한다.

                [[SimpleAudioEngine sharedEngine] playEffect:@"mon_damage.wav"];

                //미사일로 적을 공격해서 0점을 받아오는지를 체크

                if (![enemy attackedWithPoint:[bullet bulletType]]){

                    //싸운드 효과를 재생한다.

                    [[SimpleAudioEngine sharedEngine] playEffect:@"mon_die.wav"];


                    //적이 폭발되면 먼지를 뿌려주기위한 노드 생성

                    Dust *dust = [Dust node];

...

}

배경음악과 마찬가지로 싱글톤으로 SimpleAudioEngine이 만들어져서 어디서나 접근을 할 수가 있다. 효과음 재생을 위해서  배경음악 재생과 다른 playEffect 메소드를 사용하여 효과음을 한번만 재생한다. 총알과 적이 충돌 했을 경우와 적이 폭발하는 경우의 각각의 효과음을 재생하도록 한다. 

Cmd(⌘) + R을 눌러서 실행을 해보자.

게임이 시작되면 배경음악이 재생되고 총알과 적이 충돌이 되었을 때, 적이 죽었을 때의 경우 모두 효과음이 나오는 것을 확인 할 수 있다.


이로서 드래곤 플라이트 따라 만들기 (손)연재가 모두 끝났다. 따라하면서 느꼈겠지만 크게 어려움이 없을 것이다. 물론 게임 개발을 엔진부터 개발을 하면 엄청나게 어려움이 있을 것이다. 하지만 뛰어난 개발자분들 께서 좋은 게임 엔진을 많이 만들어 놨고, 우리들은 그냥 그것을 사용해서 만들면 된다. :-) 겨우 약 600줄로 게임을 만들수 있다. (주석 포함 1200줄) 코딩을 할 수 있으면 누구나 도전을 했으면 하는 바람이다. 

게임에 대해 전혀 모르는 저도 했으니 여러분들 또한 전혀 문제가 없을 겁니다. 다른 시리즈로 조만간 찾아 오겠습니다.
그리고 '모바일 게임 따라 만들기 시리즈'로 조만간 eBook 형태로도 찾아뵙겠습니다. :-)  커피숍에서 코딩 할 수 있게 많은 도움 부탁드립니다. 꾸벅. 

저작자 표시 비영리
신고
Posted by KraZYeom

폭발 효과 에니매이션 

총알에 맞은 적 또는 플레이어 케릭터가 적과 충돌할 경우에 폭발하는 애니메이션이 보이게 된다. cocos2d에서 제공하는 파티클을 사용하여서 폭발하는 효과를 더 현실감 있고 화려하게 구현 하는 방법도 있다. 이 튜토리얼에서는 한두가지의 이미지로 폭발 파티클 효과를 비슷하게 구현하도록 하겠다. 


폭발 스프라이트 애니메이션을구현 하기 위해서 CCSprite를 상속 받아서 Dust 라는 이름으로  클래스를 만든다. 


Dust.h 로 이동하여 아래 코드를 추가한다. 

@interface Dust : CCSprite {

  CCArray *dusts;

  CCArray *explosions;

}


@property (nonatomic, weak) CCSpriteBatchNode *batch;


-(void)animateDusts;

-(void)animateExplosions;

폭발할 때 일어나는 먼지 스프라이트를 저장하는 배열과 폭발효과의 스프라이트를 저장하는 배열을 생성한다. 그리고 효과적으로 스프라이트를 사용하기 위한 배치노드를 프로퍼티를 만든다. 먼지효과 애니메이션과 폭발 애니메이션을 실행하는 메소드 2개를 생성한다. 


구현을 위해 Dust.m 으로 이동한다. 

폭발을 위한 초기 설정을 위해 init 메소드에 아래 코드를 추가한다. 

- (id)init

{

  self = [super init];

  if (self) {

    self.anchorPoint = CGPointZero;

    //동일한 이미지의 반복 사용의 효율성을 위해서 배치노드 생성

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

    //배치노드 추가

    [self addChild:_batch];

    

    //10개의 먼지 이미지를 사용할 배열을 만든다.

    dusts = [CCArray arrayWithCapacity:kMaxDust];

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

      //먼지 이미지를 스프라이트 시트에서 가져온다.

      CCSprite *dust = [CCSprite spriteWithSpriteFrameName:@"dust.png"];

      //에니메이션 될때 보여 주기 위해 처음에는 숨긴다.

      dust.visible = NO;

      //작은거에서 커지는 에니메이션을 위해 1/10 으로 작게 만든다.

      dust.scale = 1.0/10.0;

      //배치 노드에 자식 노드로 추가 한다.

      [_batch addChild:dust];

      //배열에 먼지를 추가한다.

      [dusts addObject:dust];

    }

    

    //플레이어 캐릭터가 폭발될 때의 스프라이트를 위한 배열

    explosions = [CCArray arrayWithCapacity:kMaxDust];

    

    //오른쪽  폭발 효과 스프라이트

    CCSprite *rightDamage = [CCSprite spriteWithSpriteFrameName:@"damage.png"];

    //폭발시에만 보이게, 숨김 처리

    rightDamage.visible = NO;

    //터지는 효과를 위해 작게 설정한다.

    rightDamage.scale = 0.5;

    rightDamage.anchorPoint = ccp( 0.0, 0.0 );

    [self addChild:rightDamage];

    [explosions addObject:rightDamage];


    //왼쪽 폭발 효과 스프라이트

    CCSprite *leftDamage = [CCSprite spriteWithSpriteFrameName:@"damage.png"];

    //폭발시에만 보이게, 숨김 처리

    leftDamage.visible = NO;

    //터지는 효과를 위해 작게 설정한다.

    leftDamage.scale = 0.5;

    // 동일한 이미지를 왼쪽에 사용하기 위해서 X, Y 뒤집기(플립)처리

    leftDamage.flipX = YES;

    leftDamage.flipY = YES;

    leftDamage.anchorPoint = ccp( 1.0, 1.0 );

    [self addChild:leftDamage];

    [explosions addObject:leftDamage];

    

    // 가운데 처지는 스프라이트

    CCSprite *explosion = [CCSprite spriteWithSpriteFrameName:@"explosion_01.png"];

    //폭발시에만 보이게, 숨김 처리

    explosion.visible = NO;

    //터지는 효과를 위해 작게 설정한다.

    explosion.scale = 0.5;

    [self addChild:explosion];

    [explosions addObject:explosion];

  }

  return self;

}

코드는 길지만 원리는 간단하다. 우선 먼지 애니메이션의 경우에는 하나의 이미지를 효과적으로 사용하기 위해서 배치노드를 생성한다. 그리고 먼지 이미지 10개를 숨김으로 해서 1/10 크기로 배치노드에 추가 한다. 폭발 애니메이션의 경우에도 이미지를 숨김으로 해서 1/2 크기로 추가 한다. 왼쪽 이미지의 경우에만 뒤집기 효과인 플립을 줘서 X, Y축으로 뒤집는다.  그리고 터지는 스프라이트도 동일하게 추가 한다. 


초기화는 되었으니 플레이어와 적이 충돌하였을 경우에 일어나는 애니메이션을 구현하도록 하겠다. animateExplosions 메소드에 아래코드를 추가한다. 

-(void)animateExplosions{

  //배열에서 각각의 폭파 스프라이트를 꺼낸다.

  for (CCSprite *explosion in explosions) {

    //보이게 설정을 한다.

    explosion.visible = YES;

    //0.4 동안 3 커지게 설정

    CCScaleTo *scale = [CCScaleTo actionWithDuration:0.4 scale:3.0];

    //애니메이션이 끝나면 숨기고, 삭제한다.

    CCCallBlock *block = [CCCallBlock actionWithBlock:^{

        explosion.visible = NO;

        [self removeFromParentAndCleanup:YES];

    }];

    //움직임 에니메이션이 끝나면 block 순차적으로 실행하기 위한 시퀀시 생성

    CCSequence *seq = [CCSequence actions:scale, block, nil];

    [explosion runAction:seq];

  }

}


배열에서 각각의 스프라이트를 꺼낸 뒤, 보이게 설정하고, CCScaleTo 을 사용하여 특정 시간동안 스프라이트를 확대하는 액션을 생성한다. 그리고 애니메이션이 끝나면 숨기고, 삭제하는 블럭 타입의 콜백을 생성한다. 그리고 액션과 블럭 콜백을 순차적으로 실행하기 위해서 시퀀시를 생성한 후, 액션을 실행한다. 


플레이어 폭발 애니메이션을 간단하였지만, 총알과 적의 충돌시 샐행되는 먼지 효과 애니메이션을 살짝 복잡하다. 어렵지 않으니 차근차근 따라해보자. 우선 애니메이션을 위해서 animateDusts 메소드를 생성하고 아래코드를 붙여넣는다. 

-(void)animateDusts{

  //에니메이션을 위해서 먼지를 세팅

  for (CCSprite *dust in dusts) {

    //숨긴 이미지를 다시 보이게

    dust.visible = YES;

    

    //10개의 이미지의 크기를 랜덤으로 가져옴

    float scaleRandom = 0.1 + ( (double)arc4random() / (double)0xffffffff );

    //0.3 시간동안 크기 변경 에니메이션

    CCScaleTo *scale = [CCScaleTo actionWithDuration:0.3 scale:scaleRandom];

    

    // 터지는 x위치를 위해서 랜덤 1 or -1

    int x = ( (double)arc4random() / (double)0xffffffff ) < 0.5 ? -1 : +1;

    float xRandom = 5 + 4 * ( ((double)arc4random()/(double)0xffffffff) * 10 * x );

    // 터지는 y위치를 위해서 랜덤 1 or -1  

    int y = ( (double)arc4random() / (double)0xffffffff ) < 0.5 ? -1 : +1;

    float yRandom = -30 + 4 * ( ((double)arc4random() / (double)0xffffffff ) * 10 * y );

    //0.3 시간동안 위치 변경 에니메이션

    CCMoveTo *move = [CCMoveTo actionWithDuration:0.3 position:ccp(xRandom, yRandom)];


    //에니메이션이 끝나면 숨기고 자신은 삭제를 위해서 블럭 함수 생성

    CCCallBlock *block = [CCCallBlock actionWithBlock:^{

        dust.visible = NO;

        [self removeFromParentAndCleanup:YES];

    }];

    //움직임 에니메이션이 끝나면 block 순차적으로 실행하기 위한 시퀀시 생성

    CCSequence *seq = [CCSequence actions:move, block, nil];

    //runAction 연이어 하면 동시에 크기와 움직임 엑션이 일어난다.

    [dust runAction:scale];

    [dust runAction:seq];

  }

}

기본 원리는 플레이어 폭발 애니메이션과 동일하다. 추가적으로 CCMoveTo를 사용해서 가운데서 터져서 양사방으로 움직이는 효과가 들어간다. 우선 숨겼던 이미지를 보이고 10개의 이미지가 모두 다른 크기로 커지게 하기 위해서 랜덤값을 생성한다. 그리고 CCScaleTo를 사용하여 0.3초 동안 확대한다. 그리고 10개의 이미지 모두 터지면 적의 중심으로 양사방으로 움직이게 하기위해서 x, y좌표를 랜덤으로 설정한다. 그리고 CCMoveTo를 사용해서 이동시킨다. 그리고 애니메이션이 끝나면 숨기고, 삭제하는 블럭 타입의 콜백을 생성한다. 그리고 액션과 블럭 콜백을 순차적으로 실행하기 위해서 시퀀시를 생성한 후, 액션을 실행한다. 


먼지 스프라이트 클래스는 완성되었다. 적용을 하기 위해서 GameLayer.m 로 이동한다. 

상단에 아래 코드를 추가하여서 클래스를 추가할 수 있도록 한다. 

#import "Dust.h"


총알과 적이 충돌해서 적의 에너지가 0 일때를 체크해서 폭발하는 효과 애니메이션을 실행할 것이. update:(ccTime)dt 에서 //미사일로 적을 공격해서 0점을 받아오는지를 체크하는 부분안에 아래 코드를 추가한다. 

- (void)update:(ccTime)dt {

...

  //미사일로 적을 공격해서 0점을 받아오는지를 체크

  if (![enemy attackedWithPoint:[bullet bulletType]]){


    //적이 폭파되면 먼지를 뿌려주기위한 노드 생성

    Dust *dust = [Dust node];

    //위치는 적이 폭파한 위치

    dust.position = enemy.position;

    //화면에 뿌려주기 위해 자식노드로 추가

    [self addChild:dust];

    //폭파하는 에니메이션 실행

    [dust animateDusts];

  }


...

}

적의 에너지가 0 이면 폭발 스프라이트 노드를 생성하고 적의 현재 위치에 먼지를 배치하고 난뒤에 폭발하는 애니메이션을 실행한다.


그리고 적과 플레이어가 충돌했을 경우에는 update:(ccTime)dt //적과 플레이어가 충동하는지를 체크하는 부분의 미사일을 없에는 부분 바로 밑에 아래 코드를 추가한다. 

- (void)update:(ccTime)dt {

...

        //적과 플레이어 케릭터가 충돌하는지를 체크

        if (!isCollision && CGRectIntersectsRect(enemy.boundingBox, _player.boundingBox)) {

            isCollision = YES;

            if (isCollision){

                _player.visible = NO;

                // 충돌하게 되면 마사일을 없엔다.

                [self unschedule:@selector(updateBullet)];

                for (Bullet *bullet in bulletsArray) {

                    bullet.visible = NO;

                    [bullet removeFromParentAndCleanup:YES];

                }

                

                //적이 폭발되면 먼지를 뿌려주기위한 노드 생성

                Dust *dust = [Dust node];

                //위치는 적이 폭발된 위치

                dust.position = _player.position;

                //화면에 뿌려주기 위해 자식노드로 추가

                [self addChild:dust z:1000];

                //폭발하는 에니메이션 실행

                [dust animateExplosions];

             ...
         }
...
}

적과 충돌을 하게 되면 폭발 노드를 생성하고 사용자 케릭터 배치시키고 폭발하는 애니메이션을 실행한다. 


Cmd(⌘) + R을 눌러서 실행을 해보자. 

총알과 적이 충돌할 경우, 플레이어 케릭터와 적이 충돌할 경우 폭발하는 애니메이션이 실행되는 것을 확인 할 수 있다. 


기본적인 게임 구현은 모두 끝났다. 다음 파트에서는 배경음악 재생과 총알과 적이 충돌했을 때 효과음, 적이 폭발할 때 효과음을 재생하는 것을 구현하도록 하겠다. 

저작자 표시 비영리
신고
Posted by KraZYeom

HUD (Head-up Display)


대부분 게임에는 시간이나 점수를 보여주는 HUD가 있다. 우리 게임에도 간단하게 HUD를 추가하도록 하자. 

단순하게 움직인 거리를 나타내는 것만 표시할 것이다. 적을 죽여서 점수에 반영하는 것은 숙제로 남기기로 하겠다. 


우선 CCLayer를 상속받아서 HUDLayer이름으로 클래스 하나를 생성한다. 

그리고 HUDLayer.h에 아래 코드와 같이 작성한다. 


@interface HUDLayer : CCLayer {

    CCLabelTTF *scoreLabel;

}


-(void)setScoreText:(int)score;


점수(거리)를 보여주는 Label을 인스턴스 변수로 추가한다. 그리고 외부에서 점수를 메시지로 보낼 수 있도록 메소드를 하나 생성한다. 


HUDLayer.m으로 이동해서 초기 설정을 위해 init 메소드에 아래와 같이 추가한다. 

- (id)init

{

    self = [super init];

    if (self) {

        //레이블의 초기값을 0M 으로 한다. 시스템 폰트 사용

        scoreLabel = [CCLabelTTF labelWithString:@"0M" fontName:@"Arial" fontSize:20];

        //위치를 정한다.

        scoreLabel.position = ccp(280, 450);

        //화면에 뿌리기위해 자식노드에 추가 

        [self addChild:scoreLabel];

    }

    return self;

}

시스템 폰트를 사용해서 문자열의 초깃값을 "0M"로 설정한다. 그리고 오른쪽 위에 배치한 후 자식 노드로 추가해서 화면에 보여준다.


그리고 다른 클래스에서 텍스트를 변경할 수 있도록 메소드를 작성한다. 

-(void)setScoreText:(int)score{

    //레이블을 위한 스트링

    NSString *scoreString = [NSString stringWithFormat:@"%dM", score];

    //레이블의 스트링에 할당한다.

    scoreLabel.string = scoreString;

}

점수값을 받으면 스트링으로 변환한다. 그리고 레이블의 스트링값에 넣어준다. 


기본적인 HUDLayer를 작성하였다.


GameLayer에 적용을 위해서 GameLayer.h로 이동을 한다. 

점수를 저장할 인스턴스 변수 하나를 아래와 같이 추가한다. 

    int score;


그리고 프로퍼티를 설정한다. 

@property (nonatomicweakHUDLayer *hud;


GameLayer.m으로 이동해서 init 함수에서 아래와 같이 코드를 추가한다.

        //점수를 위한 초기화

        score = 0;

시작할 때 점수를 0으로 초기화한다. 


그리고 시간이 흘러감에 따라서 점수를 올려줄 수 있게 아래와 같이 코드를 추가한다.

//일정 시간마다 점수를 올려준다.

-(void)updateScore:(ccTime)dt{

    [_hud setScoreText:score++];

}

움직이는 거리를 계산하기보단 간단하게 시간을 거리로 두면 간단하게 계산이 된다. 한번 호출될 때 마다 1M씩 점수를 올려준다. 


그리고 onEnter 메소드에 아래와 같이 코드를 추가한다.


- (void)onEnter {

    [super onEnter];

...

    //점수를 위한 스케쥴

    [self schedule:@selector(updateScore:) interval:0.01f];

}


0.01초마다 updateScore: 메소드를 호출을 하는 스케쥴러를 추가한다. 


끝난 것처럼 보인다. 하지만 HUDLayer를 자식 노드에 추가하지 않아서 지금은 화면에 보이지 않는다. 


GameScene.h으로 이동해서 HUDLayer를 추가하도록 하자.


#import "HUDLayer.h"

...

@property (nonatomic, weak) HUDLayer *hudLayer;

HUDLayer를 임포트 하고 프로퍼티로 설정을 한다. 


GameScene.m으로 이동해서 init 메소드를 아래코드로 변경을 한다. 

- (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];

      

        //게임 레이어의 HUD HUD레이어 전달

        self.gameLayer.hud = _hudLayer;

    }

    return self;

}

HUDLayer를 자식노드로 추가를 하고 GameLayer의 hud에 hudLayer를 전달한다. 


Cmd(⌘) + R을 눌러서 실행을 해보자. 

시간이 지남에 따라서 오른쪽 위에 거리를 나타내는 점수가 갱신면서 보여지는 것을 확인할 수 있다. 




다음 장에서는 적이 터질때 터지는 애니메이션을 구현하도록 하겠다. 

저작자 표시 비영리
신고
Posted by KraZYeom

기본적인 게임 기능은 다 구현되었지만 밋밋한 화면을 역동적으로 하기 위해서 애니메이션을 추가하도록 하겠다. 이번 장에서는 플레이어와 적의 날갯짓 애니메이션을 구현하겠다. 


날갯짓 애니메이션

적의 날갯짓을 하기 위해서는 기본적인 날개 스프라이트를 왼쪽 날개는 오른쪽에 오른쪽 날개는 왼쪽에 축을 두고 위아래로 살짝씩만 회전을 하면 된다. 


우선 enemy.h 로 이동을 해서 아래 코드를 변수로 추가한다.

    BOOL wingDown;

날개가 내려갔는지 올라갔는지를 체크하기 위한 변수이다. 


그리고 본격적으로 애니메이션을 구현하기 위해 enemy.m 파일로 이동한다. 

특정 시간마다 반복적으로 메소드를 호출해야 하기때문에 스케쥴러를 위한 메소드 -(void)updateWings:(ccTime)dt 를 하나 만들어 아래와 같이 코드를 추가한다.

-(void)updateWings:(ccTime)dt {

    //왼쪽 오른쪽 날개짓을 위환 회전

    CCRotateTo *leftWingDown = [CCRotateTo actionWithDuration:0.2 angle:-80];

    CCRotateTo *leftWingUp = [CCRotateTo actionWithDuration:0.2 angle:0];

    CCRotateTo *rightWingDown = [CCRotateTo actionWithDuration:0.2 angle:80];

    CCRotateTo *rightWingUp = [CCRotateTo actionWithDuration:0.2 angle:0];


    //번갈아 가면서 날개짓을 한다.

    if ( (wingDown = !wingDown) ){

        [_leftWing runAction:leftWingDown];

        [_rightWing runAction:rightWingDown];

    }else{

        [_leftWing runAction:leftWingUp];

        [_rightWing runAction:rightWingUp];

    }

}

회전을 위한 액션인 CCRotateTo을 날개가 내려갔을 때, 올라갔을 때 그리고 왼쪽, 오른쪽 날개 4개의 액션을 생성한다. 시간은 0.2초, 회전 각도는 80도로 한다. 그리고 내려갔을 경우는 올라가는 애니메이션을 실행하고, 올라갔을 경우는 내려가는 애니메이션을 실행한다. 


그리고 실행하면 바로 애니메이션을 호출하기 위해서 onEnter 메소드에 아래와 같이 스케줄러 코드를 추가한다.

-(void)onEnter{

...

    //날개짓을 위한 0.2 마다 메소드 호출

    [self schedule:@selector(updateWings:) interval:0.2];

}



Cmd(⌘) + R을 눌러서 실행을 해보자. 




적의 날개를 추가하는 방법과 같은 방법으로 플레이어 캐릭터에도 적용을 하도록 하자. 


Player.h 파일로 이동해서 날갯짓을 위한 변수를 하나 만든다.

    BOOL wingDown;


구현을 위해 Player.m 파일로 이동한다. 


Enemy.m 과 똑같은 방법으로 특정 시간마다 반복적으로 메소드를 호출해야 하므로 스케쥴러를 위한 -(void)updateWings:(ccTime)dt 메소드를 만들어 아래와 같이 코드를 추가한다.

-(void)updateWings:(ccTime)dt{

    //왼쪽 날개 에니메이션을 위한 날개 내렸다 올리기

    CCRotateTo *leftWingDown = [CCRotateTo actionWithDuration:0.2 angleX:-30 angleY:60];

    CCRotateTo *leftWingUp = [CCRotateTo actionWithDuration:0.2 angleX:0 angleY:0];

    //오른쪽 날개 에니메이션을 위한 날개 내렸다 올리기

    CCRotateTo *rightWingDown = [CCRotateTo actionWithDuration:0.2 angleX:30 angleY:-60];

    CCRotateTo *rightWingUp = [CCRotateTo actionWithDuration:0.2 angleX:0 angleY:0];

    //날개짓을 번갈아 가기 위해

    if ( (wingDown = !wingDown) ){

        [_leftWing runAction:leftWingDown];

        [_rightWing runAction:rightWingDown];

    }else{

        [_leftWing runAction:leftWingUp];

        [_rightWing runAction:rightWingUp];

    }

}


적의 날갯짓 애니메이션과 다른 점이 보일 것이다. 적은 단순히 고정축인 Z축으로 으로 회전을 했다면, 플레이어 캐릭터의 날개는 X축과 Y축으로 회전을 해서 약간 비트는 느낌이 들게 한다. 회전을 위한 액션인 CCRotateTo을 내려갔을 때, 올라갔을 때 그리고 왼쪽, 오른쪽 날개 4개의 액션을 생성한다. 시간은 0.2초, x축 회전 각도는 30도, y축 회전 각도는 60도로 한다. 그리고 내려갔을 경우는 올라가는 애니메이션을 실행하고, 올라갔을 경우는 내려가는 애니메이션을 실행한다. 


그리고 실행하면 바로 애니메이션을 호출하기 위해서 onEnter 메소드에 아래와 같이 스캐쥴러 코드를 추가한다.

-(void)onEnter{

...

    //0.2초에 한번씩 날개짓을 한다.

    [self schedule:@selector(updateWings:) interval:0.2];

}


0.2초에 한번씩 날갯짓을 한다.


Cmd(⌘) + R을 눌러서 실행을 해보자. 


정적인 느낌의 게임이 날갯짓 애니메이션 하나를 추가하니, 좀더 생동감이 넘치게 되었다. :-)


다음은 화면에 점수를 표시하는 레이어인 HUD를 추가하도록 할 예정이다. 



저작자 표시 비영리
신고
Posted by KraZYeom

이번 파트에서는 총알과 플레이어 충돌,  적과 플레이어 충돌 그리고 적의 에너지 게이지 추가를 구현하겠다. 


총알과 플레이어 케릭터 충돌처리


총알과 플레이어 케릭터의 충돌처리는 아주 간단하다. 플레이어 스프라이트의 바운딩 박스 영역과 총알 스프라이트의 바운딩 박스의 영역이 겹치면 충돌했다고 처리하면 된다. 물론 더 세세한 충돌처리를 위해서는 케릭터의 모양을 여러 개의 영역으로 나눠서 충돌했는지를 체크하거나, 더 정확하게 하려면 플레이어 케릭트 외곽선을 전부 따서 하는 방법도 있다. 여러가지 방법이 있긴 하지만 가장 간단한 방법을 선택하도록 한다. 

gameLayer.h 에 이동하여 충돌하였는지를 체크 할수 있게 변수하나를 추가한다. 

    BOOL isCollision;


충돌 처리를 위해서 gameLayer.m으로 이동해서 - (void)update:(ccTime)dt  메소드에 아래 코드를 추가한다.   

  //총알 플레이어와 케릭터 충돌을 위해서 배열에서 적을 하나 꺼낸다.

for (Enemy *enemy in enemysArray) {

    //적이 죽은 상태이면 그냥 넘어간다.

    if (!enemy.state) continue;

    

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

    for (Bullet *bullet in bulletsArray) {

        //총알이 적에 맞아서 없어진 상태면 그냥 넘어간다.

        if (!bullet.visible) continue;

        

        //총알과 적이 충돌이 나는지를 체크

        if (!isCollision && CGRectIntersectsRect(bullet.boundingBox, enemy.boundingBox)){

            //총알을 없애고

            bullet.visible = NO;

            //싸운드 효과를 재생한다.

            //미사일로 적을 공격해서 0점을 받아오는지를 체크

            if (![enemy attackedWithPoint:[bullet bulletType]]){

                //싸운드 효과를 재생한다.

                //적이 폭파되면 먼지를 뿌려주기위한 에니메이션

            }

        }

    }

}
적을 담고 있는 배열에서 적을 꺼내서 죽었는지 상태 체크를 하고, 죽지 않았으면 총알의 배열에서 총알을 꺼내서 총알이 맞아서 없어진 상태인지를 체크 한다. 그리고 CGRectIntersectsRect(CGRect rect1, CGRect rect2)메소드를 통해서 두개의 영역이 겹쳤는지를 체크 한다. 충돌이 일어났으면 총알을 없애고, 총알 타입에 따른 적이 얼마나 피해를 입었는지를 체크 한다. 

총알에 따라서 적이 얼마나 피해를 입었는지 체크를 위해서 Enemy.h 로 이동해서 아래 코드를 추가한다. 

-(NSInteger)attackedWithPoint:(NSInteger)point;


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

-(NSInteger)attackedWithPoint:(NSInteger)point {

    //공격 받은 숫자 만큼 에너지를 줄여준다.

    _energy -= point;

    //0미만으로 되면 0 아니면 현재 에너지를 반환

    if ( _energy <= 0 ) {

        //죽으면 숨긴다

        [self destroy];

    }

    return _energy;

}


-(void)destroy {

    //적이 죽으면 상태 값을 변경

    _state = kDestoryed;

    //에너지는 0으로 한다.

    _energy = 0;

    //그리고 숨긴다.

    self.visible = NO;

}

미사일의 포인트 만큼 적의 에너지를 줄이고, 0이하가 되면 적을 죽인다. 상태값을 죽은 상태로 하고 에너지는 0, 그리고 숨긴다.  

적과 플레이어 케릭터 충돌처리

적과 플레이어 케릭터 충돌처리도 간단하다. 총알과 적과 충돌처리처럼 겹치는 영역이 있으면 충돌처리를 하고 게임을 끝나게 하면 된다. 


gameLayer.m 에서 위에 추가한 for (Enemy *enemy in enemysArray) {} 괄호 안에서 마지막에 아래 코드를 추가한다. 


//적과 플레이어 케릭터가 충돌하는지를 체크

if (!isCollision && CGRectIntersectsRect(enemy.boundingBox, _player.boundingBox)) {

    isCollision = YES;

    if (isCollision){

        _player.visible = NO;

        // 충돌하게 되면 총알을 다 없앤다.

        [self unschedule:@selector(updateBullet)];

        for (Bullet *bullet in bulletsArray) {

            bullet.visible = NO;

            [bullet removeFromParentAndCleanup:YES];

        }

        

        CCCallBlock *allStop = [CCCallBlock actionWithBlock:^{

            //터치 이벤트를 더이상 받지 않는다.

            self.isTouchEnabled = NO;

        }];

        //딜레이를 위한 엑션

        CCDelayTime *delay = [CCDelayTime actionWithDuration:2.0f];

        //딜레이후 메뉴로 나가기 위한 엑션 블럭

        CCCallBlock *block = [CCCallBlock actionWithBlock:^{

            //메뉴 레이어로 돌아간다.

            [[CCDirector sharedDirector] replaceScene:[MenuLayer scene]];

        }];

        //엑션을 순서대로 준비.

        CCSequence *seq = [CCSequence actions:allStop, delay, block, nil];

        //엑션 실행

        [self runAction:seq];

    }

}

메소드가 짧은 시간에 여러번 호출 되기 때문에 충돌이 났는지 나지 않았는지를 체크한다. 충돌이 없는 상태에서 영역이 겹치게 되면, 충돌 체크를 하고 총알 스케줄러를 멈춰서 더 이상 총알이 나가지 않게 한다. 콜백 블럭에 터치를 받지 않게 처리하고, 바로 끝나면 이상하므로 2초간 게임화면이 보이게 한다. 그리고 다시 콜백 블럭으로 메뉴 화면으로 빠져 나가게 한다. 이 3개의 엑션들을 순차적으로 액션처리 한다. 

모든 게임 기본 기능은 다 구현 되었다. 그런데 적이 총알을 맞았는데 얼마만큼 에너지가 줄었는지 체크를 해야 한다. 적의 하단에 에너지 게이지를 추가 하도록 하겠다. 

CCNode를 상속받아서 EnegyGauge라는 클래스를 추가한다 
그리고 EnergyGauge.h에 아래 코드를 추가한다. 

@interface EnergyGauge : CCNode {

    CGFloat maxValue;

    CGFloat currentValue;

    CGSize maxSize;

}

+ (id)initWithMaxSize:(CGSize)size maxValue:(CGFloat)_maxValue;

- (id)initWithMaxSize:(CGSize)size maxValue:(CGFloat)maxVal;

- (void)updateBar:(CGFloat)_currentValue;

게이지의 최대값, 현재값 그리고 최대 크기를 저장하는 변수를 생성하고, 생성하는 메소드와 외부에서 게이지바를 변경할 수 있게 해주는 메소드를 추가한다. 

EnergyGauge.m으로 이동해서 생성 관련 메소드를 아래와 같이 추가하여 작성한다. 

+(id)initWithMaxSize:(CGSize)size maxValue:(CGFloat)_maxValue{

     return [[self alloc] initWithMaxSize:size maxValue:_maxValue];

}


- (id)initWithMaxSize:(CGSize)size maxValue:(CGFloat)_maxValue{

    self = [super init];

    if ( self ){

        maxSize = size;

        currentValue = maxValue = _maxValue;

    }

    return self;

}



에너지 게이지를 스프라이트를 사용해서 그릴수도 있지만 간단하게 OpenGL을 이용해서 그리도록 한다. 아래 코드를 복사, 붙여 넣기한다. 

- (void)draw{

    //최대값 기준으로 % 환산한 너비

    CGFloat width = (currentValue / maxValue) * (float)maxSize.width;

    //라인의 두깨는 높이값이다.

    glLineWidth(maxSize.height);

    //100% 때는 Green

    if (currentValue == maxValue) {

        ccDrawColor4B(255, 255, 0, 255);

    // 외에는 Red

    } else {

        ccDrawColor4B(255, 0, 0, 255);

    }

    //선을 그린다.

    ccDrawLine(ccp(0, 0), ccp(width, 0));

}

너비 값을 구하고, 게이지의 두깨를 설정한다. 그리고 최대 값일때는 노란색(배경색이 초록색이여서 노락색으로 변경함), 아닐 경우에는 빨간색으로 설정한다. 그리고 길이, 두깨 만큼 선을 그린다. 

외부에서 게이지바의 업데이트 하기 위해 아래 코드를 추가한다. 

- (void)updateBar:(CGFloat)_currentValue{

    //값을 값을 현재 값으로 할당한다.

    currentValue = _currentValue;

    //0 이하면 0으로, 최대값을 초과하면 최대값으로 보정한다.

    if (currentValue < 0 ) currentValue = 0;

    else if (currentValue > maxValue) currentValue = maxValue;

}

현재 값을 할당하고, 0미만이면 0으로 설정하고 최대 값을 벗어 나면 최대값으로 설정한다. 

적에게 게이지를 추가하기 위해서 Enemy.h로 이동해서 프로퍼티를 아래와 같이 추가한다. 

@property (nonatomic, strong) EnergyGauge *gauge;


그리고 - (id)init 메소드 아래에 다음 코드를 추가한다. 

        //에너지 게이지 객채를 생성한다

        _gauge = [EnergyGauge initWithMaxSize:CGSizeMake(self.boundingBox.size.width, 10) maxValue:100];

        //에너지 게이지를 자식으로 추가.

        [self addChild:_gauge];

게이지 객체를 너비와 높이값 그리고 최대 값으로 생성한다. 그리고 자식으로 추가한다. 

적이 공격을 받았을 경우 처리를 하기 위해서 -(NSInteger)attackedWithPoint:(NSInteger)point 메소드 안 _energy -= point; 코드 아래에 다음 코드를 추가한다.

    //게이지 업데이트

    [_gauge updateBar:_energy];



그리고 적이 초기화 되었을 때 에너지 게이지를 초기화 하기 위해서  -(void)reset 메소드 가장 아래 부분에 다음 코드를 추가한다 

    //죽어서 안보이던 적을 다시 보여준다.

    [self setVisible:YES];

    //상태값을 일반 상태로 변경 한다.

    _state = kNormal;



    //게이지 바를 초기화 한다.

    [_gauge updateBar:_energy];



Cmd(⌘) + R 해서 Run을 해보자. 총알과 적이 충돌을 하면 에너지가 줄어드는 모습을 볼 수 있고, 이리저리 움직이다가 적과 충돌을 하게 되면 게임이 종료되는 것을 확인 할 수 있다. 

기본적 게임 기능은 모두 추가 되었다. 다음 파트에서는 점수를 나타내는 HUD 추가와 폭파할때 일어나는 에니메이션 등을 구현 하도록 하겠다. 


시뮬레이터 상에서 스프라이트 이미지가 많이지면서 프레임 레이트가 20 밑으로 뚝뚝 떨어지는 것을 볼 수 있다. 실제 기기 상에서는 문제 없이 나온다. 시뮬레이터와 단말기에서 사용하는 OpenGL 이 달라서 그렇다. 

저작자 표시 비영리
신고
Posted by KraZYeom