第一部分
第二部分


Object是个简单类,源自GB2Sprite。这意味着它本身具有物理和图像
性能。
为简化我们的工作,我借鉴物理子画面及子画面表单图像元素的命名方式命名音效文件。这让我们得以在必要时候通过物件的名称创建正确的模型和音效。
为顺利达成此目标,我们需要名为objName的属性——objName被输进initWithObject选择器,成为类的的一部分。
RandomObject是创建随机物件的工厂模式,会在内容完成后呈现适当的物件名称。
将此代码粘帖至Object.h:
#pragma once
#import "cocos2d.h"
#import "GB2Sprite.h"
@interface Object : GB2Sprite
{
NSString *objName; // type of the object
}
@property (retain, nonatomic) NSString *objName;
-(id) initWithObject:(NSString*)objName;
+(Object*) randomObject;
@end

现在就切换到Object.mm内容。我们首先要应对若干必要的输入内容,综合objName的属性。
#import "Object.h"
#import "GB2Contact.h"
#import "SimpleAudioEngine.h"
#import "GMath.h"
@implementation Object
@synthesize objName;

GMath.h包含若干助手功能—–例如,gFloatRand,这是个远程浮点随机数字生成器。
接着就是添加init选择器,示例物理物件。你可以采用物件的名称,因为它们将呈现物理模型。至于子画面窗口标识,你就需要添加文件名称和.png扩展名。在属性中存储objName——在碰触检测中我们将需要通过它呈现音效。
-(id) initWithObject:(NSString*)theObjName
{
self = [super initWithDynamicBody:theObjName
spriteFrameName:[NSString stringWithFormat:@"objects/%@.png", theObjName]];
if(self)
{
self.objName = theObjName;
}
return self;
}

在dealloc选择器中,我们只需释放objName属性,调用超级dealloc:
-(void) dealloc
{
[objName release];
[super dealloc];
}

下个要添加的内容就是我们的静态工厂模式,这将生成随机物件。我决定在此采用简单的逻辑陈述。原因是,我们稍后需要给香蕉和香蕉皮创建特殊类。这两个物件只有一个路径,而其他3个物件则各有3个路径,这样它们就有望频繁出现。
逻辑结构效果显著。 你可以通过有命名的数组保存CPU周期,但程序一秒会被访问一次,因此我们的方式更胜一筹。
+(Object*) randomObject
{
NSString *objName;
switch(rand() % 18)
{
case 0:
objName = @"banana";
break;
case 1:
objName = @"bananabunch";
break;
case 2: case 3: case 5:
objName = @"backpack";
break;
case 6: case 7: case 8:
objName = @"canteen";
break;
case 9: case 10: case 11:
objName = @"hat";
break;
case 12: case 13: case 14:
objName = @"statue";
break;
default:
objName = @"pineapple";
break;
}
return [[[self alloc] initWithObject:objName] autorelease];
}

最后,给文件添加结束内容:
@end
现在我们把话题转换至GameLayer.h,直接在#import陈述之后给物件类添加前置声明:
#import "cocos2d.h"
@class Object;

给GameLayer类添加这些新元素:
ccTime nextDrop; // Will keep the time until the next drop.
ccTime dropDelay; // The delay between two drops.
Object *nextObject; // Contains a reference to the next item to drop.

然后切换到GameLayer.mm,在文件开始添加Object.h输入内容。同时输入GMath.h:
#import "Object.h"
#import "GMath.h"

在init选择器末端初始化新变量,通过画面更新内容更新选择器:
nextDrop = 3.0f; // drop first object after 3s
dropDelay = 2.0f; // drop next object after 1s
[self scheduleUpdate];

最后一行的内容是,协助各窗口调用名为“update” 的选择器。此选择器的参数就是距选择器上次被访问所流逝的时间。在init后添加更新方式:
-(void) update: (ccTime) dt
{
// 1 - drop next item
nextDrop -= dt;
if(nextDrop < = 0) { // 2 - do we have the next object? if(nextObject) { // 3 - set the object as active, making it drop [nextObject setActive:YES]; // 4 - set next drop time nextDrop = dropDelay; // reduce delay to the drop after this // this will increase game difficulty dropDelay *= 0.98f; } // 5 - create new random object nextObject = [Object randomObject]; // but keep it disabled [nextObject setActive:NO]; // 6 - set random position float xPos = gFloatRand(40,440); float yPos = 400; [nextObject setPhysicsPosition:b2Vec2FromCC(xPos, yPos)]; // 7 - add it to our object layer [objectLayer addChild:[nextObject ccNode]]; } }

让我们逐一查看上述代码。
1. 此截面降低间隔时间,因为更新内容最后才由nextDrop导出。若nextDrop的数值降至0以下,我们就该制作新的降落道具。
2. 若nextDrop寿命耗尽,截面就会查看nextObject是否有储备物件。
3. 若是如此,这里应将其设置成活跃模式。将物件设置成活跃模式让我们能够对物件进行物理引擎控制。
4. 此截面设置下次降落时间同当前降落的间隔,从将降落延迟降低2%,促使游戏因每次的降落道具而变得更具难度。
5. 此截面通过我们在Object中的工厂模式创造新降落物件,将物件设置成非活跃状态,阻止物件继续降落及参与物理模拟情境中。
6. 截面给予物件随机位置。屏幕的宽度是480pt(dApps注:Cocos2d采用点数作为基本单位,1 pt相当于“陈旧”设备的1像数,视网膜显示设备的2像数)。代码确保物件的位置处于40-440点数之间。截面还将Y轴的起始位置设置成400,这样物件就会从屏幕顶端开始屏幕之外的运作。b2Vec2FromCC方式被用于在点坐标中制作box2db2Vec2。B2Vec2FromCC将Cocos2d的点数转变成Box2d的以米为单位的数值。
7. 最后,截面在物件图层中添加物件。
编辑,然后运行内容!你应看到类似于如下画面的内容,但当然其中所涉及的道具完全不同。道具看起来有些模糊,因为调试图样依然处于运行中:
画面略显模糊

画面略显模糊


通过在GameLayer.mm中给相关代码行添加注释,退出调试图层:
// [self addChild:[[GB2DebugDrawLayer alloc] init] z:30];
现在你的游戏会看起来好很多:
清晰画面

清晰画面


查看道具如何跳出屏幕,分布于左右两侧?游戏的目标是让道具堆积起来,所以我们需要在屏幕两侧添加墙体。
要进行此操作,我们只需创建两个新的GB2Node物件。它们会跳出屏幕,分布于左右两侧。
由于GB2Nodes会自己添加至当前物理模拟情境中,所以你无需手动添加这些内容。它们不会以画面形式呈现,所以只要创建这些内容就可以。
将这些代码行添加至GameLayer.mm的init中,就在平面图层之后:
GB2Node *leftWall = [[GB2Node alloc] initWithStaticBody:nil node:nil];
[leftWall addEdgeFrom:b2Vec2FromCC(0, 0) to:b2Vec2FromCC(0, 10000)];
GB2Node *rightWall = [[GB2Node alloc] initWithStaticBody:nil node:nil];
[rightWall addEdgeFrom:b2Vec2FromCC(480, 0) to:b2Vec2FromCC(480, 10000)];

然后编辑和运行内容。查看物件如何在墙面空间范围中呈现?
保存物件

保存物件


这看起来很不错,但依然缺少某些元素。我觉得物件在相互碰触时应该发出一些声音。难道你不这么觉得?
我不希望物件持续发出声音,而只是在它们以一定速度击中某物的情况下。所以我们将查看物件的速度,只在它们以相当快的速度碰触某物时发出声音。
将此代码添加至Object.mm:
-(void) beginContactWithObject:(GB2Contact*)contact
{
b2Vec2 velocity = [self linearVelocity];
// play the sound only when the impact is high
if(velocity.LengthSquared() > 3.0)
{
// play the item hit sound
// pan it depending on the position of the collision
// add some randomness to the pitch
[[SimpleAudioEngine sharedEngine] playEffect:[NSString stringWithFormat:@"%@.caf", objName]
pitch:gFloatRand(0.8,1.2)
pan:(self.ccNode.position.x-240.0f) / 240.0f
gain:1.0 ];
}
}

我们需将上述方式命做beginContactWithObject,这样当两个物体发生碰触时,GBox2D就会自动调用内容。
linearVelocity方式让我们能够把握物件的速度。调用物件Length或LengthSquared则呈现速度的具体数值。比较恒定数值时,我倾向采用LengthSquared,因为它不会要求计算数值的二次根。
我们将通过调用SimpleAudioEngine的playEffect方式制作音效。首个参数就是音频文件的名称。
为简化我们的工作,我给予音效同物件和子画面相同的名称。所以你可以运用我早前存储的objName内容,从中获得正确的音频文件。通过NSString在名称上附上.caf。
通过运用数值是0.8和1.2的gFloatRand调节音高(pitch)。这将呈现音高有所变化的音效。若所有物件都发出相同声音,我们定会觉得有些乏味。
这里我们要运用的最后一个策略是将声音来源嵌入物件的位置。Pan允许的数值范围是-0.1到1.0间。物件的X轴位置将处于0-480间,所以减去240,除以240你就会得到此范围值。
若你希望,在无需重写代码的情况下,物件能够在击中地面时能够发出声音,就添加下述方式,这会将“物件–平面”碰触发送至Object.mm:
-(void) beginContactWithFloor:(GB2Contact*)contact
{
[self beginContactWithObject:contact];
}

然后进行编辑和运作,查看物件如何降落,同时在发生碰触时形成声音。
但我们的游戏目前还有一点令我不是很满意。首个道具在降落后就停留在半空中,但声音引擎已经预置。
只要我们添加主题音乐,这就不成问题,因为音乐会立即将声音引擎初始化。但若你现在想要解决此问题,首先要在GameLayer.mm顶部添加输入陈述:
#import "SimpleAudioEngine.h"
然后在GameLayer的init选择器中添加SimpleAudioEngine共享物件调用:
[SimpleAudioEngine sharedEngine];
上述执行方式给所有降落物件的碰撞带来相同基本音效。若你有雄心壮志,可以根据物件碰撞类型制作不同音效(dApps注:也就是说,基于食堂撞击食堂,制作一种音效;根据香蕉击中食堂制作另一种音效……)
另一提高此代码的方式就是通过碰撞速度变化所呈现的音效。

下步操作是什么?

现在已是文章尾声,当前内容保存于3-DroppingObjects文件夹的源代码zip文件中。