使用COCOS2D和BOX2D制作《JETPACK JOYRIDE》PART-3

本文由游戏帮翻译,泰然授权转载,转载请联系原文作者!

作者:Bogdan Vladu

欢迎回到《Jetpack Joyride》游戏制作教程!在本系列教程中我们将使用Cocos2D和Box2D以及LevelHelper和SpriteHelper制作一款类似于《Jetpack Joyride》的游戏。(请点击此处查看本系列第1第2部分

clip_image002[6]

迄今为止我们利用了一只老鼠并在其后背安上了喷射飞行器,从而让它能够飞越滚动卷轴的关卡,完成了不同动画以及连续滚动效果。

而在本部分教程中,我们将进一步处理游戏中的碰撞。换句话说,完成本部分教程后我们便能够杀死老鼠了!

同时我们也将在此添加音效,更多复杂的动画,并消除游戏玩法中的某些问题。让我们正式开始吧!

开始

首先你必须获得第2部分教程之前(包括第2部分教程)的完整项目内容。同时你还需要下载声音包。

LevelHelper中打开项目,导航到Finder的最后一个项目并双击文件RocketMouse.lhproject

clip_image004[4]

DoubleClickOnLHProject(from raywenderlich)

LevelHelper应该打开的是我们所创造的最后一个关卡。而如果因为某种原因LevelHelper打开了早前的关卡,你就应该来到关卡部分找到并打开最后关卡。

执行碰撞:概述

我们的游戏拥有一只飞翔的老鼠以及激光射击,并且还设置了一些供玩家收集的硬币。但是如果角色碰到硬币或激光却没有任何反应,这不是很奇怪?所以我们应该在此添加一些游戏玩法。

在此之前我们需要明确已经设立了哪些内容。

LevelHelper中右击右边工具条(第一个标签)中的硬币,选择打开SpriteHelper Scene。然后在SpriteHelper中选择硬币,你便能够看到物理属性部分,并在“Is Sensor”选项中打勾:

clip_image006[4]

CoinSettings(from raywenderlich)

之所以在这个选项打勾是因为我们希望玩家在接触到硬币时能够产生碰撞回应,从而我们便能够适时地给予玩家分数,并且我们也不希望老鼠真正表现出与硬币的碰撞行为。

那激光呢?而如果是激光属性,这个精灵不需要勾选“Is Sensor”选项:

clip_image008[4]

LaserSettings(from raywenderlich)

为什么?难道我们不想要表现出相同的行为?并非如此,只不过我们希望能够追踪激光的动画,所以需要采取不同方法。

如果选中了Is Sensor,那么碰撞效果将只会发生在老鼠第一次碰触到精灵时。而我们希望老鼠与激光能够进行连续碰撞(每一帧),因为每一帧的激光动画会发生变化(游戏邦注:例如从开启关闭状态),在此期间老鼠要一直与激光打交道。

而如果我们在激光属性中选择了Is Sensor选项,并且当激光关闭时老鼠仍然与激光接触,这时候我们拥有碰撞反馈,但是我们却不能在此炸毁老鼠,因为毕竟激光已经消失了。

随后当激光再次开启时,因为我们已经执行了一次碰撞,所以再也无法知道玩家是否仍然碰触到激光。

我们该如何解决这一问题?很简单。结合Box2d使用LevelHelper的碰撞行为,我们便能够废除碰撞反馈,使得老鼠能够闪过激光而不产生与其碰撞的感觉。同时我们还将在每一帧中设置碰撞触发器,以此判断我们是否该杀死老鼠。

执行碰撞:硬币

打开你的Xcode项目,导航到HelloWorldScene.mm并在初始化之前说明这种新方法:

-(void) setupCollisionHandling
{
[lh useLevelHelperCollisionHandling];
[lh registerBeginOrEndCollisionCallbackBetweenTagA:PLAYER andTagB:COIN idListener:self selListener:@selector(mouseCoinCollision:)];
}

接下来在初始化(在调用retrieveRequiredObjects之后)最后调用新方法:

[self setupCollisionHandling];

setupCollisionHandling方法中,我们首先告知LevelHelperLoader实例,我们想要使用它的碰撞处理方法而非自己创造碰撞处理方法。我的建议是:要一直使用这种方法,因为它快速,简单又方便。

在第二次调用中我们提出了新方法,即我们想要用LevelHelper调用一次碰撞——精灵与玩家标签(在这里就是老鼠精灵)以及精灵与硬币标签(在这里就是任何硬币精灵)在任何时候发生的碰撞。

是否还记得我们在第2部分是如何设置碰撞标签?LevelHelper将为这些标签自动生成常数,从而让我们能够将其用于代码的编写中。如果你想知道它们的定义,你可以按控制键并点击其中一个常数并选择转到定义

LevelHelper的碰撞处理中有多种类型的碰撞,不过我们将在此使用beginOrEnd,因为我们的硬币精灵已被定义为感应器,而Box2d处理感应对象的碰撞只能发生在开始碰撞类型上。

以下我们将定义老鼠与硬币之间碰撞反馈的方法。在setupCollisionHandling之前添加新方法:

-(void)mouseCoinCollision:(LHContactInfo*)contact
{
LHSprite* coin = [contact spriteB];

if(nil != coin)
{
if([coin visible])
{
[self scoreHitAtPosition:[coin position] withPoints:100];
}

[coin setVisible:NO];
}
}

正如你所看到的,这个方法获得了一个LHContactInfo*对象作为参数。这是一个特别类,将能够给予我们关于碰撞的相关信息。

怎样从这种碰撞中获得硬币精灵?我们在registerBeginOrEndCollisionCallbackBetweenTagATagB调用中将硬币精灵记录为tagB。如果tag B是货币,那么我们的精灵便能够使用[contact spriteB]

接下来我们必须确保硬币不是无值。尽管这点不是非常重要,但是却能够帮助我们有效地避免错误。所以我们最好还是核查下是否出现无值情况。

如果硬币是可见的,我们将快速调用一个方法而给予获得货币的玩家分数奖励。随后我们设置硬币为不可见,从而将其隐藏在屏幕之后,并告知玩家现在他的虚拟钱包中已经有钱了。

现在编译将会出现一个错误,因为我们还未定义scoreHitAtPosition方法。让我们现在定义它。将以下代码置于mouseCoinCollision方法之前:

-(void)scoreHitAtPosition:(CGPoint)position withPoints:(int)points
{
score += points;
}

HelloWorldScene.h中说明分数变量:

int score;

编译并运行游戏,你将看到当老鼠与硬币发生碰撞时,货币将会消失!

clip_image010[4]

CoinsCollected(from raywenderlich)

执行碰撞:激光

下一步便是处理激光与老鼠之间的碰撞。

setupCollisionHandling方法最后添加以下代码:

[lh registerPreCollisionCallbackBetweenTagA:PLAYER andTagB:LASER idListener:self selListener:@selector(mouseLaserCollision:)];

在这里我们已经记下了玩家与激光之间的预制碰触回调。而现在我们需要定义mouseLaserCollision方法。

这么做是因为激光精灵并非感应器,而我们希望能够接收到每一帧的碰撞通知,从而让老鼠在接触被激活的激光时能够死去。

将以下代码放在mouseCoinCollision之后:

-(void)mouseLaserCollision:(LHContactInfo*)contact
{
LHSprite* laser = [contact spriteB];

int frame  = [laser currentFrame];

// If we make the laser a sensor, the callback will be called only once – at first collision.
// This is not good as we want to kill the player when the laser changes to active.
// So we disable the contact so that the player and laser don’t collide, but trigger a collision.
// Disabling the contact is only active for one frame,
// so on the next frame the contact will be active again, triggering the collision.

b2Contact* box2dContact = [contact contact];
box2dContact->SetEnabled(false);

if(playerIsDead)
return;

if(frame != 0)
{
[self killPlayer];
}
}

在上述代码中,我们从接触信息中获得了精灵B,而在此精灵B也就是激光。让我们从精灵中获得当前帧,因为我们需要测试激光是否被激活,并且是否杀死玩家角色。

随后我们从LevelHelper的接触对象中获得了Box2d的接触信息,并禁止了接触以免发生任何碰撞行为。我们需要检查玩家是否死亡;如果死亡了,我们就不需要再做其它事了。

最后,我们将测试帧数是否为0。如果是0,那么激光就未被激活,我们就不需要杀死玩家。而如果不为0,这就意味着我们必须杀掉玩家。我们取消了碰撞回调机制,因为我们已经不再需要它了,而只要杀掉玩家便可。

需要注意的是,我们现在正使用两个之前未定义的内容,即killPlayer方法以及playerIsDead变量。所以我们现在需要定义它们。

HelloWorldScene.h中添加以下类说明:

bool playerIsDead;

HelloWorldScene.mm中定义killPlayer方法(添加于mouseCoinCollision之后):

-(void)killPlayer
{
playerVelocity = 0.0;
playerShouldFly = false;
playerIsDead = true;
playerWasFlying = false;
[rocketFlame setVisible:NO];
[player startAnimationNamed:@"mouseDie"];

[paralaxNode setSpeed:0];

CGSize winSize = [[CCDirector sharedDirector] winSize];
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Game Over" fontName:@"Marker Felt" fontSize:64];
label.color = ccRED;
label.position = ccp(winSize.width*0.5, winSize.height*0.75);
[self addChild:label];

CCMenuItem *item = [CCMenuItemFont itemFromString:@"Restart" target: self selector:@selector(restartGame)];
CCMenu *menu = [CCMenu menuWithItems:item, nil];
[menu alignItemsVertically];

[self addChild:menu];
}

killPlayer玩法中,我们将玩家的速率设置为0。如果此时的玩家正处于飞行状态,那么他将会快速掉落到地面上。

随后我们明确设置了playIsDead的变量,从而我们便知道玩家在这个方法中死去了。随后我们隐藏了飞行器的火焰。

最后,我们通过使用LevelHelper标签: Jetpack Joyride, cocos2d教程

?>