使用Cocos2D和Box2D制作《Jetpack Joyride》(2)
作者:Bogdan Vladu
前面我们已经制作出带有背景美工元素的可行关卡(第1部分详见此处)。
在第2部分中,我们将着眼于添加游戏活动元素,包括激光、硬币、飞行角色及初始触碰活动等。
要继续本教程,你需持有我们第1部分谈到的RocketMouse项目内容。
入门指南
制作《Jetpack Joyride》游戏的下一步是,添加可供玩家直接互动的游戏元素:激光和硬币。添加这些元素也是学习如何基于LevelHelper落实动画和激光的绝佳方式。
由于我们希望激光间断运作,所以我们需要将其制作成动画效果。至于硬币,我们希望把握玩家同其互动的时间点,但我们不希望玩家弹开它们。要做到这点,我们需要赋予其传感能力。
把握动画和触碰反应基本要素后,我们会将话题转移至游戏的其他元素,包括游戏玩家。我们将学习如何创造标签,追踪触碰活动,然后将其同若干真实代码整合。
在开始内容前,请打开你的当前LevelHelper项目,将关卡状态设置成level03。在进行此过程时,将这一新关卡添加至Xcode项目中,按照下列操作更新代码,将关卡状态设置成level 3:
lh = [[LevelHelperLoader alloc] initWithContentOfFile:@”level03″];
着手动画元素:添加激光
我们对于玩法动画元素的运用相当简单。当玩家同激光接触时,我们只需测试动画帧所处位置。若动画帧处于激光关闭状态,玩家就不会有性命之忧,如果处于激光开启状态,玩家就会死亡。
要创造激光动画,我们需要查看LevelHelper右侧的图像列表,右击其中的一个激光图像。然后在菜单中选择“Open SpriteHelper scene”。
SpriteHelper会呈现正确场景。转换至Animation版块,点击Add Frame。这一操作将给动画添加两个精灵。
通过点击上下箭头,以正确顺利安排游戏画面。将速度设定成3,选中Start At Launch和Loop Forever选项。
完成这些后,点击Command-S保存画面。
现在我们回到LevelHelper,着手处理我们刚刚创建的动画。此时你会发现Animations版块已被添加至LevelHelper中,如下图所示。
激活动画有两种方式。我将逐一进行陈述。
第一种方式就是将动画拖到关卡(游戏邦注:这和操作精灵的方式类似)。
第二种方式是,将动画同已存在于关卡中的精灵连接起来。在这种情况下,我会将动画添加至激光画面中。
现在进入精灵版块。我们还没有将激光精灵添加至关卡中,所以现在要将列表中的一个精灵拖到关卡中,要选定此精灵。然后跳到General Properties版块,从列表中选择激光动画。
继续在关卡中添加激光,直到达到满意的危险系数。你可以基于选择精灵时出现的控键旋转和调节laser。
我的关卡现在呈现如下样式:
若查看laser形状,你会发现精灵现在呈现四方形模式。
就我们的游戏来说,我们希望老鼠只在击中真正的激光时死去,所以我们不妨缩小模型的尺寸。
要做到这点,我们可以采用定义小猫和小狗形状的方式,或者我们可以通过预定义的数值修改Physics菜单下的Shape Border属性。现在你已知道如何通过位点创造物件形状,下面就来尝试第二种方式。
在LevelHelper的Images版块中选择laser1图像,然后右击,选择Open SpriteHelper Scene。
当呈现SpriteHelper scene时,点击“laser1”精灵。由于激光是个动画,我们需要修改动画首个帧数的物理属性——在此就是指“laser1”精灵。动画包含第一个帧数的物理属性。
选中laser1后,进入Physics菜单,在Shape Border数值中输入70和40。精灵的最终模型大小是原本尺寸减去你所输入的数值。你可以在画面上看到视觉表征。
回到LevelHelper,我们会看到所有laser都被更新为正确形状。
现在选中所有laser,将它们添加到我们的parallax(游戏邦注:影视编辑软件)。按比例输入1和0。
保存关卡,若创建和运行游戏后你会发现,它现在具备动态laser。
处理传感设置:添加硬币
现在就轮到硬币。记住,我们想要创建硬币传感器,这样老鼠就能够直接穿过它们,不会弹开,但我们依然能够察觉到触碰活动的发生。
要创造硬币传感器,从LevelHelper中的精灵列表中选择硬币精灵,右击,然后选择Open SpriteHelper Scene。
开放SpriteHelper scene后,选择硬币精灵,选中Physics菜单中的Is Sensor、Is Circle和Can Sleep选项。然后保存画面。
* Is Sensor:让身体触发触碰活动,但不呈现触碰反应
* Is Circle:让身体的形状呈现圆形模式
* Can Sleep:加速物理元素的模拟过程
现在将硬币精灵添加到关卡,将其放在你认为合适的位置。通过Clone Tool复制硬币,基于你青睐的位移方向。
现在我的关卡呈现如下模式:
将硬币添加至parallax,将比例调成1和0,然后保存关卡。编辑和运行你的项目,现在你拥有硬币元素。
添加玩家元素
现在我们拥有包含基本要素的不错关卡,下面就来创建玩家角色及其相应的动画内容。
打开SpriteHelper,进入File\New创建空的精灵工作表。然后在Finder中点击目录(游戏邦注:这里保存游戏的美工元素)。在此你需要将所有老鼠精灵和火箭火焰拖到SpriteHelper窗口。
现在我们需要通过刚输入的美工元素创建单个画面。这是创建动画前的必要步骤。LevelHelper和SpriteHelper支持的所有引擎都将动画帧数视作图像文件的组成元素。
现在切换到Sheet Editor菜单,取消选定Crop,因为我们不希望改变老鼠动画的帧数。点击Pack Sprites,将所有精灵都分配到精灵表单中。
然后,切换到SpriteHelper的Animation版块。我们将在此创建所有必要的动画内容,和我创建laser的过程一样。
运用你先前学到的技巧,创建包含下述属性的5个动画内容。
若你忘记如何创建动画,点击+按键,双击进行重命名。然后选定动画的规定帧数,设定速度,记得给需要进行循环的动画选定Loop Forever。
1)动画名称:mouseRun
Loop Forever: YES
Speed: 0.400(默认)
Repetition: 1.000(默认)
Frames: rocketmouse_1_run, rocketmouse_2_run, rocketmouse_3_run, rocketmouse_4_run
2)动画名称:flame
Start At Launch: YES
Loop Forever: YES
Speed: 0.400(默认)
Repetition: 1.000(默认)
Frames: rocket_flame1, rocket_flame2
3)动画名称:mouseFly
Start At Launch: YES
Loop Forever: YES
Speed: 0.400(默认)
Repetition: 1.000(默认)
Frames: rocketmouse_5_fly
4)动画名称:mouseDie
Start At Launch: YES
Loop Forever: NO
Speed: 0.400(默认)
Repetition: 1.000(默认)
Frames: rocketmouse_7_die, rocketmouse_8_die
5)动画名称:mouseFall
Start At Launch: YES
Loop Forever: NO
Speed: 0.400(默认)
Repetition: 1.000(默认)
Frames: rocketmouse_6_fall
完成这些操作后,点击Command-S,保存画面。在Save对话框中,点击Xcode项目Resources文件夹中的Images文件,将画面保存为“mouse”。
回到LevelHelper,我们将在Animation区块中找到所有新创建的动画内容。
将mouseRun动画拖到主页面(有红色边界的页面),然后置于页面左侧。
然后拖动火焰精灵,将其放置在老鼠背后的红色坦克下。
最终画面如下:
仔细观察你会发现,火焰精灵位于红色坦克之上。不妨将其放在坦克之后,这样火焰看起来会更像是由坦克产生的。完成这一操作后,选择火焰精灵,然后在General Properties中将Z Order调成-1。
若你此时通过Scene Tester运行关卡,你可能会看到老鼠,但也可能没看到。
这是因为精灵是通过成批节点进行渲染。要将老鼠放在关卡其他精灵顶部,我们需要改变成批节点的Z Order。
切换到LevelHelper的Images区块,双击mouse.png的Z Order字段。输入数值4。
我们希望玩家把握所有内容,我们很快将添加更多图片。这就是为什么我们要在这里设置一个更大的数值。
现在运行关卡,你会看到老鼠在画面中奔跑,但没有发生任何触碰活动。
它其实有何小狗和小猫发生触碰,但由于老鼠和其他物件都处于静止状态,所以什么也没有发生。
现在是时候把老鼠和火焰精灵设置成动态模式。打开老鼠的SpriteHelper情境:
然后选择所有精灵,将其设置成动态模式。我们不希望老鼠在和其他精灵触碰时发生旋转,所以还要选择Fixed Rotation option。
至于两个火箭火焰精灵,我们还需要勾选Is Sensor选项。这是因为随后我们将把火焰和火箭坦克结合起来。
完成操作后保存火箭坦克。
再次运行Scene Tester,我们将会看到老鼠在画面中掉落。这是老鼠对地心引力所做出的反应。不妨让它继续停留在屏幕上。
点击Physic Boundaries按键。
在Physic Boundaries窗口点击Create。
我们现已创建边界,但想要对其进行编辑,使其出现在地板中间的老鼠的下方,老鼠会在这块区域中行走。点击Physic Boundaries窗口中的Edit。
现在你会在物理边界的拐角处看到4个红色控键。你可以拖曳任何底部控键移动边界,确保边界始终处在老鼠的脚下。
你将看到类似的画面:
若你对物理边界没有什么意见,点击Editing按键,中断编辑过程。
在Scene Tester中运行关卡,你将看到老鼠和小猫&小狗发生触碰,然后继续停留在屏幕中,但这里还存在另一问题。
这次,火焰在屏幕中落下,它有传感设置,所以不会和任何物体触碰,不会附着于老鼠的身体。但我们可以通过创建远距离接合点,将火焰和老鼠联系起来。
现在切换到LevelHelper中的Joints版块,选择列表中的Distance Joint,点击绿色+按键。
现在我们可以选择接合点要连接的精灵。
选择列表中的接合点,然后是Body A的属性,点击圆形图标。然后将老鼠拖至火焰精灵。当文本显示rocket_flame_1时,放开老鼠。
现在在Body B中重复相同操作,但这次要选择老鼠。
现在将接合点的定位点拉得更近些。
在接合点属性中选择A,拖曳显示火焰精灵的手柄,将锚点置于坦克之上。
在其他定位点中重复这一操作。选择锚点B,拖曳把手,将此锚点置于其他定位点的旁边。
若你在Scene Tester中运作关卡,你会看到火焰现在附着于老鼠身上。
现在我们已完成进入编码工作前所需的所有内容。
创建标签,执行触碰活动
要执行精灵间的触碰活动,我们需要记录同类精灵与其他类型精灵的触碰活动。
我们需要通过标签将精灵分类。所以所有有关小狗的精灵都会被标上“DOG” ,而所有涉及小猫的精灵都会被标上“CAT”。
要创建这些标签,需在LevelHelper中点击Define Tags按键。
在Define Tags窗口中,给新标签设定名称,点击Add按键创建新标签。需要创建如下标签:DOG、CAT、LASE、COIN和PLAYER。
明确定义标签后,我们需要将这些标签分配到各个精灵中。
选择左边精灵列表中的所有小狗精灵。然后在General Properties中将这些DOG标签分配到上述精灵。
然后在所有精灵中重复这一操作:将PLAYER标签分配至老鼠精灵中,CAT分配至各小猫精灵,LASER分配到各种激光精灵,COIN分配到硬币精灵中。
完成这些操作后,以Command-S保存关卡。
编写游戏逻辑的代码
我们现在准备着手编写游戏代码。所以我们重新回到Xcode,打开我们的项目,开始真正的趣味之旅。
首先,确保将新的美工元素(游戏邦注:即mouse.png)纳入其中。此时的资源文件夹将呈如下模式:
为控制parallax的运作,让玩家能够进行跳跃,我们需要设定若干指示LevelHelper物件的变量。将HelloWorldScene.h的这些代码添加至类别定义中。
LHParallaxNode* paralaxNode;
LHSprite* player;
b2Body* playerBody;LHSprite* rocketFlame;
随后在类别定义之外,即@end和+(id)scene之间,添加下列方法标记(method signature):
-(void) retrieveRequiredObjects;
在HelloWorldScene.mm中定义新方法(可以选择任何适当位置,但要确保其处在类别定义之中)。我将其放置于初始方法之后。
-(void) retrieveRequiredObjects
{
//Retrieve pointers to parallax node and player sprite.
paralaxNode = [lh paralaxNodeWithUniqueName:@"Parallax_1"];
NSAssert(paralaxNode!=nil, @”Couldn’t find the parallax!”);player = [lh spriteWithUniqueName:@"player"];
NSAssert(player!=nil, @”Couldn’t find the player!”);playerBody = [player body];
NSAssert(playerBody!=nil, @”Error taking the body from the player LHSprite.”);rocketFlame = [lh spriteWithUniqueName:@"flame"];
NSAssert(rocketFlame!=nil, @”Couldn’t find flame sprite!”);[rocketFlame setVisible:NO]; //You can do it in LH, but I do it here so you guys can see it in LH
}
这里我们将把目光转至parallax,赋予LevelHelper parallax标识名称。
再来就是玩家精灵,也是赋予它们标识名称。若标识名称原先没有修改,要将其改成“player”,如下面的截图所示。
同样,你得把火焰的标识名称改成“flame”,以同代码相匹配。
若你通过spriteWithUniqueName查看精灵,你将得到一个LHSprite*实例,这是源自CCSprite的一个类别。通过此LHSprite*实例,我们将指示器转移到玩家的Box2d身体,因为我们随后要这一身体进行跳跃。
现在我们转移到火焰精灵,将此画面隐藏起来。只有当玩家飞起来的时候,我们才会看到此火焰。
现在我们已完整定义此新方法,不妨将其称作:
lh = [[LevelHelperLoader alloc] initWithContentOfFile:@”level03″]; // or level02 if you never changed it
[lh addObjectsToWorld:world cocos2dLayer:self];
if([lh hasPhysicBoundaries])
[lh createPhysicBoundaries:world];if(![lh isGravityZero])
[lh createGravity:world];// Add this
[self retrieveRequiredObjects]; // Retrieve all objects after we’ve loaded the level.
编辑和运行代码,若火焰运行时无法为我们所见,那么操作就圆满完成。
让玩家在空中飞行
现在我们重新取回加载关卡中的玩家,让其在空中飞行。
在HelloWorldScene.h类别定义中添加如下内容:
float playerVelocity;
bool playerWasFlying;
bool playerShouldFly;
通过这一操作,我们让老鼠在用户触摸屏幕时飞入空中。
在HelloWorldScene.mm中将触控功能替换成如下内容:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{playerVelocity = 0.5f;
playerShouldFly = true;
[rocketFlame setVisible:YES];
[player startAnimationNamed:@"mouseFly"];
}
////////////////////////////////////////////////////////////////////////////////
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{}
////////////////////////////////////////////////////////////////////////////////
-(void) cancelPlayerFly
{
playerShouldFly = false;
[rocketFlame setVisible:NO];
playerWasFlying = true;
playerVelocity = 0.0f;
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self cancelPlayerFly];
}
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self cancelPlayerFly];
}
在ccTouchesBegan方法中,当用户触控屏幕时,玩家就会开始飞翔,火箭的火焰就会进入我们的视野,飞翔动画就会出现在玩家精灵中。
接着我们就会在玩家的LHSprite*实例(游戏邦注:这由我们通过retrieveRequiredObjects方法取回)中启动“mouseFly” 动画。
当玩家停止触控屏幕,或取消触控时,我们就会通过调用“cancelPlayerFly”新方法让玩家停止飞翔。然后我们会继续隐藏火焰,因为玩家已不在空中飞翔。
但这远远不够。我们设定呈现玩家飞翔的时间,但我们没有落实真实的飞行操作。这需要我们在“tick”方法末尾添加如下内容:
if(playerShouldFly)
{
playerBody->ApplyLinearImpulse(b2Vec2(0, playerVelocity), playerBody->GetWorldCenter());playerVelocity += 0.01f;
if(playerVelocity > 1.5f)
playerVelocity = 1.5f;
}
这里,我们会判断玩家是否应该飞翔,及测试操作是否正确,我们在老鼠的box2d身体中施加水平线性冲量。
然后我们会将速率变得越来越快,这样玩家就仿佛真的飞离地面,而且速度越来越快。
若玩家的速度到达一定程度(1.5),我们会停止加速。
编辑和运行游戏,现在你可以通过触控屏幕让老鼠在空中飞翔。
(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦)