前面已经讲到形状和框架的含义,它们分别是用来进行碰撞检测和物理模拟的对象。到现在我们创建的物理世界当中,还未存在任何的物体。因此接下来的内容就介绍物理世界的主角:物体(Body)。它是构成世界的主要因素,因为不仅框架,形状会依附于物体,就连后面介绍的关节也是要绑定在物体之上的。
物体定义:
Box2D引擎的物体都为刚体,它的形状不为任何外力所改变。物体上面的任何两点之间的距离都是完全不变的。它具有质量和惯性。它的运动状态由重力和受力决定。它的动态移动由线速度和角速度控制。物体分3类:
(1)静态物体(b2_staticBody) 质量为0,在模拟时,静态不可移动,就好像它具有无穷大的质量。不过,静态物理可以让用户手动的移动,它速度为0。这也就是说其移动的位置由用户控制。另外,它也不会和其他静态或者平台物体相互碰撞。它常常被用来实现游戏中的地面,边界等元素。
(2)平台物体(b2_kinematicBody)是按照固定轨迹在做运动的物体,其质量也为0,与静态物体一样,平台物体也可以被用户移动。通常情况下开发者会预设一个速度。它也不会和其他的静态或者平台物体碰撞。平台物体常常用来表示游戏中的电梯、平台、桥等等。
(3)动态物体(b2_dynamicBody)是游戏中最常见的精灵对象对应的物体,具备质量、速度、摩擦等。它参与引擎当中的碰撞检测和物理模拟。它们可以让用户手动移动,但通常它们都是受力的作用而运动。动态物体可以与物理世界中任何对象发生碰撞。
上述物体的类型都可以被施加外力(forces)、扭阵(torques)以及冲量(impuless)。但是因为静态和平台物体的质量为无穷大,所以并不会发生状态的改变。因此在游戏当中,被施加外力的经常是动态物体。
在物理世界当中,物体对象就是唯一的物种了。物体对象上会存在着框架对象,框架对象上保存着形状对象,然后物体带着框架和形状在世界中运动。Box2D中的物体总是刚体(rigid body)。这也就是说同一刚体上的两个框架,永远不会相对移动。
框架对象持有着用于碰撞检测的几何形状与用于物理模拟的密度(density)等属性。物体只是一个存在的躯壳。通常引擎要从它的框架中获得质量属性。当物体构建之后,开发者也可以改变它的质量属性。通常开发者会保存所有创建物体的指针,这样在游戏中就能查询到物体的位置,用于更新其对应游戏元素的位置。另外在不需要它们的时候,也可以使用这些指针去摧毁它们。
位置和角度(position and Angle)
位置和角度,对于一个物体来说算是最基本的属性。这也就是说存在于物理世界的物体都不会缺少这两个属性。如果开发者在构建物体的时候,没有设置这两个属性的数值,则引擎会提供初始化默认参数。在初始化直接传递位置和角度参数,这比在常见物体后再移动到某个位置或者角度更高效。
注意:不要在原点创建物体后再移动它,。如果你再原点上同事创建了几个物体,性能会很差。
物体上主要有两个属性是引擎需要的:
(1)第一个是物体的原点。框架和关节都是相对于原点而依附到物体上面的。这也就是框架和关节将会使用物体自身的坐标系来进行定位么人不是世界坐标系。
(2)第二个是物体的质心。质心由形状的质量分布决定。因为引擎中的物体是刚体,所以其质心处在均匀的位置。但是开发者可以显式的调用函数b2MassData来设置。
当开发者构造物体定义的时候,可能并不知道质心在哪里。虽然我们可以指定物体的原点,也可能会以弧度指定物体的角度,但是这都不代表质心。只有改变了物体的质量属性也就是密度,那么质心才会随之移动。在质心改变的时候,其原点以及物体上的形状和关节都不会改变,同样物体的角度也不受质心位置的影响。
在创建物体时,开发者可以在物体定义对象中设置位置和角度的初始化数值:
bodyDef.position.Set(0.0f,2.0f);//body的原点
bodyDef.angle = 0.25f *b2_pi;//弧度制下的body的角
当然,在物理引擎运转的过程中,物体的位置和角度也会随时改变。开发者可以通过物体类中的函数来改变:
void SetTransform(const b2Vec2& position,float32 angle);
阻尼(Damping)
阻尼是一种非常专业的物理概念,它是一种用于表示减少物体在世界中的速度的数值。阻尼与摩擦有所不同,摩擦仅在物体有接触的时候才会发生,而阻尼却时时刻刻阻碍着物体运动。可以把它理解为真实世界中的空气阻力。很明显它与摩擦属性之间两者是不能互相替换的,往往这两个效果需要同时作用于物体。
阻尼参数的数值范围可以是从零到无穷大。零表示没有阻尼,无穷大表示满阻尼。通常来说,阻尼的值应该在0-0.1之间,毕竟阻力太大,物体会很难前行。
注意:建议不要轻易使用线性阻尼,除非明确知道会产生怎样的效果,因为它会使物体看起来有点漂浮。
几乎所有的物体属性都可以在物体定义对象中赋予初值。下面代码是设置物体的线速度阻尼和角速度阻尼:
bodyDef,linearDamping = 0.0f;
bodyDef.angularDamping = 0.01f;
由于阻尼时时刻刻都会影响物体的运动,出于对稳定性和性能的考虑,在其值较小的时候阻尼效应几乎不依赖时间步,这是为了减少运算性能的损失。当其值较大时阻尼效应应随着时间步而改变。
休眠参数(Sleep Parameters)
模拟物体的成本是高昂的,如果物体越少,那模拟的效果就会更好。当物体停止了运动时,我们就要停止模拟它。当Box2D确定一个物体(或一组物体)已停止移动时,物体就会进入休眠状态。休眠物体只消耗很小的CPU开销。如果一个醒着的物体接触到一个休眠中的物体,那么休眠中的物体就会醒过来。当物体的关节或者触点被摧毁的时候,他们同样会醒过来。当然也可以手动的唤醒物体,通过物体定义,你可以指定一个物体是否可以休眠,或者创建一个休眠的物体:
bodyDef.allowSleep = true;
bodyDef.awake = true;
固定旋转(Fixed Rotation)
此属性也是一个与旋转有关的属性,它可以让一个刚体具有固定的旋转角,但是它在任何情况下都不会旋转。如果虚妄使用固定角度的物体,可以通过设置fixedRotation来达到目的
bodyDef.fixedRotation = true;
当其值为真,则物体的转动惯量被设置为0,这也就是说物体会保持初始化的角度,而不会发生旋转。
子弹(Bullets)
子弹也是物体中的一个很特别的属性。其实也好理解,就是将物体变成子弹一样的属性。而不是变成子弹!子弹有什么属性?子弹通常指一类速度很快而尺寸很小的物体。此类物体有一个特点,就是容易击穿其他物体。
游戏中的渲染世界,尤其自身的绘制周期。渲染引擎通常会以一定帧率绘制一系列图片。这说明渲染的过程,并不是一个连续的过程。这就是所谓的离散模拟。物理模拟中的世界同样采用了离散模拟的方法。物理世界会按照时间步的节奏来运转。如果在一个时间步内,速度快的刚体可能移动较大距离,恰好此物体拥有一个较小的体积,引擎如果没有处理好的话,那么可能看到一些物体错误的穿过了彼此。开发者并不希望此种情况的发生,因此有了子弹的特殊类型。这种现象在游戏领域中被称为隧穿效应。
既然发现了问题,就要找到应对的方法。在默认情况下,引擎会通过连续的碰撞检测(CCD)来防止动态物体穿越静态物体。这是通过扫描形状从旧位置的过程来完成的。引擎会查找扫描中新的碰撞对象,并为这些碰撞计算碰撞时间(TOI)。物体会先被移动到它们的第一个TOI,然后一直模拟到原时间步的结束。此方法应用在动态物体和静态物体之间。
一般情况下,动态物体之间不会应用连续碰撞检测的方法,因为此方法需要更多的数学运算。不过在一些游戏环境中,开发者需要在动态物体上也使用连续碰撞检测。比如《愤怒的小鸟》,开发者需要一个不会穿越其他物体的效果,如果没有连续碰撞检测,小鸟就可能会穿墙而过。
注意:连续碰撞检测也不是完美的算法,两个快速移动的物体之间也会出现穿越的问题。
考虑到上面的问题,Box2D引擎中为高速移动的物体提供了一个特别的属性。那就是将其标志为子弹(bullet)。具备了子弹属性的对象,在与动态和静态物体碰撞时都会直行连续碰撞检测(CCD)。因此,读者可以按照游戏的设计需要来决定哪些物体具备子弹的特点。如果决定一个物体应该按照子弹去处理,则可以在物体定义中设置。
bodyDef.bullet = true;
注意:子弹标记只可用在动态物体上
活动状态(Activation)
活动状态就是用来表示物体的当前状态的,如果一个物体处于活动状态,那么它就会参与引擎的物理模拟和碰撞模拟。如果为非活动状态,那么它不会参与引擎的物理模拟和碰撞检测。
注意:非活动状态和休眠状态不一样,非活动状态只能通过开发者激活,而休眠状态值需要受到碰撞就激活
在实际的代码中,读者可以创建一个非活动的物体,之后再游戏需要的情况下,再来激活它。这样做可以减少不必要的计算。代码如下:
bodyDef.active = true;
另外,一个处于非活动状态的物体,将会完全不参与物理模拟和碰撞检测,与它连接的关节也不会模拟,所以设置非活动状态一定要注意!
用户数据(User Data)
也就是存有物体属性的用户数据,它会将物体和游戏关联起来,开发者可以用一个物体对象来对应一个游戏元素。事实上所有物体的用户数据都应该指向相同的对象类型:
b2BodyDef bodyDef;
bodyDef,userData = &myActor;