(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
//
// 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