【问题标题】:How to make Fluid Drag equation not framerate dependent如何使流体阻力方程不依赖于帧率
【发布时间】:2011-11-15 07:44:51
【问题描述】:

我正在尝试为我开始编码的游戏做计划。 (非常开始)

我的问题是我希望所有游戏对象的加速/移动部分都基于加速力与阻力(因此导致终端速度作为可用速度的上限)

虽然我可以走另一条路,但如果可能的话,我宁愿不走。此外,(朋友)建议我可以使用物理库,但这似乎有点矫枉过正,此外,我想自己学习和理解这些概念 - 我总是觉得当我更好地理解自己的程序时做。

我正在制作一个 2D 游戏,并使用 Vector2 变量来表示位置、航向和推力(施加的加速力)。它是自上而下的,所以重力不是方程式的一部分。

在编写代码之前,我会在 Excel 中编写测试用例 - 这是我在将数学提交到代码之前检查数学的方式。而且我发现我对阻力方程的使用正在使有问题的对象帧率依赖! 具体来说,帧率越高,最终的终端速度就越低。

我一直在尝试根据需要修改方程式以考虑帧速率,但它让我望而却步。

如果您想使用与我相同的电子表格,you can download the spreadsheet here

但您不必这样做 - 这是具体细节。

我理解的阻力方程是:

Drag = 0.5 * FluidDensity * Velocity * Velocity * DragCoefficient * IncidenceArea

使用凭空挑选的一些数字进行计算,如果流体密度为 0.233,阻力系数为 0.4,入射面积为 0.1,加速度为每秒 50 像素,那么会发生以下情况:

如果我计算出每 0.25 秒(每四分之一秒一次)以 1/4 的加速力(以匹配时间)施加加速度,那么我们会以大约每秒 39.3 像素的速度达到最终速度。

如果我每隔一秒计算一次加速度,我们会以大约每秒 53.6 像素的速度达到终端速度。

具体来说,每次我计算给定的 DeltaTime 时,结果速度都会计算为(代码来自我的头脑 - 不是来自 IDE - 如果其中有错误,请致歉):

//In globals / initialization:
Vector2 Position;
Vector2 Speed;
Vector2 ThrustForce;
float Density = 0.233f;
float DragCoefficient = 0.4f;
float IncidentalArea = 0.1f;

//In the update loop
//DeltaTime is a float based upon how much of a second passed
Vector2 AccelerationToApply = ThrustForce * DeltaTime;
Vector2 NewSpeed = Speed + AccelerationToApply;
Vector2 Drag = Speed * Speed * 0.5f * Density * DragCoefficient * IncidentalArea;
NewSpeed -= Drag;
Speed = NewSpeed;

这就是数学问题。问题来了:

这应该如何表达以使其与帧率无关?

【问题讨论】:

  • Drag 是一种类似于 ThrustForce 的力,但您不能将其乘以 DeltaTime。这闻起来像个虫子。
  • 其实我也试过了(这是在 Excel 中进行前期工作的乐趣——我可以快速修改并查看结果),但结果仍然大不相同。
  • 我整天都在玩 Excel,终于找到了正确的答案。您的答案实际上是解决方案的一半,因此将其作为答案提交,我会接受。同时,我将更新原始帖子并添加完整答案。
  • 请不要将您的解决方案放入问题的正文中。创建并接受您自己的答案。
  • 好的,没问题 - 我现在就这样做。

标签: c# xna xna-4.0 game-physics


【解决方案1】:

经典的方法是独立于游戏循环帧速率来逐步模拟物理时间,并在必要时计算每帧的多个子迭代以推进物理。这允许您控制您的时间步长(通常使其小于主帧速率),这也有助于控制其他可能不稳定的计算(例如振荡器)。这当然意味着您的物理计算必须比实际计算更快选择固定时间步长的时间,否则您的世界将进入慢动作。

说到不稳定性,我想您会在当前的实现中看到一些振荡效应,这取决于您是否在给定时间步长内超过了终端速度。解决此问题的一种方法是通过分析积分计算速度,而不是使用增量步进行近似。为此,请将您的公式表达为微分方程,并查看它是否具有易于解析求解的形式。

【讨论】:

  • 我认为这对我来说行不通。感谢您的回复,但是......我在 XNA Update() 循环期间运行这些计算,玩家可能会以不同的速度运行游戏 - 这将是多人游戏 - 这意味着如果我确实走这条路,我d 必须始终通过网络发送每个对象的位置,而不是能够发送更新以恒定加速度创建对象,然后让远程客户端处理它们的位置(无论如何直到碰撞时间)。
  • @Dracorat,这个想法是你将在更新循环中多次更新物理,每个子迭代都有一个固定的时间步长。这实际上在多人游戏中更为重要,否则不同的帧速率可能会随着时间的推移导致不同的行为,因为每个客户端上经过不同的时间会累积舍入误差。以固定数量(假设每次更新 1 毫秒)计算物理场,然后显示当前帧的“最近”状态。因此,如果您在对应于 t=100.5ms 的帧进行渲染,则显示 t=100ms 的状态。
【解决方案2】:

上面的代码缺少两部分。虽然我曾尝试将一个部分“打开”和“关闭”来通过实验确定是否需要它,但如果没有另一个,我在找到正确答案时遇到了问题。

这两部分是这样的:合成阻力确实需要乘以时间步长,以减少其对加速度的影响,而且也许更重要的是 - 施加在该帧上的加速力需要阻力在应用于速度之前从它中减去 - 而不是像我上面所说的那样。

修改后的(现在与帧速率无关)代码如下所示: 此外,为了简单起见,我将 4 个“恒定”系数减少到只有一个系数。

//In globals / initialization:
Vector2 Position;
Vector2 Speed;
Vector2 ThrustForce;
float Coefficient = 0.009f;
float PreviousDrag = 0.000f;

//In the update loop
//DeltaTime is a float based upon how much of a second passed
Vector2 AccelerationToApply = ThrustForce * DeltaTime + PreviousDrag * DeltaTime;
Vector2 NewSpeed = Speed + AccelerationToApply;
PreviousDrag = Coefficient * NewSpeed * NewSpeed;
Speed = NewSpeed;

通过 excel 运行此逻辑,我发现无论我多久(或不多久)计算一次速度变化,我几乎在同一时间达到相同的近似终端速度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-28
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多