본문 바로가기

프로그래밍/cocos2d

주인공이 공격받았을 때의 효과 (클래스를 이용한 CCSprite 동적 생성)

(1) 적이 공격하는 애니메이션 구현
(2) CCTintBy 액션 클래스로 공격당한 주인공 스프라이트 색을 빨간색으로 변하게 만듬
(3) schedule을 이용하여 0.5초마다 새로운 적들을 생성
 

작업할 파일은 다음과 같습니다.
(1) GameLayer.h
(2) GameLayer.m
(3) EnemySprite.h
(4) EnemySprite.m




//

//  GameLayer.h

//  GameDemo

//

//  Created by cmpak on 5/10/10.

//  Copyright 2010 thefirstgood.com. All rights reserved.

//


#import "cocos2d.h"


// 적이 쓰러질 방향

typedef enum {

    kFallRight,

    kFallLeft

//}EnemyFallDirection;

}FallDirection;


@interface GameLayer : CCLayer {

    CGSize winSize;

    

    // 방향 전환에 쓰일 버튼

    // 눌리기 전과 눌렸을 때에 있도록 방향별로 두개씩 만든다.

    CCSprite  *rightSprite;

    CCSprite  *rightPressedSprite;

    CCSprite  *leftSprite;

    CCSprite  *leftPressedSprite;

// 발차기 버튼

    CCSprite  *kickSprite;

    CCSprite  *kickPressedSprite;

    

    BOOL isLeftPressed;

    BOOL isRightPressed;

    

    // 주인공 캐릭터 - 여자 이미지를 사용하지만 prince라고 부르겠습니다.

    CCSprite *princeSprite;

    

    // 주인공 캐릭터의 걷기 애니메이션

    CCAnimate *princeWalkAnimate;

// 주인공 발차기 애니메이션

    CCAnimate *princeKickAnimate;

// 발차기 애니메이션이 진행 중인지 검사하는데 사용합니다.

    BOOL isAnimating;

    

    // 캐릭터

    CCSpriteSheet *enemySpriteSheet;

    

    // 캐릭터 애니메이션

    CCAnimation   *enemyWalkAnimation;

    CCAnimation   *enemyAttackAnimation;

}


@property (nonatomic, retain) CCSprite  *rightSprite;

@property (nonatomic, retain) CCSprite  *rightPressedSprite;

@property (nonatomic, retain) CCSprite  *leftSprite;

@property (nonatomic, retain) CCSprite  *leftPressedSprite;

@property (nonatomic, retain) CCSprite  *kickSprite;

@property (nonatomic, retain) CCSprite  *kickPressedSprite;

@property (nonatomic, retain) CCSprite  *princeSprite;

@property (nonatomic, retain) CCAnimate *princeWalkAnimate;

@property (nonatomic, retain) CCAnimate *princeKickAnimate;


@property (nonatomic, retain) CCSpriteSheet *enemySpriteSheet;

@property (nonatomic, retain) CCAnimation   *enemyWalkAnimation;

@property (nonatomic, retain) CCAnimation   *enemyAttackAnimation;


- (void) createBackgroundParallax;

- (void) createArrowButtons;

- (void) createPrinceAndAnimation;

- (void) createEnemyAndAnimation;


- (void) moveBackground;


- (void) startPrinceWalking;

- (void) stopPrinceWalking;


- (void) princeAttacked:(FallDirection)fallDirection;


- (void) handleKickHit:(CGPoint)effectPoint enemySprite:(CCSprite*)enemy directionToFall:(FallDirection)fallDirection;


@end


(1) 적 캐릭터의 공격 애니메이션에 사용할 CCAnimation *enemyAttackAnimation 변수를 정의하고 주인공이 공격 당했을 때의 효과를 구현하기 위한 princeAttacked 메서드를 정의 합니다.




//

//  GameLayer.m

//  GameDemo

//

//  Created by cmpak on 5/10/10.

//  Copyright 2010 thefirstgood.com. All rights reserved.

//


#import "GameLayer.h"

#import "EnemySprite.h"


#define IMG_WIDTH 1600


enum {

    kTag_Parallax,

    kTag_Prince,

    kTag_Enemy,

    kTag_Effect,

    kTag_ControlButtonPressed,

    kTag_ControlButton

};



@implementation GameLayer


@synthesize rightSprite, rightPressedSprite, leftSprite, leftPressedSprite;

@synthesize kickSprite, kickPressedSprite;

@synthesize princeSprite, princeWalkAnimate, princeKickAnimate;

@synthesize enemySpriteSheet, enemyWalkAnimation, enemyAttackAnimation;


- (id) init {

    if( (self=[super init]) ) {

        // CCLayer 터치이벤트를 처리할 있도록 활성화시킵니다.

        self.isTouchEnabled = YES;

        

        // 화면의 픽셀 크기를 구합니다.

        winSize = [[CCDirector sharedDirector] winSize];

        

        [self createBackgroundParallax];

        [self createPrinceAndAnimation];

        [self createEnemyAndAnimation];

        [self createArrowButtons];

    }

    

    return self;

}


- (void) onEnter {

    [super onEnter];

    

    // random 함수의 수가 행할 때마다 새로운 값이 되도록 합니다.

    srandom(time(NULL));

    

    // 0.5 간격으로 캐릭터를 만들어냅니다.

    [self schedule:@selector(addNewEnemy) interval:0.5f];

}


- (void) onExit {

    [super onExit];

    

    [self unschedule:@selector(addNewEnemy)];

    

    // sprite sheet texture 캐시를 모두 지웁니다.   

    // 이상 사용하지않는 캐시를 반드시 지워주세요.

    [[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];

}


- (void) createPrinceAndAnimation {

    // 위치정보 파일을 읽어들여 바로 CCSpriteFrame 만들어 캐시에 저장합니다.

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

    

    // 프레임을 담을 Array 만듭니다.

    NSMutableArray *aniFrames = [NSMutableArray array];

    

    // 아주 간단히 프레임을 만들어 배열에 저장할 있습니다. 스프라이트 시트를 만들 사용된

    // 개별 이미지의 파일 이름을 사용하여 개별 프레임을 읽어들입니다.

    for(NSInteger idx = 1; idx <= 15; idx++) {

        CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache

                                spriteFrameByName:[NSString stringWithFormat:@"prince_walk_%02d.png", idx]];

        [aniFrames addObject:frame];

    }

    

    // 프레임으로 CCAnimation 만듭니다 프레임당 시간을 0.05초로 정해줍니다.

    CCAnimation *animation = [CCAnimation animationWithName:@"prince_walk" delay:0.05f frames:aniFrames];

    

    // CCAnimation action CCAnimate 만듭니다.

    CCAnimate *animate = [[CCAnimate alloc] initWithAnimation:animation restoreOriginalFrame:NO];

    self.princeWalkAnimate = animate;

    [animate release];

    

    // 첫번째 프레임을 만들 사용했던 이미지 파일 이름을 사용하여 주인공 sprite 만듭니다.

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

    self.princeSprite = sprite;

    [self addChild:self.princeSprite z:kTag_Prince tag:kTag_Prince];

    

    // 위치계산이 편하도록 주인공의 anchorPoint 가운데 아래로 잡습니다.

    self.princeSprite.anchorPoint = ccp(0.5, 0);

    

    // 주인공을 화면 가운데 아래에서 13픽셀 위에 위치시킵니다.

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

    

    [sprite release];

// 발차기 애니메이션에 쓰일 스프라이트 시트의 위치정보 파일을 읽어들인다.

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

[aniFrames removeAllObjects];

for(NSInteger idx = 1; idx <= 15; idx++) {

        CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache

                                spriteFrameByName:[NSString stringWithFormat:@"prince_kick_%02d.png", idx]];

        [aniFrames addObject:frame];

    }

    

    // 프레임으로 CCAnimation 만듭니다 프레임당 시간을 0.04초로 정해줍니다.

    animation = [CCAnimation animationWithName:@"prince_kick" delay:0.04f frames:aniFrames];

    

    // CCAnimation action CCAnimate 만듭니다.

    animate = [[CCAnimate alloc] initWithAnimation:animation restoreOriginalFrame:NO];

    self.princeKickAnimate = animate;

    [animate release];

}


- (void) createEnemyAndAnimation {

    // 위치정보 파일을 읽어들여 바로 CCSpriteFrame 만들어 캐시에 저장합니다.

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

    

    // 위치정보 파일과 같은 이름을 가진 스프라이트 시트로 CCSpriteSheet 객체를 만듭니다.

    self.enemySpriteSheet = [CCSpriteSheet spriteSheetWithFile:@"enemy.png"];

    [self addChild:self.enemySpriteSheet z:kTag_Enemy];

    

    // 프레임을 담을 Array 만듭니다.

    NSMutableArray *aniFrames = [NSMutableArray array];

    

    // 아주 간단히 프레임을 만들어 배열에 저장할 있습니다. 스프라이트 시트를 만들 사용된

    // 개별 이미지의 파일 이름을 사용하여 개별 프레임을 읽어들입니다.

    for(NSInteger idx = 1; idx <= 15; idx++) {

        CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache

                                spriteFrameByName:[NSString stringWithFormat:@"zombie_walk_%04d.png", idx]];

        [aniFrames addObject:frame];

    }

    

    // 프레임으로 CCAnimation 만듭니다 프레임당 시간을 0.15초로 정해줍니다.

    CCAnimation *walkAnimation = [[CCAnimation alloc] initWithName:@"enemy_walk" delay:0.15f frames:aniFrames];

    self.enemyWalkAnimation = walkAnimation;

    [walkAnimation release];

    

    // 공격에 해당하는 이미지로 프레임을 만듭니다.

    NSMutableArray *attackFrames = [NSMutableArray array];

    for(NSInteger idx = 1; idx <= 15; idx++) {

        CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache

                                spriteFrameByName:[NSString stringWithFormat:@"zombie_attack_%04d.png", idx]];

        [attackFrames addObject:frame];

    }

    

    // 프레임으로 CCAnimation 만듭니다 프레임당 시간을 0.15초로 정해줍니다.

    CCAnimation *attackAnimation = [[CCAnimation alloc] initWithName:@"enemy_attack" delay:0.07f frames:attackFrames];

    self.enemyAttackAnimation = attackAnimation;

    [attackAnimation release];

    

    // TEMP

    // 움직이는 속도와 위치를 달리하여 캐릭터 세개를 만든다.

    // 캐릭터 1

    /*EnemySprite *enemy = [[EnemySprite alloc] initWithSpriteFrameName:@"zombie_walk_0001.png" 

                                             position:CGPointMake(winSize.width / 2 + 100, 13) 

                                                speed:0.5f 

                                            gameLayer:self];                        

    [self.enemySpriteSheet addChild:(CCSprite*)enemy];

    [enemy release];

    

    // 캐릭터 2

    enemy = [[EnemySprite alloc] initWithSpriteFrameName:@"zombie_walk_0001.png" 

                                            position:CGPointMake(winSize.width / 2 - 150, 13) 

                                               speed:0.3f 

                                           gameLayer:self];

    [self.enemySpriteSheet addChild:(CCSprite*)enemy];

    [enemy release];

    

    // 캐릭터 3 - 화면 바깥에 위치시킨다.

    enemy = [[EnemySprite alloc] initWithSpriteFrameName:@"zombie_walk_0001.png" 

                                            position:CGPointMake(-100, 13) 

                                               speed:0.7f

                                           gameLayer:self];

    [self.enemySpriteSheet addChild:(CCSprite*)enemy];

    [enemy release];

    */

}


- (void) createArrowButtons {

    // 왼쪽 화살표

    CCSprite *sprite = [[CCSprite alloc] initWithFile:@"arrow_left.png"];

    self.leftSprite = sprite;

    

    // 기본 anchorPoint 가운데, (0.5, 0.5)이므로 이미지의 가로 세로 크기의 반에 5픽셀의 

    // 여유를 두고 화면 아래에 표시합니다.

    self.leftSprite.position = ccp(10 + self.leftSprite.contentSize.width / 2

                                   self.leftSprite.contentSize.height / 2 + 5);

    

    [self addChild:self.leftSprite z:kTag_ControlButton];

    [sprite release];

    

    // 눌렸을 쓰일 왼쪽 화살표 

    sprite = [[CCSprite alloc] initWithFile:@"arrow_left_s.png"];

    self.leftPressedSprite = sprite;

    

    // self.leftSprite 똑같은 위치에 표시합니다.

    self.leftPressedSprite.position = self.leftSprite.position;

    

    // 눌렸을 때의 화살표를 하위 z-order 넣습니다. 그럼, 위에 같은 크기의 화살표가 

    // 똑같은 위치에 있으니까 가려서 화면상에 보이지 않게됩니다.

    [self addChild:self.leftPressedSprite z:kTag_ControlButtonPressed];

    [sprite release];

    

    // 오른쪽 화살표

    sprite = [[CCSprite alloc] initWithFile:@"arrow_right.png"];

    self.rightSprite = sprite;

    

    // 왼쪽 화살표에서 15픽셀 오르쪽에 위치시킵니다.

    self.rightSprite.position = ccp(self.leftSprite.position.x + self.rightSprite.contentSize.width + 15

                                    self.leftSprite.position.y);

    [self addChild:self.rightSprite z:kTag_ControlButton];

    [sprite release];

    

    // 눌렸을 쓰일 오른쪽 화살표 

    sprite = [[CCSprite alloc] initWithFile:@"arrow_right_s.png"];

    self.rightPressedSprite = sprite;

    

    // self.rightSprite 똑같은 위치에 표시합니다.

    self.rightPressedSprite.position = self.rightSprite.position;

    

    // 눌렸을 때의 화살표를 하위 z-order 넣습니다. 그럼, 위에 같은 크기의 화살표가 

    // 똑같은 위치에 있으니까 가려서 화면상에 보이지 않게됩니다.

    [self addChild:self.rightPressedSprite z:kTag_ControlButtonPressed];

    [sprite release];

// 발차기 버튼

    sprite = [[CCSprite alloc] initWithFile:@"kick.png"];

    self.kickSprite = sprite;

    self.kickSprite.position = ccp(winSize.width - self.kickSprite.contentSize.width / 2 - 5, self.leftSprite.position.y);

    [self addChild:self.kickSprite z:kTag_ControlButton];

    [sprite release];

    // 발차기 버튼 눌렸을

    sprite = [[CCSprite alloc] initWithFile:@"kick_s.png"];

    self.kickPressedSprite = sprite;

    self.kickPressedSprite.position = self.kickSprite.position;

    [self addChild:self.kickPressedSprite z:kTag_ControlButtonPressed];

    [sprite release];

}


- (void) createBackgroundParallax {

    // 이미지로 백그라운드에 쓰일 CCSprite 만듭니다.

    CCSprite *bgSprite1 = [CCSprite spriteWithFile:@"background1.png"];

    CCSprite *bgSprite2 = [CCSprite spriteWithFile:@"background2.png"];

    

    // Transform 사용되는 anchorPoint 왼쪽 아래 귀퉁이 (0, 0) 잡습니다

    bgSprite1.anchorPoint = ccp(0, 0);

    bgSprite2.anchorPoint = ccp(0, 0);

    

    // 위에서 만든 sprite 담을 parent CCParallaxNode 만듭니다.

    CCParallaxNode *voidNode = [CCParallaxNode node];

    

    // 배경 sprite Parallax 넣습니다.

    // parallaxRatio 가로/세로로 움직이는 속도라고 보시면 되겠습니다

    // 우리는 가로로만 움직이므로 y 값을 0으로 줍니다.

    

    // background1.png 파일의 세로 크기가 160 픽셀이기 때문에 positionOffset 이용하여 

    // 화면 위에 위치하도록 좌표를 조정합니다.

    // 뒤쪽에 깔릴 배경인 background1.png 천천히 움직이도록 parallaxRatio x 값을 1보다 작은

    // 0.4 설정합니다.

    [voidNode addChild:bgSprite1 z:0 parallaxRatio:ccp(0.4f, 0) positionOffset:ccp(0, winSize.height / 2)];    

    [voidNode addChild:bgSprite2 z:1 parallaxRatio:ccp(1.0f, 0) positionOffset:CGPointZero];


    [self addChild:voidNode z:kTag_Parallax tag:kTag_Parallax];

}



#pragma mark -

#pragma mark Game Play


- (void) addNewEnemy {

    // 만약 현재 캐릭터의 수가 4개이면 이상 만들지않습니다.

    if([[self.enemySpriteSheet children] count] >= 4)

        return;

    

    // 움직이는 속도와 위치를 달리하여 캐릭터를 만듭니다.

    CGFloat xPos = winSize.width + 100.0;

    if(random() % 100 < 50)

        xPos = -100.0;

    

    CGFloat speed = 0.30 + ((random() % 70) / 100.0);

    

    EnemySprite *enemy = [[EnemySprite alloc] initWithSpriteFrameName:@"zombie_walk_0001.png" 

                                                    position:CGPointMake(xPos, 13

                                                       speed:speed

                                                   gameLayer:self ];

    [self.enemySpriteSheet addChild:(CCSprite*)enemy];

    [enemy release];

}


- (void) startMovingBackground {

    // 만약 버튼 두개가 눌려졌으면 화면을 이동시키지 않습니다.

    if(isLeftPressed == YES && isRightPressed == YES)

        return;

    

    NSLog(@"start moving");

    [self schedule:@selector(moveBackground)];

    

    // 걷기 애니메이션을 시작합니다.

    [self startPrinceWalking];

}


- (void) stopMovingBackground {

    NSLog(@"stop moving");

    [self unschedule:@selector(moveBackground)];

    

    // 걷기 애니메이션을 멈춥니다.

    [self stopPrinceWalking];

}



- (void) moveBackground {

    // GameLayer 들어있는 parallax node 받습니다.

    CCNode *voidNode = [self getChildByTag:kTag_Parallax];

    

    // 프레임마다 움직일 거리

    CGPoint moveStep = ccp(3, 0);

    

    // 오른쪽 버튼이 눌려졌을 때는 반대로 움직임

    if(isRightPressed)

        moveStep.x = -moveStep.x;

    

    CGFloat bgParallaxRatio = 1.0f;

    

    CGPoint newPos = ccp(voidNode.position.x + moveStep.x, voidNode.position.y);

    

    // 배경이 양쪽 끝에 도달하면 이상 움직이지 않음

    if(isLeftPressed == YES && newPos.x > 0)

        newPos.x = 0;

    else if(isRightPressed == YES && newPos.x < -(IMG_WIDTH - winSize.width) / bgParallaxRatio)

        newPos.x = -(IMG_WIDTH - winSize.width) / bgParallaxRatio;

    

    // 주인공이 화면 가운데 있을 경우에만 배경을 움직입니다.

    CGFloat halfWinWidth = winSize.width / 2;

    if(self.princeSprite.position.x == halfWinWidth)

        voidNode.position = newPos;

    

    

    // 주인공의 방향을 정합니다.

    // flipX 이용하여 하나의 이미지로 방향을 표현할 있습니다.

    if(isRightPressed == YES)

        self.princeSprite.flipX = NO;

    else

        self.princeSprite.flipX = YES;

    

    // 만약 주인공이 화면 가운데 있지않을 경우에는 주인공을 화면가운데까지

    // 이동하게 합니다.

    

    if(isRightPressed == YES && self.princeSprite.position.x < halfWinWidth) {

        // moveStep.x 부호를 바꾼 이유는 배경과 주인공의 움직임 방향이 서로 반대이기 때문입니다.

        self.princeSprite.position = ccp(self.princeSprite.position.x + (moveStep.x * -1),

                                         self.princeSprite.position.y);

        // 가운데 이상 움직이지 않도록 체크합니다.

        if(self.princeSprite.position.x > halfWinWidth)

            self.princeSprite.position = ccp(halfWinWidth, self.princeSprite.position.y);

    }else if(isLeftPressed == YES && self.princeSprite.position.x > halfWinWidth) {

        // moveStep.x 부호를 바꾼 이유는 배경과 주인공의 움직임 방향이 서로 반대이기 때문입니다.

        self.princeSprite.position = ccp(self.princeSprite.position.x + (moveStep.x * -1),

                                         self.princeSprite.position.y);

        

        // 가운데 이상 움직이지 않도록 체크합니다.

        if(self.princeSprite.position.x < halfWinWidth)

            self.princeSprite.position = ccp(halfWinWidth, self.princeSprite.position.y);

    }

    

    // 배경의 끝에 도달하면 배경은 움직이지 않고 주인공을 화면 끝까지 이동시킵니다.

    if(newPos.x == 0 || newPos.x == -(IMG_WIDTH - winSize.width)) {

        CGPoint newPrincePos = ccp(self.princeSprite.position.x + (moveStep.x * -1), self.princeSprite.position.y);

        

        // 주인공이 화면의 왼쪽 또는 오른쪽 끝까지 도달했을 때는 이상 움직이지 않습니다.

        CGFloat halfWidth = self.princeSprite.contentSize.width / 2;

        if(newPrincePos.x <= halfWidth)

            newPrincePos.x = halfWidth;

        else if(newPrincePos.x >= winSize.width - halfWidth)

            newPrincePos.x = winSize.width - halfWidth;

        

        self.princeSprite.position = newPrincePos;

    }else {

        // 주인공의 움직임에 맞추어 적들의 위치를 조정합니다.

        NSArray *enemies = [self.enemySpriteSheet children];

        for(EnemySprite *enemy in enemies) {

            [enemy adjustPosition];

        }

    }

}


- (void) startPrinceWalking {

    NSLog(@"주인공 걷기 시작");

    [self.princeSprite runAction:[CCRepeatForever actionWithAction:self.princeWalkAnimate]];

}


- (void) stopPrinceWalking {

    NSLog(@"주인공 걷기 ");

    [self.princeSprite stopAllActions];

}


- (void) kickAnimateCompleteHandler {

    // 버튼을 다시 보이도록 합니다.

    self.kickSprite.visible = YES;

    [self.princeSprite stopAllActions];

isAnimating = NO;

    

    // 주인공 스프라이트를 원래 색으로 돌립니다.

    [self.princeSprite setColor:ccc3(255,255,255)];

}


- (void) handleKickHit:(CGPoint)effectPoint enemySprite:(CCSprite*)enemy directionToFall:(FallDirection)fallDirection {

    // 발차기 공격 이펙트

    CCSprite *hitEffectSprite = [CCSprite spriteWithFile:@"hit_effect.png"];

    hitEffectSprite.position = effectPoint;

    [self addChild:hitEffectSprite z:kTag_Effect];

    

    // 발차기 이펙트 애니메이션

    hitEffectSprite.scale = 0.3;

    

    // CCSequence 사용하면 여러개의 action 순서대로 있습니다.

    [hitEffectSprite runAction:[CCSequence actions:

                                // 주인공 발차기 애니메이션이 이루어지는 시간동안 잠시 기다린다.

                                [CCDelayTime actionWithDuration:0.05],

                                

                                [CCScaleTo actionWithDuration:0.4 scale:1.0],

                                

                                // scale 액션이 끝나면 hitEffectCompleteHandler 메소드를 호출합니다.

                                [CCCallFuncN actionWithTarget:self selector:@selector(hitEffectCompleteHandler:)],

                                nil]];

    

    // 캐릭터 넘어지는 애니메이션

    CGFloat xDiff = 120;

    CGFloat angle = 90;

    if(fallDirection == kFallLeft) {

        xDiff = -120;

        angle = -90;

    }

    

    CGPoint targetPoint = CGPointMake(effectPoint.x + xDiff, enemy.position.y);

    

    // CCSpawn 사용하면 여러 종류의 action 동시에 있습니다.

    id spawn = [CCSpawn actions:[CCJumpTo actionWithDuration:0.7 position:targetPoint height:80 jumps:1],

                [CCRotateBy actionWithDuration:0.5 angle:angle],

                nil];

    [enemy runAction:[CCSequence actions:

                      [CCEaseIn actionWithAction:spawn rate:2.0f],

                      [CCCallFuncND actionWithTarget:self selector:@selector(enemyFallCompleteHandler: enemySprite:) data:(void*)enemy],

                      nil]];

}


- (void) hitEffectCompleteHandler:(CCNode*)node {

    if(node != nil)

        [self removeChild:node cleanup:YES];

}


- (void) enemyFallCompleteHandler:(CCNode*)node enemySprite:(CCSprite*)enemy {

    [self.enemySpriteSheet stopAllActions];

    

    if(enemy != nil)

        [self.enemySpriteSheet removeChild:enemy cleanup:YES];

}


- (void) princeAttacked:(FallDirection)fallDirection {

    // 좌우로 60픽셀만큼 밀려나게 합니다.

    CGFloat xDiff = 60.0;

    if(fallDirection == kFallLeft)

        xDiff *= -1.0;

    

    // x좌표의 값이 화면 바깥으로 나가지않도록 합니다.

    CGFloat xPos = self.princeSprite.position.x + xDiff;

    if(xPos < 0)

        xPos = 0;

    else if(xPos > winSize.width)

        xPos = winSize.width;

    

    CGPoint targetPoint = CGPointMake(xPos, self.princeSprite.position.y);

    

    CGFloat duration = 0.5;

    

    // 빨간색으로 변화시킵니다.

    id tint = [CCTintBy actionWithDuration:duration/2.0 red:0 green:-255 blue:-255];

    

    // CCEaseExponentialOut 사용하여 속도감이 있도록 합니다.

    id move = [CCEaseExponentialOut actionWithAction:[CCMoveTo actionWithDuration:duration position:targetPoint]];

    

    id spawn = [CCSpawn actions:tint, move, nil];

    

    [self.princeSprite runAction:[CCSequence actions:

                                  spawn,

                                  [CCCallFunc actionWithTarget:self selector:@selector(princeAttackedCompleteHandler)],

                                  nil]];

}


- (void) princeAttackedCompleteHandler {

    // Tint 바뀐색을 원래색으로 돌립니다.

    [self.princeSprite setColor:ccc3(255,255,255)];

    

    // 라이프를 감소시킨다.

}


#pragma mark -

#pragma mark Touch Event Handling


// 터치가 버튼 Sprite안에서 이루어졌는지 확인합니다.

- (BOOL) isTouchInside:(CCSprite*)sprite withTouch:(UITouch*)touch {

    // Cocoa 좌표 

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

    

    // Cocoa 좌표를 cocos2d 좌표로 변환합니다

CGPoint convertedLocation = [[CCDirector sharedDirector] convertToGL:location];

    

    CGFloat halfWidth = sprite.contentSize.height / 2.0;

    CGFloat halfHeight = sprite.contentSize.height / 2.0;

    

    if(convertedLocation.x > (sprite.position.x + halfWidth) ||

       convertedLocation.x < (sprite.position.x - halfWidth) ||

       convertedLocation.y < (sprite.position.y - halfHeight) ||

       convertedLocation.y > (sprite.position.y + halfHeight) ) {

        return NO;

    }

    

    return YES;

}


// 손가락이 닫는 순간 호출됩니다.

- (void) ccTouchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {

// 발차기를 하고 있는 동안에는 움직일 없도록 합니다

// 또한 발차기 애니메이션이 진행되는 동안에는 다시 발차기를 없습니다.

if(isAnimating == YES) {

        NSLog(@"Touch Began ignored");

        return;

    }

    UITouch *touch = [touches anyObject];

    

// 발차기 버튼이 터치되었는지 검사

    if([self isTouchInside:self.kickSprite withTouch:touch] == YES) {

        self.kickSprite.visible = NO;

        // 발차기 공격의 유효 타격거리 안에 들어와 있는 적이 있는지 확인합니다.

        // 주인공 sprite 중심에서 40 픽셀, sprite 중심에서 30픽셀 되는 거리를 발차기 유효거리로 정합니다.

        // 위에서 사용된 수치는 현재 사용중인 이미지의 모양과 크기에 따라서 정한 숫자이므로

        // 자신이 사용할 이미지에 맞게 조정하시면 됩니다

        NSArray *enemies = [self.enemySpriteSheet children];

        for(CCSprite *enemy in enemies) {

            if(self.princeSprite.flipX == NO  &&

               self.princeSprite.position.x + 40 >= enemy.position.x - 30 &&

                 self.princeSprite.position.x + 40 <= enemy.position.x + 30)

            {

                [self handleKickHit:CGPointMake(self.princeSprite.position.x + 55, self.princeSprite.position.y + 113)

                         enemySprite:enemy directionToFall:kFallRight];

            }else if(self.princeSprite.flipX == YES &&

                self.princeSprite.position.x - 40 <= enemy.position.x + 30 &&

                self.princeSprite.position.x - 40 >= enemy.position.x - 30)

            {

                [self handleKickHit:CGPointMake(self.princeSprite.position.x - 55, self.princeSprite.position.y + 113)

                         enemySprite:enemy directionToFall:kFallLeft];

            }

        }

        

isAnimating = YES// 발차기 애니메이션이 진행

        [self.princeSprite runAction:[CCSequence actions:

  self.princeKickAnimate,

  [CCCallFunc actionWithTarget:self selector:@selector(kickAnimateCompleteHandler)],

  nil]];

    }

    // 아래 Boolean 변수 대신에 leftSprite rightSprite visible 값을 직접 사용해도 무방합니다.

    isLeftPressed = NO;

    isRightPressed = NO;

    

    // 터치가 왼쪽 또는 오른쪽 화살표 안에 들어왔는지 확인합니다.

    if([self isTouchInside:self.leftSprite withTouch:touch] == YES) {

        // 왼쪽 화살표를 안보이게 합니다. 그럼 아래에 있던 눌릴 보여지는 이미지가 나타날 것입니다.

        self.leftSprite.visible = NO;

        

        isLeftPressed = YES;

    }else if([self isTouchInside:self.rightSprite withTouch:touch] == YES) {

        // 오른쪽 화살표를 안보이게 합니다.

        self.rightSprite.visible = NO;

        

        isRightPressed = YES;

    }

    

    // 버튼이 눌려졌으면 화면을 움직입니다.

    if(isLeftPressed == YES || isRightPressed == YES)

        [self startMovingBackground];

}


// 손가락을 떼는 순간 호출됩니다.

- (void)ccTouchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {

    // 배경화면을 멈춥니다.

    if(isLeftPressed == YES || isRightPressed == YES)

        [self stopMovingBackground];

    

    // 감춰졌던 버튼이미지를 다시 보이게 합니다.

    if(isLeftPressed == YES)

        self.leftSprite.visible = YES;

    

    if(isRightPressed == YES

        self.rightSprite.visible = YES;

}


// 손가락을 움직일 계속해서 호출됩니다.

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];

    

    // 손가락이 버튼을 벗어나면 움직임을 중단합니다.

    if(isLeftPressed == YES && [self isTouchInside:self.leftSprite withTouch:touch] == NO) {

        self.leftSprite.visible = YES;

        [self stopMovingBackground];

    }else if(isRightPressed == YES && [self isTouchInside:self.rightSprite withTouch:touch] == NO) {

        self.rightSprite.visible = YES;

        [self stopMovingBackground];

    }

}



//- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}


#pragma mark -

#pragma mark Memory Release


- (void) dealloc {

    [rightSprite release];

    [rightPressedSprite release];

    [leftSprite release];

    [leftPressedSprite release];

[kickSprite release];

    [kickPressedSprite release];

    

    [princeSprite release];

    [princeWalkAnimate release];

[princeKickAnimate release];

    

    [enemySpriteSheet release];

    [enemyWalkAnimation release];

    [enemyAttackAnimation release];

    

    [super dealloc];

}


@end







//

//  EnemySprite.h

//  GameDemo

//

//  Created by Chang-Min Pak on 6/2/10.

//  Copyright 2010 thefirstgood.com. All rights reserved.

//


#import "cocos2d.h"



@class GameLayer;


@interface EnemySprite : CCSprite {

    GameLayer *gameLayer;

    CCAnimate *walkAnimate;

    CCAnimate *attackAnimate;

    CGFloat   walkingSpeed

}


@property (nonatomic, retain) CCAnimate *walkAnimate;

@property (nonatomic, retain) CCAnimate *attackAnimate;


- (id) initWithSpriteFrameName:(NSString*)frameName position:(CGPoint)pos speed:(CGFloat)speed gameLayer:(GameLayer*)layer;


- (void) adjustPosition;

- (void) startWalking;


@end








//

//  EnemySprite.m

//  GameDemo

//

//  Created by Chang-Min Pak on 6/2/10.

//  Copyright 2010 thefirstgood.com. All rights reserved.

//


#import "EnemySprite.h"

#import "GameLayer.h"


@implementation EnemySprite


@synthesize walkAnimate, attackAnimate;


// GameLayer 있는 오브젝트들을 사용하여야 하므로, init 메소드에 gameLayer 넘겨줍니다.

- (id) initWithSpriteFrameName:(NSString *)frameName position:(CGPoint)pos 

                         speed:(CGFloat)speed gameLayer:(GameLayer*)layer

                  

{

    if( (self=[super initWithSpriteFrameName:frameName]) ) {

        gameLayer = layer;

        

        walkingSpeed = speed;

        

        // GameLayer에서 만들어 놓은 CCAnimation 이용하여 CCAnimate 객체를 만듭니다.

        CCAnimate *animate = [[CCAnimate alloc] initWithAnimation:gameLayer.enemyWalkAnimation restoreOriginalFrame:NO];

        self.walkAnimate = animate;

        [animate release];

        

        animate = [[CCAnimate alloc] initWithAnimation:gameLayer.enemyAttackAnimation restoreOriginalFrame:NO];

        self.attackAnimate = animate;

        [animate release];

        

        self.anchorPoint = CGPointMake(0.5, 0);

        self.position = pos;

    }

    

    return self;

}


// EnemySprite객체가 만들어지면, 바로 걷는 애니메이션을 시작합니다.

- (void) onEnter {

    [super onEnter];

    

    [self startWalking];

}


- (void) startWalking {

    [self schedule:@selector(walking)];

    

    [self runAction:[CCRepeatForever actionWithAction:self.walkAnimate]];

}


- (void) stopWalking {

    [self unschedule:@selector(walking)];

    

    [self stopAllActions];

}



- (void) walking {

    // 주인공에 가까이 왔으면(40픽셀 이내) 움직임을 멈춥니다.

    if(abs(self.position.x - gameLayer.princeSprite.position.x) <= 40) {

        // 걷는 동작을 멈춥니다.

        [self stopWalking];

        

        // 0.5에서 1.4 사이의 랜덤시간만큼 기다렸다가 공격을 합니다.

        // (레벨이 올라갈수록 지체하는 시간을 줄이면 그만큼 강한 적들이 되겠죠?)

        CGFloat delayTime = ((random() % 10) + 5) / 10.0f;

        NSLog(@" 공격 %1.2f ...", delayTime);

        [self runAction:[CCSequence actions:

                         // 위에서 계산된 랜덤시간만큼 기다립니다.

                         [CCDelayTime actionWithDuration:delayTime],

                         // 주인공을 공격 합니다.

                         [CCCallFunc actionWithTarget:self selector:@selector(attackPrince)],

                         nil]];

    }

    

    // 아직 주인공과 충분히 가깝지 않다면 계속 이동시킵니다.

    CGFloat tmpWalkingSpeed = walkingSpeed;

    

    // 주인공이 왼쪽에 있는지 오른쪽에 있는지 알아봅니다.

    if(gameLayer.princeSprite.position.x > self.position.x)

        self.flipX = YES;

    else {

        self.flipX = NO;

        tmpWalkingSpeed *= -1.0;

    }

    

    // 새로운 위치를 업데이트 합니다.

    self.position = CGPointMake(self.position.x + tmpWalkingSpeed, self.position.y);

}



// 메소드가 바로 주인공이 이동할 적들이 뒤쳐지도록 만드는 부분입니다.

// 주인공의 이동방향과 적의 이동방향에 맞추어 x 좌표의 값을 변경합니다.

- (void) adjustPosition {

    if(gameLayer.princeSprite.position.x > self.position.x && gameLayer.princeSprite.flipX == YES)

        self.position = CGPointMake(self.position.x + 1.0, self.position.y);

    else if(gameLayer.princeSprite.position.x < self.position.x && gameLayer.princeSprite.flipX == NO)

        self.position = CGPointMake(self.position.x - 1.0, self.position.y);

    else if(gameLayer.princeSprite.flipX == YES && self.flipX == NO)

        self.position = CGPointMake(self.position.x + 1.0, self.position.y);

    else if(gameLayer.princeSprite.flipX == NO && self.flipX == YES)

        self.position = CGPointMake(self.position.x - 1.0, self.position.y);

}


- (void) attackPrince {

    /*

    // 공격하는 애니매이션을 시작합니다다음 강좌에서 실재 에니메이션을 구현해보도록 하겠습니다.

    // 주인공과의 거리를 다시 계산합니다이렇게 다시 계산하는 이유는 위에서 잠깐 지체하는동안

    // 주인공이 이동을 했을 수도 있기 때문입니다.

    if(abs(gameLayer.princeSprite.position.x - self.position.x) <= 50) {

        NSLog(@" 공격 성공!");

    }else {

        NSLog(@" 공격 실패!");

    }

    

    [self startWalking];

    */

    

    [self runAction:[CCSequence actions:

                     self.attackAnimate,

                     [CCCallFunc actionWithTarget:self selector:@selector(attackCompleteHandler)],

                     nil]];

}


- (void) attackCompleteHandler {

    // 주인공과의 거리를 계산합니다이렇게 다시 계산하는 이유는 위에서 잠깐 지체하는동안

    // 주인공이 이동을 했을 수도 있기 때문입니다.

    if(abs(gameLayer.princeSprite.position.x - self.position.x) <= 60) {

        FallDirection fallDirection = kFallLeft;

        if(self.flipX == YES)

            fallDirection = kFallRight;

        

        [gameLayer princeAttacked:fallDirection];

    }else {

        NSLog(@" 공격 실패!");

    }

    

    [self startWalking];

}


- (void) dealloc {

    [walkAnimate release];

    [attackAnimate release];

    

    [super dealloc];

}


@end