今天我们来介绍一款物理引擎,并基于它完成一个弹球游戏。
提到物理引擎,就是在游戏中模拟真实世界的运动,碰撞,摩擦等等。Cocos2d集成了两款常用的物理引擎:Box2D和Chipmunk,两款引擎都是在2D下工作的,最主要的区别是Box2D是基于C++开发的,Chipmunk是基于C语言开发的。
在这个教程中,我们基于Box2D物理引擎完成游戏,Xcode版本是5.1。
首先我们简要介绍一下Box2D中的核心概念:
刚体(rigid body):不会产生形变的物体,刚体上任意两点之间的距离都是固定的,在Box2D中一般用物体(body)来简单描述刚体。
形状(shape):一个严格依附于物体的2D碰撞几何结构,具有密度,摩擦和弹力这些性质。
约束(constraint):一个约束就是消除物体自由度的物理连接。例如,在2D中,一个物体有三个自由度(水平,垂直和旋转),如果我们把一个物体钉在墙上,那么这个物体就只剩下了旋转自由度。
接触约束:一个防止刚体穿透,以及用于模拟摩擦和恢复的特殊约束。这种约束不需要我们创建,Box2D会在我们创建物体时自动创建该约束。
关节(joint):一种用于把两个或多个物体固定到一起的约束。Box2D中的关节如下:
距离关节(distance joint):两个物体上各有一点,两点之间的距离固定不变
旋转关节(revolute joint):强制两个物体共享一个锚点,及所谓的铰接点。旋转关节只有一个自由度:两个物体相对旋转。
移动关节(prismatic joint):允许两个物体沿指定轴相对移动,它会阻止相对旋转,移动关节只有一个自由度。
滑轮关节(pulley joint):滑轮关节用于创建理想滑轮,将两个物体接地并彼此链接,这样当一个物体上升,另一个物体就会下降。
齿轮关节(gear joint):齿轮关节需要两个被旋转关节或移动关节接地的物体。
线性关节(line joint):跟移动关节类似,但是没有旋转约束。
焊接关节(weld joint):使两个物体不能相对运动。
鼠标关节(mouse joint):驱使物体向鼠标所在位置移动的关节。
关节限制(joint limit):限定了一个关节的运动范围。
关节马达(joint motor):一个关节马达能按照关节的自由度来驱动所连接的物体。
世界(world):一个物理世界就是物体,形状和约束相互作用的集合。Box2D支持创建多个世界,但这通常是不必要的。
下面我们来看一下最终实现的效果:
第一步我们在Xcode中创建一个Box2D的工程:
工程名命名为PinballGame,device类型选IPad。
创建完成后我们看到所有的类定义文件都是以“.mm”结尾的,我们说过,Box2D是基于C++实现了,原因就在于此,如果我们创建Chipmunk框架的工程,则所有的类定义都是在“.m”文件中。因此,我们在后面创建类的时候,需要将默认的“.m”文件修改为“.mm”文件,不然无法通过编译。
创建完成后,我们删除默认创建出来的四个文件:
HelloWorldLayer.h
HelloWorldLayer.mm
IntroLayer.h
IntroLayer.mm
在Box2D中,我们通过定义碰撞多边形来定义物体的轮廓,通过指定一系列的点(vertex)来“描述”出一个物体的外轮廓。但是在实际的编写过程中,我们创建的物体的形状可能有简单的也有复杂的,简单的物体比如上面的圆形、三角形、圆角方形这些物体,复杂物体比如我们弧形的墙体。定义这些简单物体的外轮廓相对比较容易,但是定义复杂物体的外轮廓,如果通过手工编写代码,就是一个耗时耗力的工作了,因此我们这里介绍一个实用的工具:PhysicsEditor,帮助我们“绘制”物体的外轮廓并导出文件导入到Xcode中(听起来和TexturePacker挺像的,实际上我们看这两个Tool的图标还真是如出一辙)。
打开PhysicsEditor的界面:
我们首先修改右侧Parameters中的Exporter,选择“Box2D generic(PLIST)”,我们首先修改这个选项是因为不同的导出类型会决定其他选项设置。
接着我们修改PTM-Ratio值为256。解释一下这个参数,在Box2D中使用的长度单位不是像素(Pixel)或者点(Point),而是使用米(meter)作为基本单位,PTM代表的就是pixel to meter,这个值定义了转化比例,我们这里也就是定义256像素转化为1米,那么游戏屏幕尺寸也就是2048÷256=8米。实际在Cocos2D中,PTM-Ratio只有128,因为Cocos2D中坐标系的单位是点(Point),这是为了统一Retina屏和非Retina屏,使得两种屏幕的分辨率均为1024
Points×768 Points。
在Box2D物理引擎中,定义1米到10米之间的物体模拟的物理效果最好,当然你也可以定义更大的或者更小的物体,但是模拟的精度可能就会大打折扣了。
首先我们来定义弹射器,我们将弹射器的图片拖拽到Physics
Editor最左边的列表中,图片素材:
plunger.png(我们的发射通道的有角度的,所以发射器也是倾斜的):
然后我们点击中间上方的“add
polygon”(
)来创建碰撞多边形,点击按钮后默认生成一个三角形,我们双击任意一条边即可添加一个顶点,双击任何一个节点可以删除那个节点,然后我们拖拽4个顶点对齐到弹射器的4个顶点上:
这里我们看到,有一个紫色的中间有十字的圆点,这个点是这个图形的锚点(anchor
point),与图形的锚点一致,我们可以拖拽进行重新定位,我们将锚点拖拽到弹射器的底边中点。如果更精确的控制,我们可以在右侧的参数菜单中进行编辑。
在右侧的参数菜单最下方,我们可以定义碰撞标记(collision
bit),通过碰撞标记我们定义哪些种类的物体之间能够发生碰撞,我们看到默认的碰撞标记名字为bit_i,i=0,1,2,3…,15,我们将其重命名,以便我们在后面的修改中能够区分不同种类的物体。重命名之后如下图:
接着,我们在Cat.(Categoty)中选中Plunger对应的checkbox,表明当前物体是弹射器,然后在Mask中只勾选Ball(可以利用右下角的快捷按钮进行全选,全不选,反选操作),表示我们的Plunger只会和Ball发生碰撞。
我们来继续定义墙体(Wall),这里注意一个细节:我们将右侧的墙体分成两部分,避免因为图形过于复杂影响计算速度,三张墙体素材(由于版面原因,对图片做了适当缩放):
left-wall.png:right-wall1.png:right-wall2.png:
我们将三张墙体图片拖拽到左侧列表中,这个时候,我们如果像创建弹射器的碰撞多边形那样去做,既费时间,又可能因为操作不细致导致创建的不够完美。好在Physics
Editor为我们提供了一个好用的工具,我们在“add
polygon”按钮的旁边找到一个类似魔棒的按钮“shape
tracer”(
),点击之后,出现下面这个设置界面:
这里面Tolerance决定了图形的精确程度,这个值越高,精确度越差,生成的碰撞多边形的节点数也就越少,但是计算效率就越高。另外Alpha
threshold决定了边缘的处理方式,我们的墙体图片带有一点点外发光,所以我们将这个值设置为255,忽略墙体发光的部分。Animation
phase允许针对同一个物体不同帧进行设置。
如图设置完成后,点击确定回到主界面。回到主界面中,我们将生成的碰撞多边形的边界节点向屏幕外面外拖拽一些,避免因为计算的不精确导致不必要的抖动。并且我们删掉了一些不必要的节点(右下角半圆区域内的那些节点),因为这个区域是墙体跟我们的挡板的接触区域,没有必要检测这两类物体的碰撞修改后的结果如图:
接着我们修改墙体的碰撞标记,在“Cat.”中勾选Wall,在“Mask”中勾选Ball。
左边墙体完成后,我们用类似的操作将右边的墙体的碰撞多边形创建出来(不要忘记修改碰撞标记和锚点):
同理我们绘制左右两个挡板的碰撞多边形,挡板图片:
flipper-left.png:
flipper-right.png:
创建之后修改节点如下图所示(不要忘记修改碰撞标记为Cat.:Flipper,Mask:Ball,锚点的修改如图所示):
接下来我们继续制作缓冲器(Bumper):
缓冲器图片如下:
round-bumper.png:
bumper-group-left.png:bumper-group-right.png:
bumper-left-base.png:bumper-right-base.png:
bumper-left.png:bumper-right.png:
我们分别为这些缓冲器创建碰撞多边形(尽量用最少覆盖),修改碰撞标记(均为Cat.:Bumpers,Mask:Ball),修改锚点(锚点的修改没有统一的标准,只要方便我们加入元素的时候定位即可,另外对于左右对称的缓冲器,其锚点最好在属性编辑器中修改,避免人工拖拽的误差),这里限于篇幅不一一列出了。单独说一下round-bumper和左右两个bumper-group:round-bumper我们不必用polygon tracer,用add circle来生成圆形的碰撞图形;bumper-group中由于存在多个组成部分,因此需要用多次polygon
tracer为其添加碰撞多边形。
接下来我们把最后两个元素添加上去:
ball.png:
roller.png: