这次我们利用Box2D物理引擎来制作一款类似于下楼梯的小游戏,关于Box2D物理引擎的介绍,可以参考我博客中的Box2D v2.3.0 用户指南进行学习。

我们先来看一下最终效果:

小游戏架构 小游戏 设计_box2d

游戏的组成元素有:

滚动背景(左右两个宽度为15,与屏幕等高的部分),上升的台阶(或者说是楼梯),滚动的球,分数。

滚动背景的制作我们不做介绍了,制作方法可以参考FlaggyBird的滚动背景的制作方法(手把手教你制作那个风靡的flappy bird小游戏),背景大部分面积填充的是深蓝色(静止),用平铺的方法平铺两个小的纹理:

小游戏架构 小游戏 设计_小游戏架构_02

 

小游戏架构 小游戏 设计_box2d_03

我们先来介绍使用Box2D来创建台阶:

首先创建StepBlock类,注意我们在创建类之后默认会创建出.h和.m文件,因为Box2D是基于C++的框架,所以需要我们手动将.m文件的扩展名更改为.mm。StepBlock类声明如下:

#import "CCNode.h"
 #import "CCPhysicsSprite.h"
 #import "Box2D.h"
 #import "TagDefinitions.h"
  
 @interface StepBlock : CCNode{
     float screenWidth;
     float screenHeight;
     CCPhysicsSprite* shape;
     b2Body* body;
     int tagSave;
 }
  
 - (id)initWithY:(float) y;
 - (id)initWithPos:(CGPoint) pos andType:(StepBlockTypes)type;
 - (void)resetWithY:(float) yPos;
 - (BOOL)moveUp:(float) length lowestStepY:(float) lowestStepY;
 - (void)resetWithPos:(CGPoint) pos andType:(StepBlockTypes) type;
  
 @end

类中我们定义了screenHeight和screenWidth用来存放屏幕长宽,避免反复获取影响效率。同时我们定义了shape,来存储StepBlock(后面简称Block)的图形(CCPhysicsSprite和CCSprite用法相同,只不过可以指定b2Body作为其成员,这里我们也可以使用CCSprite,不影响效果)。另外又定义了一个body对象,用来存储StepBlock的刚体对象。tagSave我们后面会用到,这里先忽略这个成员。

另外我们定义了一系列的方法,initWithY用来初始化StepBlock,所以这个初始化方法只需要指定y值即可。initWithPos:andType:方法用一个确定的初始位置和Block类型来初始化,这里Block类型我们接下来会介绍,resetWithY方法重置Block的类型和位置,moveUp用来将Block上移,resetWithPos:andType:用确定的位置和Block类型来重置Block。我们看到头文件中的“TagDefinitions.h”,这个头文件中包含了下面的枚举定义和预编译指令:

#ifndef Steps_TagDefinitions_h
 #define Steps_TagDefinitions_h
  
 #define PTM_RATIO 100
  
 typedef enum{
     StepBlockNormal = 0,
     StepBlockConcave,
     StepBlockConvex,
     StepBlockHighLow,
     StepBlockLowHigh,
     StepBlockRough,
     StepBlockSlideLeft,
     StepBlockSlideRight
 } StepBlockTypes;
  
 typedef enum {
     ForceNone,
     ForceLeft,
     ForceRight
 } ForceType;

#endif

这里PTM_RATIO定义了Box2D中“米”和“像素”的转化比例,StepBlockTypes定义了Block类型的枚举,ForceType定义了对小球的施力方向枚举,我们后面会用到。

下面是用到的不同类型Block的贴图:

step_high_low.png:

小游戏架构 小游戏 设计_小游戏架构_04

step_low_high.png:

小游戏架构 小游戏 设计_box2d_05

step_concave.png:

小游戏架构 小游戏 设计_box2d_06

step_normal.png:

小游戏架构 小游戏 设计_IOS_07

step_rough.png:

小游戏架构 小游戏 设计_IOS_08

step_slide_left.png:

小游戏架构 小游戏 设计_游戏_09

step_slide_right.png:

小游戏架构 小游戏 设计_ide_10

step_convex.png:

小游戏架构 小游戏 设计_ide_11

将这些图片导入到工程资源中。接着我们来看StepBlock.mm中的类定义。首先定义一个静态数组,用来存放Block的类型名:

static NSArray* stepBlockTypes = [NSArrayarrayWithObjects:@"step_normal", @"step_concave",@"step_convex",
@"step_high_low",@"step_low_high", @"step_rough",@"step_slide_left", @"step_slide_right", nil];

接着定义下面的方法,用来设置Block的精灵对象:

- (void)initShape:(NSString*) shapeName{
     shape = [CCSprite spriteWithFile:shapeName];
  
     [self addChild:shape];
 }
下面的方法用来初始化screenHeight和screenWidth属性:
 - (void)initProps{
     //screen dimensions
     CGSize screenSize = [[CCDirector sharedDirector]winSize];
     screenHeight = screenSize.height;
     screenWidth = screenSize.width;
 }
接着我们定义resetWithPos:andType:方法:
 - (void)resetWithPos:(CGPoint) pos andType:(StepBlockTypes) type{
     float xPos = pos.x;
     xPos = max(65,xPos);                 // 15(wall width) + 50(half the step block width)
     xPos = min(xPos, screenWidth -65);    // 65 = 100(step block width) + 15(wall width) - 50(halfthe step block width)
     
     if (body) {
         [[GameLayer gameLayer]sharedWorld]->DestroyBody(body);
     }
     b2BodyDef bodyDef;
     bodyDef.type = b2_staticBody;
     bodyDef.userData = [NSNumbernumberWithInteger:tagSave];
     body = [[GameLayer gameLayer]sharedWorld]->CreateBody(&bodyDef);
     
     [self generateShapeAndFixture:ccp(xPos, pos.y)withType:type];
 }

在计算xPos的时候我们进行了一些判断,防止Block的位置超出左右的墙壁。并且由于我们在reset的时候会随机更换Block的类型,所以会先将body清除掉(destroy),但是第一次初始化的时候body并没有定义,所以添加了if语句进行了判断。这里用到的GameLayer类是游戏的主场景(CCScene)的层(CCLayer),我们下面会介绍,sharedWorld是我们这个游戏里的世界对象。在清除了body对象后,我们用b2BodyDef来定义body,定义的类型是静态物体(staticBody),因为我们不需要Block像小球一样受力运动,他们只需要匀速上升即可。接着我们定义了body的userData,将tagSave存储进去,这个值在我们后面记分(score)的时候会用到。接着使用CreateBody来创建刚体,然后调用generateShapeAndFixture:withType:方法。方法定义如下:

- (void)generateShapeAndFixture:(CGPoint) pos withType:(int) type{
     NSString *shapeName = [stepBlockTypesobjectAtIndex:type];
     shape.texture = [[CCTextureCachesharedTextureCache] addImage:[NSString stringWithFormat:@"%@.png",shapeName]];
     shape.position = pos;
     [[GB2ShapeCache sharedShapeCache]addFixturesToBody:body forShapeName:shapeName];
     shape.anchorPoint = ccp(0.5f, 0.5f);
     b2Vec2 newPos;
     newPos.Set(pos.x/PTM_RATIO, pos.y/PTM_RATIO);
     body->SetTransform(newPos, 0);
 }

这个方法刷新了形状的纹理(texture),然后使用GB2ShapeCache类的addFixturesToBody:forShapeName:来初始化物体的fixture。GB2ShapeCache类可以在PhysicsEditor(简称PE)的dmg文件中的“\Loaders\generic-box2d-plist”路径下找到,关于PE的用法,请参考手把手教你使用PhysicsEditor来辅助制作Box2D刚体。将PE中创建导出的plist文件导入到Box2D工程中,使用下面的语句导入:

[[GB2ShapeCache sharedShapeCache]addShapesWithFile:@"steps-elements.plist"];

其中“steps-elements.plist”为PE生成的文件。

SetTransform方法用来重置物体的位置和角度,第一个参数是位置,第二个是角度,这里Block为水平放置,所以角度为0。

定义好之后,我们继续添加下面两个reset方法,只是参数上有所不同,复用上面的方法:

- (void)resetWithY:(float) yPos{
     float xPos = CCRANDOM_0_1() * (screenWidth -100);  // 100 is the width of the step block
     [self resetWithPos:ccp(xPos, yPos)];
 }
  
 - (void)resetWithPos:(CGPoint) pos{
     int type = rand() % 8;
     [self resetWithPos:posandType:(StepBlockTypes)type];
 }
最后,我们定义好Block的初始化方法:
 - (id)initWithY:(float) y{
     if (self = [super init]) {
         [self initProps];
         [selfinitShape:@"step_normal.png"];
         [self resetWithY:y];
     }
     
     return self;
 }
  
 - (id)initWithPos:(CGPoint)pos andType:(StepBlockTypes)type{
     if (self = [super init]) {
         [self initProps];
         NSString *shapeName =[NSString stringWithFormat:@"%@.png", [stepBlockTypesobjectAtIndex:(int)type]];
         [selfinitShape:shapeName];
         [self resetWithPos:posandType:type];
     }
     
     return self;
 }

这里我们还差一个moveUp的方法,定义如下:

- (BOOL)moveUp:(float) length lowestStepY:(float) lowestStepY{
     float y = shape.position.y + length;
     float x = shape.position.x;
     BOOL isReallyMovedUp = true;
     if (y > screenHeight + 15) {
         y = lowestStepY;
         isReallyMovedUp = false;
         [self resetWithY:y];
     }
     shape.position = ccp(x, y);
     b2Vec2 newPos;
     newPos.Set(x/PTM_RATIO, y/PTM_RATIO);
     body->SetTransform(newPos, 0);
     
     return isReallyMovedUp;
 }

使用SetTransform方法来移动刚体的位置,注意的一点是,当Block移动到屏幕外时,并不清理掉这个Block,而是重置Block并将其移动到最下方的Block的下面去,lowestStepY即是最下方的Block的y坐标。返回值isReallyMovedUp用来标记Block是否被重置到了最下方。

到这里StepBlock类就完成了定义。