Oh!Coder

Coding Life

第七章 物体

| Comments

声明:此文章翻译自Box2D v2.2.0用户手册,仅供学习参考。

7.1 关于(About)

物体有位置和速度。你可以将力,扭矩和冲量作用于物体之上。物体可以是静态物体(static),运动学物体(kinematic)或者动态物体(dynamic)。下面是物体类型的定义:

  • b2_staticBody

在模拟环境下静态物体是不会移动的,就好像有无限大的质量。在Box2D的内部会将质量至反,存储为零。静态物体可以被用户手动移动。静态物体有零速度。静态物体不能和其它静态或运动学物体进行碰撞。

  • b2_kinematicBody

运动学物体在模拟环境中根据自身的速度进行移动。运动学物体自身不受力的作用。虽然用户可以手动移动它,但是通常情况下我们会设置它的速度来进行移动。运动学物体的行为就像是有无限大的质量,尽管如此,在Box2D内部还是会对运动学物体的质量至反设置为零。运动学物体不能和其它静态或运动学物体进行碰撞。

  • b2_dynamicBody

动态物体可以进行全模拟。它们可以被用户手动移动,但是通常情况下会根据受力进行移动。动态物体可以和任何物体发生碰撞。动态物体总是拥有有限的非零质量。如果你尝试设置动态物体的质量为零,它会自动设置一个1千克质量的物体。

物体是定制器的骨架。物体带着定制器在世界中运动。在Box2D中物体总是刚体。这也就意味着附加在同一个物体上的两个定制器之间永远都不会产生相对移动。

定制器有可碰撞的几何形状和密度。一般来说,物体需要从定制器那里获得质量属性。即便这样,当物体创建之后你也可以修改质量属性。接下来我们将会对此进行讨论。

通常你会一直保持指针指向所有你创建的物体。这样可以查询物体的位置,以更新图形实体的位置。同时如果你不再使用的时候你也可以通过指针对它们进行销毁。

7.2 物体定义(Body Definition)

创建物体之前你必须创建一个物体的定义(b2BodyDef)。物体的定义(b2BodyDef)持有创建和初始化一个物体的数据。Box2D会把数据从物体的定义(b2BodyDef)拷贝出来;不会用指针一直指向物体的定义(b2BodyDef)。这就意味着你可以使用一个物体的定义(b2BodyDef)来创建多个物体。

让我们复习一些物体的定义(b2BodyDef)中的关键成员。

  • 物体类型(Body Type)

正如本章开始所描述的,有三种不同的物体类型:静态(static),运动(kinematic),以及动态(dynamic)。你需要在创建的时候指定物体类型,因为如果以后再修改的话代价会很高。

1
bodyDef.type = b2_dynamicBody;

设定物体类型是强制性的。

  • 位置和角度(Position and Angle)

物体的定义给了你在创建物体的时候初始化物体位置的机会。这比在世界原点创建物体的同时再移动到指定位置要高效。

警告
不要在世界原点创建一个物体后再进行移动。如果你在世界原点同时创建多个物体,这么做性能将会很差。

一个物体有两个主要让人感兴趣的点。第一个是物体的原点。定制器和连接器附加到物体上的时候是相对于物体的原点进行关联的。第二个让人感兴趣的点 ,是物体的质心。质心由附加到形状上的质量分布来决定,或者也可以使用b2MassData进行显示的设置。Box2D中的很多内部计算都是使用质点。例如b2Body为质心存储的线速度。

当你创建物体的定义(body definition)的时候,你也许不知道质心在什么地方。因此你可以指定物体的原点。你也可以用弧度指定物体上不会受质心影响的旋转度数。如果随后你改变了物体的质量,那么物体上相应的质心的位置做出相应的调整,但是原点以及附加在物体上的形状和连接器也不会移动位置。

1
2
bodyDef.position.Set(0.0f, 2.0f);//the body’s origin position.
bodyDef.angle = 0.25f * b2_pi;  //the body’s angle in radians.
  • 阻尼(Damping)

阻尼用来降低世界中物体的速度。阻尼和摩擦不同,因为摩擦仅仅和接触同时发生。阻尼不是摩擦的一个替代者,并且这两个效果可以被同时使用。

阻尼系数可以设置为0到无穷大,0意味着没有阻尼,无穷大意味着全阻尼。通常来说,你可以在0到0.1之间来设置阻尼系数。一般情况下我不会使用线性阻尼因为这会使物体看起来有点飘。

1
2
bodyDef.linearDamping = 0.0f;
bodyDef.angularDamping = 0.0f;

阻尼类似于稳定性和性能。当阻尼的值很小时几乎不会受时间步长的影响。当阻尼的值变大时将会收到时间步长的影响。如果你使用一个固定时间步长 (建议) 这就不是问题了。

  • 重力因子(Gravity Scale)

在单个物体上你可以使用重力因子来调整物体的重力。不过要当心,增加重力因子会降低稳定性。

1
2
//Set the gravity scale to zero so this body will float
bodyDef.gravityScale = 0.0f;
  • 睡眠参数(Sleep Parameters)

睡眠是什么意思?对物体进行模拟是需要高昂的开销,所以在保证正常模拟的情况下开销越低当然越好。当一个物体停止运动之后我们希望能够停止对它的模拟。

当Box2D判定一个物体(或一组物体)停止运动时,物体会被设置成睡眠状态从而占用非常低的CPU。如果一个非睡眠状态的物体与一个睡眠状态的物体相撞,那么睡眠当中的物体会立刻醒过来。如果附加在物体上的连接器和接触被销毁的时候它们也会醒来。你也可以手动唤醒一个物体。

通过物体的定义(b2BodyDef)可以指定一个物体是否能睡眠或者创建一个睡眠的物体。

1
2
bodyDef.allowSleep = true;
bodyDef.awake = true;
  • 固定旋转(Fixed Rotation)

你也许想要某个具有固定旋转角的刚体角色。即便有相应的负载也不会旋转。你可以设置固定旋转角来达到这个目的:

1
bodyDef.fixedRotation = true;

固定旋转标记把转动惯量逐渐递减为零。

  • 子弹(Bullets)

游戏模拟中某个帧率下通常会生成序列动画。这叫做离散模拟。在离散模拟中刚体在一个时间步长内移动一个较大的范围。如果物理引擎不能够处理这个较大的范围,你会看到一些物体会错误的彼此穿透现象,这种效果称为隧道效应(tunneling)。

默认情况,Box2D使用持续碰撞检测(continuous collision detection,CCD)来防止在发生隧道效应(tunneling)时动态物体穿透静态物体。这是通过对形状从旧位置到新位置之间不断的扫描来完成的。引擎为了模拟更好的碰撞效果,会通过扫描和计算撞击时间(time of impact,TOI)来寻找新的碰撞。通常物体会按照第一个撞击时间(TOI)进行移动,在余下的时间步长内停止移动。

一般情况下不会在两个动态物体之间使用持续碰撞检测(CCD)。这么做的原因是为了保持效率。但是在一些游戏场景中你需要在动态物体之间使用持续碰撞检测(CCD)。例如,你想用子弹高速射击一摞动态类型的砖头。如果不使用持续碰撞检测(CCD),那么子弹会产生隧道效应(tunnel)从而穿越砖头。

Box2D中快速移动的物体可以被看做子弹。子弹跟静态与动态物体之间的碰撞需要使用持续碰撞检测(CCD)。在你的游戏设计中,你需要判断哪些物体可以看做子弹。如果你决定了哪个物体像子弹一样对待,使用如下的设置:

1
bodyDef.bullet = true;

子弹标志仅仅能够影响动态物体。

Box2D顺序执行持续碰撞,所以子弹有可能会丢失快速移动的物体。

  • 活动状态(Activation)

或许你想创建一个不属于碰撞或者动态的物体。除了不会被物体和物体定制器所唤醒以外,还不会被放置到broad-phase算法当中,活动状态(Activation)有点类似于睡眠状态。这就意味着物体将不会参与碰撞,光线投射(ray casts),等等。

你可以创建一个非活动状态的物体,随后可以重新激活它。

1
bodyDef.active = true;

连接器或许会连接一个非活动物体。这些连接器将不会被模拟。你要注意的是,当你激活一个物体时,它的连接器不会马上弯曲。

  • 用户数据(User Data)

用户数据是一个void类型的指针(译者注: C++中有时也称为万能指针)。这就为你提供了一个钩子来连接你的应用程序中的对象到物体中。你应该考虑让所有的用户数据(user data)来连接相同类型的数据。

1
2
b2BodyDef bodyDef;
bodyDef.userData = &maActor;

7.3 物体工厂(Body Factory)

物体的创建和销毁可以通过world类型中提供的物体工厂来实现。这可以让world用高效的分配器来创建物体并把物体添加到world数据结构当中。

物体可以被当成动态或静态取决于质量属性。两种物体类型的创建和销毁方法都是一样的。

1
2
3
4
b2Body*  dynamicBody = myWorld->CreateBody(&bodyDef);
... do stuff ...
myWorld->DestroyBody(dynamicBody);
dynamicBody = NULL;

警告
永远都不要使用new和malloc来创建物体。世界将不会知道这个物体并且物体将不会正确初始化。

在其它物体的影响下静态物体是不会移动的。虽然你可以手动移动静态物体,但是你必须小心,不要在两个或多个静态物体之间挤压动态物体。如果你移动一个静态物体,那么摩擦力将不会正常工作。静态物体永远不会和静态物体或者运动学物体(kinematic)进行碰撞。附加多个形状到一个静态物体上要比创建多个物体每个物体都附加一个形状高效。在内部,Box2D设置质量并把静态物体的质量至零。这些都是在数学库中做好的,所以大部分算法都不需要特殊对待静态物体。

Box2D不会保留一个物体定义(body defini)的引用或者其它任何数据的引用(用户数据(user data)除外)。所以你可以创建临时的物体定义并且可重用相同的物体定义。

Box2D允许你通过删除b2World对象来避免直接删除物体,b2World会为你完成清理工作。尽管如此你也要格外小心你的游戏引擎中带有空指针的物体。

当你销毁一个物体,附加在物体上的定制器和连接器会自动销毁。这对你管理形状和连接器指针有重要的含义。

7.4 使用物体(Using a Body)

创建完一个物体后,你可以对它做很多操作。包括设置质量属性,获取位置和速度,施加作用力,转换点和向量。

  • 质量数据(Mass Data)

每一个物体都有质量(标量),质心(2维向量),转动惯量(标量)。对于静态物体来说质量和转动惯量设置成零。当一个物体有固定转动的时候,它的转动惯量为零。通常情况下,物体的质量属性会随着附加到这个物体上的定制器而自动确定。当然你也可以在运行的时候调整物体的属性。当你在特殊游戏场景中需要改变物体的质量时,通常可以这样做:

1
void SetMassData(const b2MassData* data);

直接改变物体质量后,也许希望再次恢复定制器中所指定的质量。你可以这样做:

1
void ResetMassData();

可以通过如下方法获得可用的物体质量数据:

1
2
3
4
float32 GetMass() const;
float32 GetInertia() const;
const b2Vec2& GetLocalCenter() const;
void GetMassData(b2MassData* data) const;
  • 状态信息(State Information)

关于物体的状态有很多方面。你可以通过以下方法来有效的访问状态数据:

1
2
3
4
5
6
7
8
9
10
11
12
void SetType(b2BodyType  type);
b2BodyType  GetType();
void SetBullet(bool  flag);
bool IsBullet() const;
void SetSleepingAllowed(bool  flag);
bool IsSleepingAllowed() const;
void SetAwake(bool  flag);
bool IsAwake() const;
void SetActive(bool  flag);
bool IsActive() const;
void SetFixedRotation(bool  flag);
bool IsFixedRotation() const;
  • 位置和速度(Position and Velocity)

你可以获取一个物体的位置和角度。当渲染你的游戏角色的时候这很常见。虽然在Box2D的模拟环境中很少自己设置位置,但是你仍然可以进行手动设置。

1
2
3
4
bool SetTransform(const b2Vec2& position, float32 angle);
const b2Transform& GetTransform() const;
const b2Vec2& GetPosition() const;
float32 GetAngle() const;

你可以获取本地坐标或世界坐标下的质心位置。Box2D中大部分的内部模拟都使用质心。即便这样,一般情况下你也不需要访问它。相比而言,你会经常使用物体变换。举个例子,你也许有一个正方形物体。物体的原点在四个角中的一个角上,而质心位于正方体的中心上。

1
2
const b2Vec2& GetWorldCenter() const;
const b2Vec2& GetLocalCenter() const;

你可以访问线速度和角速度。线速度是相对与质心而言的。因此,如果质量属性改变了那么线速度也会随之改变。

Comments