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{
// 控制count为100的倍数时添加一架敌机
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.png和e2.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所示。
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;
// 控制count为8的倍数时发射一颗×××
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所示。
13.14.9添加背景音乐
有了音效,有了爆炸效果,没有背景音乐好像缺少点热血澎湃的游戏感觉,让我们马上加入背景音乐吧。
找到onEnter方法,在游戏的主循环代码之前添加背景音乐播放功能,实现代码如下(程序清单同上)。
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"s3.wav" loop:YES];
[[SimpleAudioEngine sharedEngine] setBackgroundMusicVolume:0.5];
再次编译并运行游戏,开始游戏时就可以听见令人热血澎湃的背景音乐了。读者可以按照13.13.2节的内容为游戏增加声音设置功能选项,包括声音开关、声音大小调节等,这里不再赘述。