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所示。
按照先前设定的规则,击中一个敌机加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方法,即终止游戏主循环。
编译并运行,大家玩一玩这个游戏去吧!
当然,还可以给这个游戏添加更多的乐趣,比如每过一关,敌机的数量越来越多,过关的分数要求越来越高等。本章介绍就到此为止,后面章节我们还会不断地完善此游戏,使之更加丰富多彩。大家在学习的过程中,也可以充分发挥想象力和创造力,不断地给这个游戏注入新的活力。