Oh!Coder

Coding Life

第一章 简介

| Comments

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

1.1 关于

Box2D 是一个针对游戏中2D刚体运动模拟的程序库。程序员可以在游戏中使用此程序库让游戏中的物件以逼真的方式动起来,为游戏世界增添更多的互动性。从游戏引擎的角度来看,物理引擎仅仅是游戏中用来处理动画的系统。

Box2D使用可移植的C++语言写成。引擎中的大多数类型的命名都是以b2作为前缀。之所以这么做仅仅是希望能够避免在你的游戏引擎中产生命名冲突。

1.2 学习条件

学习本手册之前,我假设你已经熟悉了基本的物理学概念,例如,质量(mass),力(force),转矩(torque),以及冲量(impulses)。如果还没有,请通过Google搜索和Wikipedia了解相关概念。

Box2D的创建,起源于在GDC(Game Developer Conference,游戏开发者大会)中物理学讲解的部分。你可以通过box2d.org网站中下载页面找到这些教程。

既然Box2D是用C++语言编写,那么希望你已经具备了C++编程的基本经验。Box2D最好不是你的第一个C++编程项目。至少你应该能够熟练的对C++语言顺利的进行编译,链接和调试。

注意
Box2D不应该是你的第一个C++项目。在使用Box2D之前,请先学习C++编程,编译,链接以及调试。网上有很多关于C++学习的资源。

1.3 关于手册

此手册包括了Box2D的大部分API。尽管如此,并不是所有方面都包括。所以还是鼓励你多看看包含在Box2D中的testbed例子,从中可以学到更多。另外,Box2D代码的格式基于Doxygen的通用格式,所以很容易创建一个带有超链接的API文档。

此手册仅仅随着新的release版本发布。所以相对于源程序,手册版本可能会过时。

1.4 反馈与报告bug

如果你有关于Box2D的疑问或者反馈意见,请在网站论坛里留言。这里是一个不错的讨论社区。

Box2D自身的问题使用Google code project里进行跟踪。这是一个很好的跟踪问题的方法,可以保证你的问题不会迷失在论坛的某个深处的角落里。

请把bug归档和特性需求发布到这里:https://code.google.com/p/box2d/

如果你对你的提问提供了足够详细的信息,那么你可以自己查看问题是否被修复。testbed例子是一个重现问题的好地方。你可以读完文档之后查看testbed例子(译者注:testbed例子一般会随Box2D源码一起发布)。

1.5 核心概念

Box2D基于几个基本的对象开展工作。所以这里我们简要的给出这些对象的定义,更多细节会在文档后面向大家展示。

  • 形状(shape)

2D几何体对象,例如圆形或者多边形。

  • 刚体(rigid body)

无比坚硬的物体,不管他们之间的距离有多么的接近,自身都是不会发生变化。他们的坚硬程度就像钻石一样。在接下来的讨论中,我们将会把刚体统称为物体。

  • 定制器(fixture)

定制器定义了物体的形状以及物体的诸多性质,例如密度(density),摩擦系数(friction)以及恢复系数(restitution)。

  • 约束(constraint)

约束可以为物体自身移除相应自由度的物理状态关联。在2D中,一个物体包括了3个自由度(两个平移的坐标和一个旋转的坐标)。例如如果我们拿一个物体,用图钉将他订到墙上(就像钟摆一样)。此时这个物体只能绕图钉旋转,即约束为此物体移除了两个自由度。

  • 接触约束(contact constraint)

接触约束是一种特殊的约束,被设计用来防止不同刚体之间的碰撞进而产生交叉以及模拟之间的摩擦和状态还原。此约束并不需要你去创建,Box2D会为你自动创建这些。

  • 连接器(joint)

用于固定两个或更多物体在一起的约束。Box2D支持几种类型的连接器:旋转,平移,距离等等。其中一些连接器可以设置限制和马达。

  • 限制连接器(joint limit)

限制连接器可以控制一个连接动作的活动范围。比如说人类的手肘仅仅可以绕一定范围的角度进行旋转。

  • 马达连接器(joint motor)

马达连接器可以在物体连接器所允许的自由度范围内对所要做的动作进行驱动。比如说你可以用马达连接器驱动手肘的旋转。

  • 世界(world)

一个物理世界是由物体,定制器以及相互之间的约束共同组成。Box2D支持创建多个世界,但一般情况下没必要或者说是不可取的。

  • 求解器(solver)

物理世界中有一个求解器用来推进时间并且解决物体之间的接触和连接限制。Box2D中的求解器是可以进行N次操作的高性能迭代求解器,其中N是连接限制的次数。

  • 持续碰撞(continuous collision)

在离散的时间步长中,求解器及时推进物体的状态。如果不加于干涉会导致隧道效应(译者注:所谓隧道效应就是在单个时间步长内两个多边形瞬间互相穿透的情况,示意图可见4.13)。

pic

Box2D包含专门的算法来处理隧道效应。首先,碰撞算法会介入两个物体的运动,并且会发现它们的第一次撞击(first time of impace,TOI)。之后有一个分步求解器会在第一次碰撞之后移动它们的位置,然后解决碰撞问题。

1.6 模块

Box2D由三个模块组成:公用(Common),碰撞(Collision)以及动力学(Dynamics)。公用模块包括内存分配,数学库,设置。碰撞模块定义了形状,broad-phase算法,碰撞的功能/查询。最后动力学模块提供了模拟物理世界,物体,定制器(fixtures),以及连接器。

pic

1.7 单位

为了使Box2D有更好的表现,不得不使用浮点型数字的公差。为了更好的工作,这些公差被转换成了米-千克-秒(meters-kilogram-second,MKS)作为单位。特别是,Box2D中能正常工作的物体大小的范围是0.1米~10米之间。所以这就意味着,物体能正常工作的大小范围在汤罐和公交车大小之间。静态物体可以达到50米不会有大的麻烦。

作为一个2D物理引擎,使用像素作为基本单位是一件很爽的事情。倒霉的是这会导致很烂的仿真效果并且可能会出现怪异的行为。一个具有200像素长的物体会被Box2D看做是一个具有45层楼高度的大小。

注意
使用Box2D需要把单位转换成MKS(米、千克、秒)。确保动态物体的大致范围在0.1米和10米之间。当你渲染环境和场上的演员时,你需要一些额外的单位转换系统。Box2D的testbed例子中的单位转换就是通过使用OpenGL中的viewport transform进行转换的。千万不要使用像素!

在你开始创作作品之前,最好可以先考虑把Box2D里需要移动的物体当做广告牌(译者注:这里的广告牌个人的理解仅仅是用来试探单位系统用的,其实可以任意换成其他物体以方便寻找缩放因子)。让广告牌在以米为单位的系统里进行移动,之后你可以使用一个简单的缩放因子将此广告牌转换成像素坐标。以后你就可以用此像素坐标系统重置你的精灵,等等。

Box2D使用弧度作为角度。物体的旋转是以弧度存储的并且可能会无限增大。考虑到物体通常的角度,如果物体的角度大小值变的太大可以使用(b2Body::SetAngle)方法进行重置。

1.8 工厂模式和定义

在Box2D API的设计中内存管理占有重要的地位。所以当你创建b2Body或者b2Joint对象时,你需要调用b2World中的工厂方法。永远不要以其他方式分配这些类型的内存空间。

创建方法如下:

1
2
b2Body* b2World::CreateBody(const b2BodyDef* def);
b2Joint* b2World::CreateJoint(const b2JointDef* def);

相对应的销毁方法如下:

1
2
void b2World::DestroyBody(b2Body* body);
void b2World::DestroyJoint(b2Joint* joint);

当你创建一个物体或者连接器的时候,你需要提供相应的定义。定义中包括全部建立物体或者连接器的相关信息。通过这种方式,我们可以防止错误的创建,通过提供默认值,确保函数参数的数量最少,以此可以大量减少参数的访问。

由于定制器(fixtures)必须隶属于一个物体,所以它们创建和销毁需要使用b2Body中的工厂方法:

1
2
b2Fixture* b2Body::CreateFixture(const b2FixtureDef* def);
void b2Body::DestroyFixture(b2Fixture* fixture);

还有一种简短的方法可以通过形状(shape)和密度(density)参数直接创建一个定制器:

1
b2Fixture* b2Body::CreateFixture(const b2Shape* shape,float32 density)

工厂方法不会保留定义的引用。所以你可以在栈上创建定义,并把他们保留在临时资源里。

1.9 用户数据

b2Fixture,b2Body以及b2Joint类型允许你把自定义的数据作为void指针类型(俗称万能指针)附加上去。当你正在检查Box2D当中的对应的自定义的数据结构,而且你想确定Box2D如何在你的游戏引擎中牵连你附加上去的这些实体的时候会非常方便。

举例说明,一个典型的例子是将一个actor指针赋值到物体的userData上,然后把物体的指针赋值到actor的body上。这就设置了一个循环引用。如果你有actor,你就可以获取物体,如果你有物体,你可以获取actor。

1
2
3
4
GameActor* actor = GameCreateActor();
b2BodyDef bodyDef;
bodyDef.userData = actor;
actor->body = box2Dworld->CreateBody(&bodyDef);

这里举了一些你可能会用到user data情况的例子:

  • 根据碰撞结果申请销毁一个actor
  • 如果玩家在齐轴的盒子里,同时出发一个脚本事件
  • 当Box2D通知你连接器将要被销毁的时候,需要访问游戏数据结构

牢记,user data是灵活可选的并且你可以放置任何东西。尽管可以放置任何东西,但是你最好还是应该保持一致。比如说,如果你想把一个actor指针存放到物体里,那么就应该保持所有的actor指针都存放到物体里。不要试图把一个actor指针存放到物体里,又把一个称为foo的指针存放到物体。当你把actor指针类型截取成foo指针类型的时候,也许会导致程序的崩溃。

User data指针默认是NULL。

Comments