13.14.7添加敌机

游戏当中怎么能少了千军万马的敌人呢?现在,我们来添加一些敌机,大量的敌机将从屏幕上方随机出现,并以随机的速度向下俯冲。这些敌机暂时不会发射×××,之后读者可以自己添加该功能。具体步骤如下。

自定义一个FKSprite类,继承自CCSprite,因为创建的敌机玩家会发射×××去消灭,为了增加游戏的趣味性和难度,需要加入敌机的生命值、血条和爆炸效果等特效。在FKSprite类中定义了相关变量用于存储数据。实现代码如下。

程序清单:codes/13/13.14/AirfightGame/AirfightGame/FKSprite.h

@interface FKSprite : CCSprite {

}

// 精灵的生命值

@property int lifeValue;

// 精灵的名称

@property NSString* name;

// 敌机的血条

@property CCProgressTimer* enemyPlaneHP;

// 血条的更新量

@property float HPInterval;

@end

打开HelloWorldLayer.m文件,添加两个变量,实现代码如下。

// 敌机数组

CCArray* enemyPlaneArray;

// 游戏帧计数器

NSInteger count;

在私有分类中添加两个和敌机相关的方法,实现代码如下。

// 更新敌机

-(void) updateEnemySprite:(ccTime)delta;

// 敌机离开屏幕删除

-(void) removeEnemySprite:(ccTime)delta;

updateEnemySprite:用于控制敌机的创建、俯冲。实现代码如下。

程序清单:codes/13/13.14/AirfightGame/AirfightGame/HelloWorldLayer.m

-(void) updateEnemySprite:(ccTime)delta{

// 控制count100的倍数时添加一架敌机

if (count % 30 == 0)

{

// FKSprite精灵对象继承自CCSprite,增加了生命值

FKSprite* enemyPlaneSprite;

// 根据rand随机数添加不同的敌机

int rand = arc4random() % 2;

// 使用随机数来设置敌机的X坐标

int randX = arc4random() % (screenWidth - 40) + 20;

switch(rand)

{

case 0:

// 创建代表敌机

enemyPlaneSprite = [FKSprite spriteWithSpriteFrameName:@"e0.png"];

enemyPlaneSprite.position = ccp(randX,480+enemyPlaneSprite.contentSize.height);

enemyPlaneSprite.name = @"e0";

enemyPlaneSprite.lifeValue = 1;

break;

case 1:

// 创建代表敌机

enemyPlaneSprite = [FKSprite spriteWithSpriteFrameName:@"e2.png"];

enemyPlaneSprite.position = ccp(randX,480+enemyPlaneSprite.contentSize.height);

enemyPlaneSprite.name = @"e2";

enemyPlaneSprite.lifeValue = 1;

break;

}

// 获取随机时间(敌机俯冲的时间)

float durationTime = arc4random() %2 + 2;

// 敌机俯冲

id moveBy = [CCMoveBy actionWithDuration:durationTime

position:ccp(0,-enemyPlaneSprite.position.y-enemyPlaneSprite.contentSize.height)];

[enemyPlaneSprite runAction:moveBy];

// 将敌机精灵添加到敌机数组中

[enemyPlaneArray addObject:enemyPlaneSprite];

// 获得精灵表单

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

[batchNode addChild:enemyPlaneSprite z:4];

}else{

if (count % 200 == 0)

{

int randX = arc4random() % (screenWidth - 40) + 20;

// FKSprite精灵对象继承自CCSprite,增加了生命值

FKSprite* enemyPlaneSprite;

// 创建代表敌机

enemyPlaneSprite = [FKSprite spriteWithSpriteFrameName:@"e1.png"];

enemyPlaneSprite.position = ccp(randX,480+enemyPlaneSprite.contentSize.height);

enemyPlaneSprite.name = @"e1";

enemyPlaneSprite.lifeValue = 10;

// 获取随机时间(敌机掉落的时间)

float durationTime = arc4random() %2 + 2;

// 敌机俯冲

id moveBy = [CCMoveBy actionWithDuration:durationTime

position:ccp(0,-enemyPlaneSprite.position.y-enemyPlaneSprite.contentSize.height)];

[enemyPlaneSprite runAction:moveBy];

// 将敌机精灵添加到敌机数组中

[enemyPlaneArray addObject:enemyPlaneSprite];

// 获得精灵表单

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

[batchNode addChild:enemyPlaneSprite z:4];

// 创建一个进度条精灵

CCSprite* barSprite = [CCSprite spriteWithFile:@"planeHP.png"];

// 初始化一个CCProgressTimer对象

CCProgressTimer* enemyPlaneHP = [CCProgressTimer progressWithSprite:barSprite];

// setPercentage:0.0f,表示并未加载任何资源,表现在画面上就是什么也看不见

[enemyPlaneHP setPercentage:0.0f];

// 由于图片大小关系,把scale设置成0.15,即缩小一半

enemyPlaneHP.scale = 0.15;

enemyPlaneHP.midpoint = ccp(0,0.5);

enemyPlaneHP.barChangeRate = ccp(1, 0);

enemyPlaneHP.type = kCCProgressTimerTypeBar;

enemyPlaneHP.percentage = 100;

CGPoint pos = enemyPlaneSprite.position;

enemyPlaneHP.position = ccp(pos.x, pos.y+32);

[self addChild:enemyPlaneHP];

id moveBy2 = [CCMoveBy actionWithDuration:durationTime

position:ccp(0,-enemyPlaneSprite.position.y-enemyPlaneSprite.contentSize.height)];

[enemyPlaneHP runAction:moveBy2];

enemyPlaneSprite.enemyPlaneHP = enemyPlaneHP;

enemyPlaneSprite.HPInterval = 100.0 / (float)enemyPlaneSprite.lifeValue;

}

}

}

这段代码有点长,接下来为大家详细解释。

首先,游戏中出现的小敌机有3种,通过count变量来控制敌机出现的频率(count变量在update方法中自增)。

count%30==0时,随机创建两种小飞机,对应e0.pnge2.png图片,设置生命值为1。接下来获取一个随机俯冲时间,根据该时间创建一个moveBy动作,让飞机执行moveBy动作俯冲。同时将敌机精灵添加到敌机数组和精灵表单当中。

count%200==0时,创建一种小飞碟,对应e1.png图片,设置生命值为10。接下来获取一个随机俯冲时间,根据该时间创建一个moveBy动作,让飞机执行moveBy动作俯冲。同时将敌机精灵添加到敌机数组和精灵表单当中。之后使用前面用过的CCProgressTimer类创建了一个血条,游戏中血条会随着敌机被玩家飞机的×××打中而减少,从而实现非常炫的射击效果。

removeEnemySprite的作用是当敌机已经移出屏幕外时删除敌机精灵。实现代码如下。

程序清单:codes/13/13.14/AirfightGame/ AirfightGame/HelloWorldLayer.m

-(void) removeEnemySprite:(ccTime)delta{

// 获得精灵表单

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

// 定义循环变量

CCSprite* enemyPlaneSprite;

// 遍历所有的敌机精灵

CCARRAY_FOREACH(enemyPlaneArray, enemyPlaneSprite){

// 如果敌机已经移出屏幕外,则删除敌机精灵

if (enemyPlaneSprite.position.y <= -enemyPlaneSprite.contentSize.height)

{

// 从精灵表单中删除该敌机精灵

[batchNode removeChild:enemyPlaneSprite cleanup:YES];

// 从敌机数组中删除该敌机精灵

[enemyPlaneArray removeObject:enemyPlaneSprite];

}

}

}

removeEnemySprite:比较简单,首先调用getChildByTag:方法获取精灵表单,然后遍历敌机数组,判断敌机的y轴若超出屏幕范围,则从精灵表单和敌机数组中删除敌机精灵。

找到onEnter方法,在⑤部分代码后初始化敌机数组,实现代码如下(程序清单同上)。

enemyPlaneArray = [[CCArray alloc] init];

修改update方法,实现代码如下(程序清单同上)。

-(void) update:(ccTime)delta{

count++;

[self updateBackground:delta];

[self updateEnemySprite:delta];

[self removeEnemySprite:delta];

}

再次编译并运行游戏,大量的敌机会随机出现,并向屏幕下方俯冲。模拟器显示效果如图13.63所示。

疯狂ios之疯狂打飞机游戏(3)_第1张图片


13.14.8玩家飞机添加×××并射击

现在,大量的敌机向玩家飞机俯冲过来,不用多说,给玩家飞机添加×××射击功能打爆它们。这里我们设计成×××自动发射,并且×××是无限的,这样玩家就只需要专心控制飞机的飞行就可以了。具体步骤如下。

打开HelloWorldLayer.m文件,添加变量,实现代码如下(程序清单同上)。

// 代表×××精灵数组

CCArray* bulletArray;

在私有分类中添加3个和敌机相关的方法,实现代码如下(程序清单同上)。

// 更新×××

-(void) updateShooting:(ccTime)delta;

// ×××离开屏幕,删除

-(void) removeBulletSprite:(ccTime)delta;

// 碰撞检测

-(void) collisionDetection:(ccTime)delta;

updateShooting:方法用于不断发射×××。实现代码如下(程序清单同上)。

-(void) updateShooting:(ccTime)delta{

// 获得精灵表单

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

// 飞机精灵坐标

CGPoint pos = planeSprite.position;

// 控制count8的倍数时发射一颗×××

if(count % 8 == 0)

{

// 创建代表×××的精灵

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

// 设置×××坐标

CGPoint bulletPos = ccp(pos.x, pos.y +

planeSprite.contentSize.height/2 + bulletSprite.contentSize.height);

bulletSprite.position = bulletPos;

// ×××移动时间为0.4秒,移动距离为屏幕高度-×××的y

id moveBy = [CCMoveBy actionWithDuration:0.4f position:ccp(0, screenHeight-bulletPos.y)];

[bulletSprite runAction:moveBy];

// 将×××精灵添加到精灵表单中

[batchNode addChild:bulletSprite z:4];

// 将×××精灵添加到×××精灵数组中

[bulletArray addObject:bulletSprite];

}

}

updateShooting:方法并不复杂,首先获取精灵表单,然后获取玩家飞机的坐标位置,当count%8==0时创建一颗×××精灵,并执行moveBy动作达到发射×××的效果,最后将×××精灵添加到精灵表单和×××精灵数组当中。

removeBulletSprite:方法的作用是当×××精灵已经移出屏幕外时删除×××精灵。实现代码如下(程序清单同上)。

-(void) removeBulletSprite:(ccTime)delta{

// 获得精灵表单

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

CCSprite* bulletSprite;

// 遍历所有的×××

CCARRAY_FOREACH(bulletArray, bulletSprite){

// 如果×××已经移出屏幕外,则删除×××

if (bulletSprite.position.y >=screenHeight){

// 从精灵表单中删除该×××精灵

[batchNode removeChild:bulletSprite cleanup:YES];

// 从×××数组中删除该×××精灵

[bulletArray removeObject:bulletSprite];

}

}

}

removeBulletSprite:方法也比较简单,首先调用getChildByTag:方法获取精灵表单,然后遍历×××数组,判断×××的y轴若超出屏幕范围,则从×××表单和×××数组中删除×××精灵。

collisionDetection:是检查玩家飞机和敌机碰撞或者×××和敌机碰撞的方法。实现代码如下(程序清单同上)。

-(void) collisionDetection:(ccTime)dt{

// 获得精灵表单

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

// 定义循环变量

FKSprite* enemyPlaneSprite;

CCSprite* bulletSprite;

// 遍历敌机数组

CCARRAY_FOREACH(enemyPlaneArray, enemyPlaneSprite){

// 玩家飞机和敌机发生碰撞

if(CGRectIntersectsRect(planeSprite.boundingBox,enemyPlaneSprite.boundingBox)){

// 播放爆炸动画

[self bombAnimate:@"blast" :enemyPlaneSprite.position ];

// 删除敌机精灵

[enemyPlaneArray removeObject:enemyPlaneSprite];

[batchNode removeChild:enemyPlaneSprite cleanup:YES];

[planeSprite stopAllActions];

// 删除玩家精灵

[batchNode removeChild:planeSprite cleanup:YES];

[self gameOver:@"游戏结束!"];

}

// 遍历×××数组

CCARRAY_FOREACH(bulletArray, bulletSprite){

// 如果敌机与×××发生碰撞

if(CGRectIntersectsRect(enemyPlaneSprite.boundingBox, bulletSprite.boundingBox)){

// 播放×××音效

[[SimpleAudioEngine sharedEngine] playEffect:@"bullet.mp3"];

// 删除×××精灵

[bulletArray removeObject:bulletSprite];

[batchNode removeChild:bulletSprite cleanup:YES];

// 敌机生命值减1

enemyPlaneSprite.lifeValue--;

// 血条减少

if (enemyPlaneSprite.enemyPlaneHP != nil) {

enemyPlaneSprite.enemyPlaneHP.percentage

= enemyPlaneSprite.HPInterval * enemyPlaneSprite.lifeValue;

}

// 判断敌机的生命值

if (enemyPlaneSprite.lifeValue <= 0) {

// 删除敌机精灵

[enemyPlaneArray removeObject:enemyPlaneSprite];

[batchNode removeChild:enemyPlaneSprite cleanup:YES];

// 播放爆炸动画

[self bombAnimate:@"blast" :enemyPlaneSprite.position ];

// 播放爆炸音效

[[SimpleAudioEngine sharedEngine] playEffect:@"b0.mp3"];

}

break;

}

}

}

}

collisionDetection:方法首先调用getChildByTag:方法获取精灵表单,然后循环遍历敌机数组。CGRectIntersectsRect(rect 1.feet 2)函数可以判断矩形结构是否交叉,两个矩形对象是否重叠,常用来检测两个图标是否发生碰撞。CCNode有一个属性boundingBox,会返回精灵的边界框。使用这个属性比自己计算要好,因为这样做更简单,同时也考虑了精灵的变形。通过CGRectIntersectsRect(rect 1.feet 2)函数判断玩家飞机和敌机是否发生碰撞,如果发生碰撞,则播放一段爆炸动画,然后从敌机数组和精灵表单中删除敌机精灵,再从精灵表单中删除玩家飞机精灵,最后调用gameOver:方法结束游戏。

如果玩家飞机和敌机没有发生碰撞,则不循环遍历×××数组,判断×××和敌机是否发生碰撞,如果发生碰撞,则播放×××音效,删除×××精灵,将敌机生命值减去1;如果敌机有血条,则更新血条(小飞碟生命值为10,有血条)。接下来判断敌机的生命值,如果敌机生命值为0,则从敌机数组和精灵表单中删除敌机精灵,播放爆炸动画和爆炸音效。

在私有分类中添加两个方法,实现代码如下(程序清单同上)。

// 播放爆炸动画

-(void) bombAnimate:(NSString*) name : (CGPoint) position;

// 游戏结束

-(void) gameOver:(NSString*) labelString;

bombAnimate: name:方法用于播放爆炸动画。实现代码如下(程序清单同上)。

-(void) bombAnimate:(NSString*) name : (CGPoint) position {

NSString* bombName = [NSString stringWithFormat:@"%@0.png",name];

float delay = 0.08f;

CCSpriteBatchNode *batchNode = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

CCSprite* blastSprite = [CCSprite spriteWithSpriteFrameName:bombName];

blastSprite.position = position;

// 获得动画帧

CCAnimation* blastAnimation = [self getAnimationByName:name delay:delay animNum:4];

// 组合动作:1.播放动画 2.删除动画精灵对象

id action = [CCSequence actions: [CCAnimate actionWithAnimation:blastAnimation],

[CCCallBlock actionWithBlock:^() {

[batchNode removeChild:blastSprite cleanup:YES];

}],nil];

[blastSprite runAction:action];

[batchNode addChild:blastSprite z:4];

}

bombAnimate: name:方法接收传进来的爆炸效果名称,获取爆炸动画帧,先播放动画,动画播放完毕后删除动画。

gameOver:方法用于游戏结束时清除精灵对象并提示游戏信息。实现代码如下(程序清单同上)。

-(void) gameOver:(NSString*) labelString{

// 停止所有动作

[self unscheduleUpdate];

// 游戏结束

CCMenuItemFont* gameItem = [CCMenuItemFont itemWithString:labelString

target:self selector:@selector(onRestartGame:)];

gameItem.position=ccp(screenWidth/2, screenHeight/2);

CCMenu* menu = [CCMenu menuWithItems:gameItem, nil];

menu.position = CGPointZero;

[self addChild:menu];

}

找到onEnter方法,在⑤部分代码后初始化×××数组,实现代码如下。

bulletArray = [[CCArray alloc] init];

修改update方法,实现代码如下(程序清单同上)。

-(void) update:(ccTime)delta{

count++;

[self updateBackground:delta];

[self updateEnemySprite:delta];

[self removeEnemySprite:delta];

[self updateShooting:delta];

[self removeBulletSprite:delta];

self collisionDetection:delta];

}

再次编译并运行游戏,大量的敌机会随机出现,并向屏幕下方俯冲,控制玩家飞机发射×××,×××击中敌机显示爆炸效果。模拟器显示效果如图13.64所示。

疯狂ios之疯狂打飞机游戏(3)_第2张图片

13.14.9添加背景音乐

有了音效,有了爆炸效果,没有背景音乐好像缺少点热血澎湃的游戏感觉,让我们马上加入背景音乐吧。

找到onEnter方法,在游戏的主循环代码之前添加背景音乐播放功能,实现代码如下(程序清单同上)。

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"s3.wav" loop:YES];

[[SimpleAudioEngine sharedEngine] setBackgroundMusicVolume:0.5];

再次编译并运行游戏,开始游戏时就可以听见令人热血澎湃的背景音乐了。读者可以按照13.13.2节的内容为游戏增加声音设置功能选项,包括声音开关、声音大小调节等,这里不再赘述。