简单的基于物理的运动

31

我正在开发一个二维游戏,在尝试使用一些基础的物理代码将对象加速到最高速度。

以下是其伪代码:


const float acceleration = 0.02f;
const float friction     = 0.8f;  // value is always 0.0..1.0
      float velocity     = 0;
      float position     = 0;

move()
{
   velocity += acceleration;
   velocity *= friction;
   position += velocity;
}
这是一个非常简化的方法,不依赖于质量或实际摩擦力(代码中的摩擦力只是一种对运动的通用力)。它很有效,因为“velocity *= friction;”部分可以防止速度超过某个点。然而,我在最高速度及其与加速度和摩擦之间的关系方面有些困惑。
我的目标是设置一个顶速度,并确定达到该速度所需的时间,然后使用它们来推导出加速度和摩擦值。
例如,

const float max_velocity = 2.0; 
const int   ticks;       = 120; // If my game runs at 60 FPS, I'd like a 
                                // moving object to reach max_velocity in 
                                // exactly 2 seconds.
const float acceleration = ?
const float friction     = ?

2
你有没有考虑过查看《物理学为游戏开发者》(http://oreilly.com/catalog/9780596000066/)? - NoahD
这是一个有趣的方法。我通常为对象定义加速度和最大速度,但从未考虑过“达到最大速度所需时间”的值,这实际上是一种很好的表达方式。 - Nick Van Brunt
6个回答

41

我发现这个问题非常有趣,因为我最近做了一些关于带阻力的抛体运动建模的工作。

要点1:你基本上是使用显式/向前欧拉迭代来更新位置和速度,在这种情况下,每个状态的新值应该是旧值的函数。在这种情况下,你应该先更新位置,然后再更新速度。

要点2:有更现实的物理模型可以用于阻力摩擦效应。其中一个模型(由Adam Liss提出)涉及一个与速度成比例的阻力力(称为斯托克斯阻力,通常适用于低速情况)。我之前提出的一个模型涉及一个与速度平方成比例的阻力力(称为二次阻力,通常适用于高速情况)。我将针对如何推导最大速度和有效到达最大速度所需时间的公式来讨论每个模型。我将放弃完整的推导,因为它们相当复杂。


斯托克斯阻力:

更新速度的方程式为:

velocity += acceleration - friction*velocity

这代表以下微分方程:

dv/dt = a - f*v

使用这个积分表中的第一个条目,我们可以找到解决方案(假设在t = 0时v = 0):

v = (a/f) - (a/f)*exp(-f*t)

最大速度(即终端速度)发生在 t >> 0 时,因此方程中的第二项非常接近于零,并且:

v_max = a/f

关于达到最大速度所需的时间,请注意方程式从未真正达到最大速度,而是渐近于最大速度。然而,当指数等于-5时,速度约为最大速度的98%,足以认为它相等。您可以将达到最大速度的时间近似为:
t_max = 5/f

您可以使用这两个方程式来解出fa,假设您已知所需的vmaxtmax


二次阻力:

更新速度的方程为:

velocity += acceleration - friction*velocity*velocity

这代表以下微分方程:

dv/dt = a - f*v^2

使用 这个积分表 中的第一个条目,我们可以找到解决方案(假设在 t = 0 时 v = 0)。
v = sqrt(a/f)*(exp(2*sqrt(a*f)*t) - 1)/(exp(2*sqrt(a*f)*t) + 1)

当 t >> 0 时,最大(即终端)速度发生,因此指数项远大于1,方程式趋近于:

v_max = sqrt(a/f)

关于达到最大速度所需的时间,请注意该方程永远无法真正达到最大速度,而是渐近趋向于它。然而,当指数函数的参数等于5时,速度接近最大速度的99%,可能足够接近以被认为相等。您可以将达到最大速度的时间近似为:

t_max = 2.5/sqrt(a*f)

这也等同于:

t_max = 2.5/(f*v_max)

对于所需的vmaxtmax,第二个关于tmax的方程将告诉您应该是什么f,然后您可以将其插入到vmax的方程中以获取a的值。


这似乎有点过度,但这些实际上是建模阻力最简单的方法之一!真正想看到整合步骤的人可以给我发电子邮件,我会把它们发送给您。它们有点复杂,无法在此处键入。
另一个要点:我没有立即意识到,但如果您使用我为v(t)推导的公式,则不再需要更新速度。如果您只是从静止开始建模加速度,并且您正在跟踪自加速以来的时间,则代码将如下所示:
position += velocity_function(timeSinceStart)

其中"velocity_function"是两个公式之一,用于v(t),您将不再需要速度变量。一般来说,这里存在一个权衡:计算v(t)可能比使用迭代方案更新速度更加计算密集(由于指数项),但它保证保持稳定和有界。在某些条件下(例如尝试获得非常短的tmax),迭代可能变得不稳定并崩溃,这是前向欧拉方法的常见问题。然而,限制变量(如0<f<1)应该可以防止这些不稳定性。
此外,如果您感觉有点虐待狂,您可能能够积分v(t)的公式,从而放弃完全需要牛顿迭代的需求。我会让其他人尝试这个。=)

+1 避免掉进我似乎已经创造出来的离散数学漩涡。我喜欢你的简洁明了,尽管我真的想回答 OP 对指定时间和最大速度的请求!顺便说一句,很棒的用户名! - Adam Liss
2
谢谢!我可能避免了一个离散数学的陷阱,但我却掉进了一个积分的冰裂缝里!=) - gnovice
谢谢!这比我预期的要复杂得多,但最终结果的逼近非常接近,并且效果非常好。 - Radek
斯托克斯阻力和二次阻力哪个更现实? - alexpinho98
@alexpinho98:我在第二点中提到了这一点。斯托克斯阻力更好地模拟低速情况,而二次阻力更好地模拟高速情况。在我提供的链接中有更详细的讨论。 - gnovice
显示剩余2条评论

3

警告:部分解决方案

如果我们按照物理学所述的来看,就没有最大速度。从纯粹的物理观点来看,你已经将加速度固定为一个恒定值,这意味着速度始终在增加。

作为替代方案,请考虑作用于对象上的两个力:

  • 常数外力F,它倾向于加速它,以及
  • 阻力d,它与速度成比例且趋向于减速。

因此,迭代n时的速度变为:vn = v0 + n F - dvn-1

您要选择发生在迭代nmax时的最大速度vnmax

请注意,这个问题是不完全约束的;也就是说,Fd是相关的,因此您可以任意选择其中一个的值,然后计算另一个的值。

现在球已经滚动了,有人愿意接手数学吗?

警告:它很丑陋,涉及幂级数


编辑:为什么第一个方程中的序列n**F**会字面出现,除非在n后面有一个空格?


+1 对于部分解决方案,尽管我认为我可以通过一些闭式积分来避开幂级数,以展示如何选择最大速度和达到最大速度的时间。 - gnovice

2
velocity *= friction;

这并不能阻止速度超过某一点……

随着速度的增加,摩擦力呈指数级增加(请不要引用我说的话),而在静止时为0。最终,您将达到摩擦力等于加速度的点。

因此,您需要像这样的东西:

velocity += (acceleration - friction);
position += velocity;
friction = a*exp(b*velocity);

在这里,您需要选择a和b的值。 b将控制达到最高速度所需的时间,而a将控制摩擦力增加的突然程度。(再次强调,请不要自行研究 - 我只是根据我记得的12年级物理知识进行解释。)


我最初忘记提到摩擦应该在0.0..1.0之间。无论如何,尽管上面的代码使用步骤增加了速度,但我不明白最大速度是如何通过“b”引用的。 - Radek

2

这并不是回答你的问题,但在这样的模拟中,你不应该依赖于固定的帧率。计算自上次更新以来的时间,并在你的方程式中使用 delta-T。像这样:

static double lastUpdate=0;
if (lastUpdate!=0) {
  deltaT = time() - lastUpdate;
  velocity += acceleration * deltaT;
  position += velocity * deltaT;
}
lastUpdate = time();

另外,检查一下是否失去了焦点并停止更新,当你重新获得焦点时,将lastUpdate设置为0。这样,当你回到页面时就不会有大量待处理的deltaT。


2
对于这个特定的实现,固定帧并不是一个问题,因为没有太多的计算需要处理,而且我也没有使用受限制的(即慢速的)设备。但是,否则,增量时间肯定会成为方程的一部分。 - Radek
1
设置 deltaT = time() - lastUpdate,除非你在照镜子。 :-) - Adam Liss

1

如果你想看看用非常简单的物理模型和非常简单的数学可以做些什么,可以看看http://scratch.mit.edu/上的一些Scratch项目-您可能会获得一些有用的想法,并且肯定会玩得开心。


1

这可能不是你想要的,但根据你正在使用的引擎,最好使用其他人构建的引擎,例如farseer(适用于C#)。 注意 Codeplex正在维护中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接