0
回答
疯狂ios之疯狂打飞机游戏(3)
华为云实践训练营,热门技术免费实践!>>>   

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所示。



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所示。


13.14.9  添加背景音乐

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

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

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

[[SimpleAudioEngine sharedEngine] setBackgroundMusicVolume:0.5];

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

iOS
举报
博文视点
发帖于4年前 0回/655阅
顶部