如何实现重力?


Answers:


52

正如其他人在评论中指出的那样,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(实际上是我在上面使用的),无论是否使用加速重用,效果都更好或更好。也许这与他们的力量包括随机的布朗运动分量有关。


Velocity Verlet很好,但是它的电位可能不受速度的影响,因此无法实现摩擦。我认为Runge-Kutta 2对我而言是最好的;)
Pizzirani Leonardo 2012年

1
@PizziraniLeonardo:即使对于速度相关的力,也可以使用速度Verlet(的一种变体)。参见上面的编辑。
Ilmari Karonen '11年

1
文献没有给“速度Verlet”的这种解释起一个不同的名字。正如本文fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf中所述,它依赖于预测器-校正器策略。
teodron

3
@ Unit978:这取决于游戏,尤其取决于它实现的物理模型。将force(time, position, velocity)在我的答案以上只是速记“作用在物体上的力position在移动velocitytime”。通常,该力取决于物体是否处于自由落体状态或位于固体表面上,附近是否有其他物体对其施加力,物体在表面(摩擦)和/或液体中移动的速度有多快或汽油(阻力)等
Ilmari Karonen 2014年

1
这是一个很好的答案,但是如果不谈论固定的时间步长(gafferongames.com/game-physics/fix-your-timestep),这是不完整的。我会添加一个单独的答案,但是大多数人会停止接受该答案,尤其是当它以如此大的优势获得最多的选票时,就像这里的情况一样。我认为通过增加这一群体可以更好地为社区服务。
Jibb Smart 2016年

13

游戏的每个更新循环,请执行以下操作:

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;

6
你什么意思?只需选择您自己的重力值,由于它会改变您的速度,而不仅仅是您的位置,因此看起来很自然。
Pecant 2011年

1
我不喜欢关闭重力。我认为重力应该是恒定的。应该改变的东西(恕我直言)是您的跳跃能力。
ultifinitus 2011年

2
如果有帮助,可以将其视为“下降”,而不是“重力”。该功能总体上控制物体是否由于重力而掉落。引力本身就和[在此插入引力值]一样存在。因此,从这个意义上讲,重力恒定的,除非将物体放到空中,否则您什么都不要用它。
杰森·皮诺

2
这段代码是与帧速率有关的,这不是很好,但是如果您不断进行更新,那您就笑了。
tenpn 2011年

1
-1,对不起。添加速度和重力,或位置和速度,这根本没有意义。我了解您正在执行的快捷方式,但这是错误的。我会用我能找到的最大的线索击中任何学生,实习生或同事。单元的一致性很重要。
sam hocevar

8

适当的独立于帧率的*牛顿物理积分:

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


4
不要使用Euler集成。请参阅Glenn Fiedler的这篇文章,它比我能更好地解释了问题和解决方案。:)
Martin Sojka

1
我了解随着时间的推移,Euler是不准确的,但是我认为在某些情况下这并不重要。只要规则对于每个人都是一致的,并且“感觉”正确,那就很好。而且,如果您只是在学习物理化学,则很容易记住和实现。
tenpn 2011年

...很好的链接。;)
tenpn 2011年

4
您可以简单地通过将position += velocity * timestep上面的替换为position += (velocity - acceleration * timestep / 2) * timestep(其中velocity - acceleration * timestep / 2是新旧速度的平均值)来解决与Euler集成有关的大多数问题。特别是,如果加速度恒定(通常是重力),则该积分器会给出准确的结果。为了在变化的加速度下获得更高的精度,您可以向速度更新添加类似的校正,以获得速度Verlet积分
Ilmari Karonen 2012年

您的论点是有道理的,而且不准确通常不是什么大问题。但您不应声称它是“适当的帧速率独立”集成,因为它不是(帧速率独立)。
sam hocevar

3

如果要在更大的范围内实现重力,则可以在每个循环中使用这种计算方式:

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流体动力学方程式所决定的,因此,您还必须牢记有限的光速-因此也要注意重力。


1

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;

干杯'


我认为您错了,因为加速度取决于速度
超级

-4

Pecant的答案忽略了帧时间,这使您的物理行为有时会有所不同。

如果您要制作一个非常简单的游戏,则可以制作自己的小型物理引擎-为每个移动物体分配质量和各种物理参数,并进行碰撞检测,然后在每帧中更新其位置和速度。为了加快进度,您需要简化碰撞网格,减少碰撞检测的调用等。在大多数情况下,这很痛苦。

最好使用物理引擎,例如physix,ODE和bullet。它们中的任何一个对于您来说都是足够稳定和高效的。

http://www.nvidia.com/object/physx_new.html

http://bulletphysics.org/wordpress/


4
-1无法回答问题的无用回应。
doppelgreener

4
好吧,如果您想调整时间,您可以按从上次update()开始所经过的时间缩放速度。
Pecant 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.