【技术讨论】从弹弹堂说起,如何用2D物理引擎编写一个游戏<一>20111105 10:36

转自:http://www.cnblogs.com/zcsor/archive/2010/09/09/1822466.html

呃,小儿科拿出来讨论一下。

巨注意:不是让大家去写外挂哦!纯属技术讨论。

先写一部分,测试是用GDI+写的,非常的简陋,而且整体只是一个雏形,抛砖引玉。

【一、物理引擎】

所谓物理引擎,是通过为刚体赋予真实的物理属性的方式来计算它们的运动、旋转、碰撞等等的结果。也许你曾经编写过台球游戏,使用了大量的类似于碰撞检测,线相交和折返等等数学方法来解决问题,但那不是对真实世界的物理模拟,虽然可以使你的游戏看起来比较真实,但当游戏需要比较复杂的物体碰撞、滚动、滑动或者弹跳的时候(比如赛车类游戏或者保龄球游戏),通过编程的方法就比较困难了。而物理引擎则使用动量、扭矩等用高等数学手段来模拟真实物体,这将得到更真实的效果且使我们的编码更加容易。当然好的物理引擎允许有复杂的机械装置,像球形关节、轮子、气缸或者铰链。有些也支持非刚性体的物理属性,比如流体。

著名的物理引擎有:

Lagoa Multiphysics

Physx

Bullet

Havok

等等,它们多提供3D特性,用于在三维空间内来模拟物理运动——就像星际2、暗黑3使用的就是Havok。这些物理引擎当然可以应用到2D游戏当中,但它们的运算量将会大于2D引擎。很多物理引擎提供了图形界面功能,AI功能等诸多游戏要素,而且非常出色!但我们这里还是从比较简单的BOX2D入手,说它简单是它只负责计算物理运动(当然它也非常强大——除了不支持流体等)进而只提供了很少的一些接口,这有利于我们专注于物理引擎的使用。

出处:http://www.cnblogs.com/zcsor/

【二、BOX2D】

Box2D 是一个用于游戏的 2D 刚体仿真库。程序员可以在他们的游戏里使用它,它可以使物体的运动更加可信,让世界看起来更具交互性。从游戏的视角来看,物理引擎就是一个程序性动画(proceduralanimation)的系统,而不是由动画师去移动你的物体。你可以让牛顿来做导演。Box2D 是用可移植的 C++ 来写成的。引擎中定义的大部分类型都有 b2 前缀,希望这能消除它和游戏引擎之间的名字冲突。这就是BOX2D的概况。在学习如何使用时,我们先认识几个必要概念和它们存在的意义:

A、我想,质量,力,扭矩和冲量大家都非常清楚了,呃,如果不清楚那可以去百科恶补一下……

B、刚体:即说这个物体会在碰撞、挤压等力的作用后不变形。你应该注意我这个描述。(当然如果你愿意,可以告诉BOX2D改变它的任何属性)

C、形状:这决定了物体的外观以及碰撞等发生的位置,当然,还有摩擦的大小等

D、自由度:这个概念也许比较深奥,但在这里,我们只需要知道,2D物理引擎只模拟物体在一个平面上的运动——即你的游戏将是一个平面游戏,即使你用一些手段将地面和墙壁以及天空分开。。。其实不必在意,这种伎俩已经很少有游戏在用了,而且我相信当XNA向VB.NET微笑时,那些手段会被赶出你的脑袋。

E、世界:我们应该把所有物体限定在一个范围内——当物体跑出去了,引擎会抛出一个错误并停止对该物体进行演算,所以,我们的世界通常有足够大……

F、其他:当然指关节,齿轮等,这些与我们入门毫无关系。甚至在我的代码里也采用了一些卑劣的手段避开使用回调方式来处理物体的接触——这将使代码更“入门”,而且也足够应付这么简单的情况

【三、创建一个世界】

首先,确保你引用了BOX2D引擎。如果没有可以在其网站下载:http://www.box2d.org/

接下来我们创建一个世界,这是使用BOX2D引擎的第一步:

'世界外边框
Dim Wordaabb As Box2DX.Collision.AABB
Wordaabb.LowerBound.Set(-10000.0F, -10000.0F)
Wordaabb.UpperBound.Set(10000.0F, 10000.0F)
'创建世界,第二个参数是重力,第三个是是否允许引擎休眠
world = New Box2DX.Dynamics.World(Wordaabb, g, False)

这样,就创建完成了,非常简单吧!

【四、创建一个物体】

在BOX2D中,物体是这样创建的:

1、首先有一个“物体”(之所以这样叫,是因为到你附加形状并计算质量之前,它应该只是一个“抽象”的处于意识中的物体)被创建,但是他没有形态

2、指定物体的一些信息:位置,空气阻尼等

3、创建一个形态并设置由形态先关的信息:体积、密度、摩擦系数等

出处:http://www.cnblogs.com/zcsor/

4、为物体附加形态,让引擎计算其质量

可能这不是我们所习惯的,但你确实应该这样做:先设置形态,然后设置密度,因为决定物体运动状态的并不只是质量,形态将起到决定性作用!
'创建动态物体,它的质量一定要大于0
Dim bodyDef As New Box2DX.Dynamics.BodyDef

'物体的位置
bodyDef.Position.Set(X,Y)

'空气的阻尼
bodyDef.LinearDamping = Damping

'实际物体
Dim Body As Box2DX.Dynamics.Body

'在世界中创建物体
Body = world.CreateBody(bodyDef)
'为物体附加一个多边形
Dim shapeDef As New Box2DX.Collision.CircleDef
shapeDef.IsSensor = IsSensor '可否“休眠”即物体不动时引擎是否可以不计算它。(可能听起来有点别扭)
shapeDef.Radius = R '设置体积为1,以使得质量计算结果为1
shapeDef.Density = Density '密度
'shapeDef.Friction = 1 '摩擦系数
Dim shape = Body.CreateShape(shapeDef) '附加到物体
Body.SetMassFromShapes() '根据形状计算质量
至此,我们创建了一个动态物体。

【五、物体的运动】

好,我们先考虑一下,影响我们的“炮弹”运动轨迹的因素都有哪些:

1、重力

2、发射角度

3、发射力度

4、空气阻尼

5、风力

6、发射点(这在创建动态物体时已经被指定了,当然你可以告诉引擎来修改它的属性来使它停下并“瞬移”到你想要的位置重新开始运动。但我没有这样做——出于编码的原因当它无用时,我销毁它,下次计算时创建一个新物体,这样做效率并不很低)所以我们不关心它。

好,我们一个一个解决这些问题:

1、重力——这个属性是属于世界的,一般我们定义重力是向下的一个向量(BOX2D提供了用于内置计算的向量对象)

Dim g As New Box2DX.Common.Vec2(0.0F, 17.27F) '重力

2、3、发射角度和力度——这将是瞬间完成的,不要指望去模拟一个在炮筒中的爆炸,那是得不偿失的——我们直接用一个向量作用于物体质心即可:

Body.ApplyImpulse(New Box2DX.Common.Vec2( Math.Cos(Angle) * Power , -Math.Sin(Angle) * Power), Body.GetPosition)

这个语句可能稍微复杂一些,实际上有两个参数,第一个就是冲量,第二个是物体当前位置。

第一个参数只是用了三角形的基本知识,将发射角度和力度进行换算,得到X,Y两个方向上的力——即得到一个向量^ ^。

4、空气阻尼——一个属于物体的性质:这也灰常的简单,只需要告诉BOX2D一个数值,它就会在迭代器模拟物理运动的计算过程中应用它。

bodyDef.LinearDamping = Damping

5、风力——这是一个BOX2D所没有提供的性质,换句话说,BOX2D的世界里没有风……果然是灰常的风和日丽……那怎么办呢?呃……

思考中……

思考中……

其实物理引擎无非是在一定的时刻(后面将提到步长)近似的求得物体的位置及速度(线速度和角速度),那么我们可以在每次计算时,都作用一个风力给物体。这是我的解决方案,当然,你也可以改变重力的大小和方向来达到目的(将风力和重力的合力作为重力),也许你还有更好的办法……

好了,这个风力代码我是这样写的:

'添加位置到返回数组

Dim ret(MaxStep) As Box2DX.Common.Vec2
For i As Integer = 0 To MaxStep
Body.ApplyForce(f, Body.GetPosition) '应用风力
world.Step(timeStep, MaxStep, 1) '步进计算,第一个参数是步长(多久计算一次),第二个参数是计算的最多步数,第三个,呃是迭代数(每次计算时用迭代器的次数)
ret(i) = New Box2DX.Common.Vec2(Body.GetPosition.X, Body.GetPosition.Y) '当前步位置
'检测进入
'Dim s(0) As Box2DX.Collision.Shape

‘出处:http://www.cnblogs.com/zcsor/
'If RsEvnt = False AndAlso world.Query(mAABB, s, 1) = 1 Then
' RsX = Body.GetPosition.X * 10
' RsY = Body.GetPosition.Y * 10
' RsEvnt = True
'End If
Next

为了看起来更清晰,我仅保留了记录当前位置和应用风力的代码——注释掉的是检测是否击中目标的代码。

至此,我们已经可以得到一个物体的运动轨迹,也许——你应该在GDI+上绘制一下他们了。

【六、冲突检测】

BOX2D提供了AABB检测和其他的冲突检测方式。我们这里简要的提一下AABB,如果你感兴趣,完全可以使用“接触监听器”或者“传感器”来完成你的代码。

创建一个AABB检测和使用它将非常简单:只需要设置AABB的两个坐标点——像创建世界外边框那样,指定左上角和右下角。
mAABB.LowerBound.Set(X1, Y1)
mAABB.UpperBound.Set(X2, Y2)

而检测代码已经在上面的代码中了——还记得吗,我把他们注释掉了。

原文地址:https://www.cnblogs.com/tiankonguse/p/2609046.html