如何实现重力?不是针对特定语言,而是伪代码...
如何实现重力?不是针对特定语言,而是伪代码...
Answers:
正如其他人在评论中指出的那样,tenpn的答案中描述的基本Euler积分方法存在一些问题:
即使对于简单的运动,例如在恒定重力下的弹道跳跃,也会引入系统误差。
错误取决于时间步长,这意味着更改时间步长会以系统的方式更改对象轨迹,如果游戏使用可变的时间步长,则玩家可能会注意到这一点。即使对于具有固定物理时间步长的游戏,在开发过程中更改时间步长也会明显影响游戏物理性,例如以给定力发射的物体飞行的距离,可能会破坏先前设计的水平。
即使基础物理学应该这样做,它也不节省能量。特别是,应该稳定振荡的物体(例如,钟摆,弹簧,绕行行星等)可能会稳定地积累能量,直到整个系统爆炸。
幸运的是,用几乎一样简单但没有这些问题的东西代替Euler积分并不难,特别是使用二阶辛辛积分器,例如跨越式积分或紧密相关的速度Verlet方法。特别是,基本的Euler积分将速度和位置更新为:
加速度=力(时间,位置)/质量; 时间+ =时间步长; 位置+ =时间步*速度; 速度+ =时间步*加速度;
速度Verlet方法是这样的:
加速度=力(时间,位置)/质量; 时间+ =时间步长; 位置+ =时间步长* (速度+时间步长*加速度/ 2) ; newAcceleration =力(时间,位置)/质量; 速度+ =时间步* (加速度+ newAcceleration)/ 2 ;
如果您有多个相互作用的对象,则应在重新计算力和更新速度之前更新它们的所有位置。然后可以保存新的加速度并将其用于下一时间步更新位置,从而将force()
每个时间步的调用次数减少到一个(每个对象),就像使用Euler方法一样。
另外,如果加速度通常是恒定的(例如弹道跳跃时的重力),我们可以将以上简化为:
时间+ =时间步长; 位置+ =时间步长* (速度+时间步长*加速度/ 2) ; 速度+ =时间步*加速度;
与基本的Euler集成相比,加粗的术语是唯一的更改。
与Euler积分相比,速度Verlet和Jumpfrog方法具有几个不错的特性:
对于恒定的加速度,它们会给出精确的结果(无论如何,最多会浮点舍入误差),这意味着即使时间步长发生变化,弹道跳跃的轨迹也保持不变。
它们是二阶积分器,这意味着,即使加速度变化,平均积分误差也仅与时间步长的平方成比例。这可以允许更大的时间步长而不会影响准确性。
它们是辛辛的,这意味着如果基础物理学做到了,它们将节省能量(至少在时间步长不变的情况下)。尤其是,这意味着您不会像行星自发地飞出轨道一样,也不会像弹簧那样逐渐附着在彼此之间的物体不断摆动直到整个物体炸毁。
但是速度Verlet /越级跳变方法几乎与基本的Euler积分一样简单,快速,并且比诸如四阶Runge-Kutta积分之类的替代方法简单得多(后者通常是一个非常好的积分器,但缺乏辛性质,需要进行四次评估)所述的force()
每个时间步功能)。因此,我强烈建议任何编写任何游戏物理代码的人都推荐使用它们,即使它就像从一个平台跳到另一个平台一样简单。
编辑:虽然速度Verlet方法的形式推导仅在力与速度无关时才有效,但实际上,即使在与速度有关的力(例如流体阻力)下,也可以很好地使用它。为了获得最佳结果,您应该使用初始加速度值来估算第二次调用的新速度force()
,如下所示:
加速度=力(时间,位置,速度)/质量; 时间+ =时间步长; 位置+ =时间步长* (速度+时间步长*加速度/ 2) ; 速度+ =时间步*加速度; newAcceleration =力(时间,位置,速度)/质量; 速度+ =时间步*(newAcceleration-加速度)/ 2 ;
我不确定速度Verlet方法的此特定变体是否具有特定名称,但是我已经对其进行了测试,并且看来效果很好。它不如四阶Runge-Kutta准确(正如二阶方法所期望的那样),但是它比没有中间速度估计的Euler或天真Verlet好得多,并且仍然保留了法线的辛性质。速度Verlet适用于非速度依赖的保守力。
编辑2:例如,Groot&Warren(J. Chem。Phys。1997)描述了一种非常相似的算法,尽管在行之间读取时,似乎他们通过保存newAcceleration
使用估计速度计算出的值而牺牲了一些精度以增加速度。并将其用作acceleration
下一个时间步。但是它们也引入一个参数0≤ λ ≤1,它被乘以acceleration
在初始速度估计; 出于某种原因,他们建议λ = 0.5,即使所有我的测试表明,λ= 1(实际上是我在上面使用的),无论是否使用加速重用,效果都更好或更好。也许这与他们的力量包括随机的布朗运动分量有关。
force(time, position, velocity)
在我的答案以上只是速记“作用在物体上的力position
在移动velocity
的time
”。通常,该力取决于物体是否处于自由落体状态或位于固体表面上,附近是否有其他物体对其施加力,物体在表面(摩擦)和/或液体中移动的速度有多快或汽油(阻力)等
游戏的每个更新循环,请执行以下操作:
if (collidingBelow())
gravity = 0;
else gravity = [insert gravity value here];
velocity.y += gravity;
例如,在平台游戏机中,一旦您跳了重力,便会启用(collidingBelow告诉您下方是否有地面),一旦撞到地面,便会被禁用。
除此之外,要实现跳转,请执行以下操作:
if (pressingJumpButton() && collidingBelow())
velocity.y = [insert jump speed here]; // the jump speed should be negative
很明显,在更新循环中,您还必须更新位置:
position += velocity;
适当的独立于帧率的*牛顿物理积分:
Vector forces = 0.0f;
// gravity
forces += down * m_gravityConstant; // 9.8m/s/s on earth
// left/right movement
forces += right * m_movementConstant * controlInput; // where input is scaled -1..1
// add other forces in for taste - usual suspects include air resistence
// proportional to the square of velocity, against the direction of movement.
// this has the effect of capping max speed.
Vector acceleration = forces / m_massConstant;
m_velocity += acceleration * timeStep;
m_position += velocity * timeStep;
调整重力常数,运动常数和质量常数,直到感觉正确为止。这是一个直观的事情,可能需要一段时间才能感觉良好。
可以很容易地扩展力矢量以添加新的游戏玩法-例如,添加力使其远离附近的爆炸或黑洞。
*编辑:随着时间的流逝,这些结果将是错误的,但对于您的忠诚或才能而言可能“足够好”。有关更多信息,请参见此链接http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games。
position += velocity * timestep
上面的替换为position += (velocity - acceleration * timestep / 2) * timestep
(其中velocity - acceleration * timestep / 2
是新旧速度的平均值)来解决与Euler集成有关的大多数问题。特别是,如果加速度恒定(通常是重力),则该积分器会给出准确的结果。为了在变化的加速度下获得更高的精度,您可以向速度更新添加类似的校正,以获得速度Verlet积分。
如果要在更大的范围内实现重力,则可以在每个循环中使用这种计算方式:
for each object in the scene
for each other_object in the scene not equal to object
if object.mass * other_object.mass / object.distanceSquaredBetweenCenterOfMasses(other_object) < epsilon
abort the calculation for this pair
if object.mass is much, much bigger than other_object.mass
abort the calculation for this pair
force = gravitational_constant
* object.mass * other_object.mass
/ object.distanceSquaredBetweenCenterOfMasses(other_object)
object.addForceAtCenterOfMass(force * object.normalizedDirectionalVectorTo(other_object))
end for loop
end for loop
对于更大的(银河系)尺度,仅靠重力就不足以产生“真实”运动。恒星系统的相互作用在很大程度上是由Navier-Stokes流体动力学方程式所决定的,因此,您还必须牢记有限的光速-因此也要注意重力。
Ilmari Karonen提供的代码几乎是正确的,但存在一些小故障。您实际上计算了每个刻度2次加速度,这并不遵循教科书方程式。
acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;
以下mod是正确的:
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;
干杯'
Pecant的答案忽略了帧时间,这使您的物理行为有时会有所不同。
如果您要制作一个非常简单的游戏,则可以制作自己的小型物理引擎-为每个移动物体分配质量和各种物理参数,并进行碰撞检测,然后在每帧中更新其位置和速度。为了加快进度,您需要简化碰撞网格,减少碰撞检测的调用等。在大多数情况下,这很痛苦。
最好使用物理引擎,例如physix,ODE和bullet。它们中的任何一个对于您来说都是足够稳定和高效的。