31

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

这是它的伪代码:


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;
}

这是一种非常简化的方法,不依赖于质量或实际摩擦(代码中的摩擦只是作用于运动的通用力)。它和“速度 *= 摩擦力”一样有效;部分防止速度超过某个点。然而,正是这个最高速度以及它与加速度和摩擦的关系让我有点迷茫。

我想做的是设置一个最高速度,以及达到它所需的时间,然后用它们来推导出加速度和摩擦值。

IE,


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     = ?
4

6 回答 6

40

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

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

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


斯托克斯的阻力:

更新速度的方程为:

velocity += acceleration - friction*velocity

它表示以下微分方程:

dv/dt = a - f*v

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

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

当 t >> 0 时出现最大(即终端)速度,因此等式中的第二项非常接近于零,并且:

v_max = a/f

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

t_max = 5/f

然后,您可以使用这两个方程来求解f定的所需vmaxtmax


二次阻力:

更新速度的方程为:

velocity += acceleration - friction*velocity*velocity

它表示以下微分方程:

dv/dt = a - f*v^2

使用这个积分表中的第一个条目,我们可以找到解(假设 v = 0 在 t = 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)的公式以获得p(t)的封闭形式解决方案,从而完全不需要牛顿迭代。我会把这个留给其他人尝试。=)

于 2009-03-20T17:13:37.980 回答
3

警告:部分解决方案

如果我们遵循所述的物理学,则没有最大速度。从纯粹的物理角度来看,您已将加速度固定在一个恒定值,这意味着速度始终在增加。

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

  • 趋向于加速它的恒定外力F
  • 阻力d与速度成正比并趋于减慢速度。

因此迭代速度n变为:v n = v 0 + n F - d v n-1

您已要求选择迭代时出现的最大速度v nmaxnmax

请注意,问题是欠约束的;也就是Fd是相关的,所以可以任意给其中一个取一个值,然后再计算另一个。

现在球在滚动,有人愿意学习数学吗?

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


编辑:为什么n**F**第一个等式中的序列会按字面意思出现,除非 ? 后面有空格n

于 2009-03-20T20:25:21.653 回答
2
velocity *= friction;

这并不能阻止速度达到某个点...

随着速度的增加,摩擦力呈指数增长(不要引用我的话),并且在静止时为0。最终,您将达到摩擦=加速度的地步。

所以你想要这样的东西:

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

在哪里选择 a 和 b 的值。b 将控制达到最高速度所需的时间,a 将控制摩擦增加的突然程度。(再一次,不要对此进行自己的研究——我从我记得的 12 年级物理开始。)

于 2009-03-20T17:11:49.763 回答
2

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

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

检查您是否失去焦点并停止更新也很好,当您获得焦点时,将 lastUpdate 设置为 0。这样,当您返回时,您不会获得巨大的 deltaT 来处理。

于 2009-03-20T21:09:34.700 回答
1

如果你想看看使用非常简单的数学可以用非常简单的物理模型做什么,请查看http://scratch.mit.edu/上的一些 Scratch 项目- 你可能会得到一些有用的想法,你会当然玩得开心。

于 2009-03-20T20:30:04.047 回答
1

这可能不是您正在寻找的,但取决于您正在使用的引擎,使用其他人构建的引擎可能会更好,例如farseer(用于 C#)。 注意Codeplex 因维护而停机。

于 2009-03-20T20:50:00.643 回答