且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《Cocos2D权威指南》——2.6 最后的点缀

更新时间:2022-09-21 13:57:35

2.6 最后的点缀

有大师曾经说过,游戏永远没有真正意义上的完结,因为总是可以不断地完善它,给它赋予更多的玩法和元素,使之更充实和完美。本章我们的目的是学习使用Cocos2D制作一个简单的游戏,而制作游戏追求完美的心境是永恒不变的。接下来将进一步完善此游戏。
2.6.1 添加计分和玩家生命值
为了增加游戏的趣味性,游戏还需要加上计分规则以及玩家生命值的设定。简单起见,我们设置每击中一个敌人计100分,玩家共有3条生命,如果玩家分数超过1000分,即击中10个敌人就可以胜利过关;反之,如果失去3条生命,则Game Over!
打开HelloWorldLayer.h文件,在头文件中添加两个实例变量,如代码清单2-28所示。
代码清单2-28 在头文件中添加两个实例变量

CCLabelTTF *_lifeLabel;
CCLabelTTF *_scoreLabel;

在init方法中return self;语句前、if条件语句的最后添加代码清单2-29所示代码。
代码清单2-29 在方法中return self;语句前、if条件语句的最后添加代码

//12.init player lives & score
 CCLabelTTF *lifeIndicator = [CCLabelTTF labelWithString:@"生命值:" fontName:@"Arial" fontSize:20];
 lifeIndicator.anchorPoint = ccp(0.0,0.5);
 lifeIndicator.position = ccp(20,winSize.height - 20);
 [selfaddChild:lifeIndicator z:10];
 _lifeLabel = [CCLabelTTF labelWithString:@"3" fontName:@"Arial" fontSize:20];
 _lifeLabel.position = ccpAdd(lifeIndicator.position, ccp(lifeIndicator.contentSize.width+10,0));
 [self addChild:_lifeLabel z:10];

 CCLabelTTF *scoreIndicator = [CCLabelTTF labelWithString:@"分数:" fontName:@"Arial" fontSize:20];
 scoreIndicator.anchorPoint = ccp(0.0,0.5f);
 scoreIndicator.position = ccp(winSize.width - 100,winSize.height - 20);
 [selfaddChild:scoreIndicator z:10];
 _scoreLabel = [CCLabelTTF labelWithString:@"00" fontName:@"Arial" fontSize:20];
 _scoreLabel.position = ccpAdd(scoreIndicator.position, ccp(scoreIndicator.contentSize.width+ 10,0));
 [self addChild:_scoreLabel z:10];

这段代码的主要作用是在游戏界面中添加计分和玩家生命值的显示。
注意 坐标计算技巧:这里通过修改label的anchorPoint定位label,同时使用ccpAdd宏计算两个点的和。
编译并运行代码,屏幕输出如图2-9所示。

《Cocos2D权威指南》——2.6 最后的点缀


按照先前设定的规则,击中一个敌机加100分,被敌机撞到,生命值减去1。
步骤1 打开HelloWorldLayer.h,向其中添加两个实例变量,如代码清单2-30所示。
代码清单2-30 添加两个实例变量
int     _totalLives;
int     _totalScore;

步骤2 在init方法中return self;语句前、if条件语句的最后添加代码进行初始化,如代码清单2-31所示。
代码清单2-31 在方法中return self;语句前、if条件语句的最后添加代码

//13.init lives & score variable
    _totalLives = 3;
   _totalScore = 0;

步骤3 定义一个updateHUD的方法,根据_totalLives和_totalScore这两个变量实时更新相应的label方法,该方法的实现如代码清单2-32所示。
代码清单2-32 实时更新相应的label方法

-(void) updateHUD:(ccTime)dt{
    [_lifeLabelsetString:[NSStringstringWithFormat:@"%2d",_totalLives]];
    [_scoreLabelsetString:[NSStringstringWithFormat:@"%04d",_totalScore]];
}

注意 游戏开发初学者对HUD可能有点陌生,这里有必要做一下解释。HUD是Head-Up Display的简写,是游戏开发中一个非常重要的概念。Mini地图、分数、血条、时间进度条和魔兽世界里的技能条等,这些都是HUD,主要用来给玩家提供一些辅助信息,让玩家可更加关心游戏玩法,而不用去管底层的数据。
步骤4 更新游戏主循环,添加updateHUD方法调用。如代码清单2-33所示。
代码清单2-33 游戏主循环

-(void) update:(ccTime)dt{
    [selfupdatePlayerPosition:dt];
    [selfupdatePlayerShooting:dt];
    [selfcollisionDetection:dt];
    [selfupdateHUD:dt];
}

步骤5 在碰撞检测的逻辑里添加相应的计分和损失生命值的逻辑计算,如代码清单2-34所示。

代码清单2-34 添加计分和损失生命值的逻辑计算

-(void) collisionDetection:(ccTime)dt{

    CCSprite *enemy;
CGRectbulletRect = [self rectOfSprite:_bulletSprite];
CCARRAY_FOREACH(_enemySprites, enemy)
    {
if (enemy.visible) {
            //1.bullet & enemy collision detection
CGRectenemyRect = [self rectOfSprite:enemy];
if (_bulletSprite.visible&&CGRectIntersectsRect(enemyRect, bulletRect)) {
enemy.visible = NO;
                _bulletSprite.visible = NO;

                _totalScore += 100;

                [_bulletSpritestopAllActions];
                [enemystopAllActions];
CCLOG(@"collision bullet");
break;
            }

            //2.enemy & player collision detection
            CCSprite *playerSprite = (CCSprite*)[self getChildByTag:kTagPalyer];
CGRectplayRect = [self rectOfSprite:playerSprite];
if (playerSprite.visible&&
playerSprite.numberOfRunningActions == 0
&&CGRectIntersectsRect(enemyRect, playRect)) {
enemy.visible = NO;

                _totalLives -= 1;

id blink = [CCBlink actionWithDuration:2.0 blinks:4];
                [playerSpritestopAllActions];
                [playerSpriterunAction:blink];
CCLOG(@"collision player");
break;
            }
        }
    }
}

2.6.2 添加游戏胜利和结束画面
玩家达到1000分或者生命值变为0时,需要呈现游戏胜利或者失败的画面。为了简单,我们设置游戏结束时(不管是胜利还是失败)只在中间显示一个label,2秒后重新开始游戏。
步骤1 打开HelloWorldLayer.h,添加一个新的实例变量,如代码清单2-35所示。
代码清单2-35 添加一个新的实例变量

CCLabelTTF *_gameEndLabel;

步骤2 添加一个私有方法,如代码清单2-36所示。
代码清单2-36 添加一个私有方法

-(void) onRestartGame{
    [[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];
}

步骤3 修改碰撞处理的代码,如代码清单2-37所示。
代码清单2-37 修改碰撞处理

-(void) collisionDetection:(ccTime)dt{

    CCSprite *enemy;
CGRectbulletRect = [self rectOfSprite:_bulletSprite];
CCARRAY_FOREACH(_enemySprites, enemy)
    {
if (enemy.visible) {
            //1.bullet & enemy collision detection
CGRectenemyRect = [self rectOfSprite:enemy];
if (_bulletSprite.visible&&CGRectIntersectsRect(enemyRect, bulletRect)) {
enemy.visible = NO;
                _bulletSprite.visible = NO;

                _totalScore += 100;
if (_totalScore>= 1000) {
                    [_gameEndLabelsetString:@"游戏胜利!"];
                    _gameEndLabel.visible = YES;
idscaleTo = [CCScaleTo actionWithDuration:1.0 scale:1.2f];
                    [_gameEndLabelrunAction:scaleTo];
                    [selfunscheduleUpdate];
                    [self performSelector:@selector(onRestartGame) withObject:nil afterDelay:2.0f];
                }

                [_bulletSpritestopAllActions];
                [enemystopAllActions];
CCLOG(@"collision bullet");
break;
            }

            //2.enemy & player collision detection
            CCSprite *playerSprite = (CCSprite*)[self getChildByTag:kTagPalyer];
CGRectplayRect = [self rectOfSprite:playerSprite];
if (playerSprite.visible&&
playerSprite.numberOfRunningActions == 0
&&CGRectIntersectsRect(enemyRect, playRect)) {
enemy.visible = NO;
                _totalLives -= 1;
if (_totalLives<= 0) {
                    [_gameEndLabelsetString:@"游戏失败!"];
                    _gameEndLabel.visible = YES;
idscaleTo = [CCScaleTo actionWithDuration:1.0 scale:1.2f];
                    [_gameEndLabelrunAction:scaleTo];
                    [selfunscheduleUpdate];
                    [self performSelector:@selector(onRestartGame) withObject:nil afterDelay:3.0f];
                }

id blink = [CCBlink actionWithDuration:2.0 blinks:4];
                [playerSpritestopAllActions];
                [playerSpriterunAction:blink];
CCLOG(@"collision player");
break;
            }
        }
    }
}

代码比较简单,就是当条件满足时更改label显示的内容,然后让label运行一个action,以放大的形式呈现;最后停止游戏主循环,隔2秒调用重启游戏的方法。
注意 这里需要注意[self unscheduleUpdate]方法,就是终止update方法,即终止游戏主循环。
编译并运行,大家玩一玩这个游戏去吧!
当然,还可以给这个游戏添加更多的乐趣,比如每过一关,敌机的数量越来越多,过关的分数要求越来越高等。本章介绍就到此为止,后面章节我们还会不断地完善此游戏,使之更加丰富多彩。大家在学习的过程中,也可以充分发挥想象力和创造力,不断地给这个游戏注入新的活力。