如何将运动物理函数缩放到每秒帧数(在游戏引擎中)?

Posted

技术标签:

【中文标题】如何将运动物理函数缩放到每秒帧数(在游戏引擎中)?【英文标题】:How can I scale movement physics functions to frames per second (in a game engine)? 【发布时间】:2012-09-12 02:38:57 【问题描述】:

我正在使用 javascript (html5 Canvas) 开发游戏。我实现了一个简单的算法,它允许一个对象跟随另一个混合了基本物理的对象(一个力矢量以正确的方向驱动对象,速度叠加动量,但被恒定的阻力减慢)。目前,我将其设置为鼠标 (x, y) 坐标后的矩形。代码如下:

// rectangle x, y position
var x = 400; // starting x position
var y = 250; // starting y position
var FPS = 60; // frames per second of the screen
// physics variables:
var velX = 0; // initial velocity at 0 (not moving)
var velY = 0; // not moving
var drag = 0.92; // drag force reduces velocity by 8% per frame
var force = 0.35; // overall force applied to move the rectangle
var angle = 0; // angle in which to move

// called every frame (at 60 frames per second):
function update()
    // calculate distance between mouse and rectangle
    var dx = mouseX - x;
    var dy = mouseY - y;
    // calculate angle between mouse and rectangle
    var angle = Math.atan(dy/dx);
    if(dx < 0)
        angle += Math.PI;
    else if(dy < 0)
        angle += 2*Math.PI;

    // calculate the force (on or off, depending on user input)
    var curForce;
    if(keys[32]) // SPACE bar
        curForce = force; // if pressed, use 0.35 as force
    else
        curForce = 0; // otherwise, force is 0

    // increment velocty by the force, and scaled by drag for x and y
    velX += curForce * Math.cos(angle);
    velX *= drag;
    velY += curForce * Math.sin(angle);
    velY *= drag;

    // update x and y by their velocities
    x += velX;
    y += velY;

这在每秒 60 帧的情况下运行良好。现在,棘手的部分是:我的问题是,如果我将其更改为不同的帧速率(例如 30 FPS),我如何修改力和拖动值以保持运动恒定?

也就是说,现在我的矩形(其位置由 x 和 y 变量决定)以每秒大约 4 个像素的最大速度移动,并在大约 1 秒内加速到其最大速度。但是,如果我更改帧速率,它会移动得更慢(例如 30 FPS 加速到每帧只有 2 个像素)。

那么,我如何创建一个以 FPS(每秒帧数)作为输入的方程,并吐出正确的“拖动”和“力”值,这些值将实时表现相同?

我知道这是一个沉重的问题,但也许具有游戏设计经验或编程物理知识的人可以提供帮助。感谢您的努力。

jsFiddle:http://jsfiddle.net/BadDB

【问题讨论】:

我删除了我的答案,因为它显然不正确,看着小提琴。我不确定为什么这是不对的,但是,对不起。这可能是因为“物理”实现(例如,您实际上并没有使用运动学方程,AIUI)。 是的,这是一个粗略的实现。这是我第一次尝试玩模拟物理,所以它并不漂亮。无论如何,感谢您的帮助,我很感激。 【参考方案1】:

我将介绍一个实际的时间度量。然后,您应该修改您的方程,使其成为实际经过时间和所需最大速度的函数。使用实际经过时间的好处是,即使在(由于负载或你有什么问题)不以编程的 FPS 运行的系统上,这些方程也能很好地工作。

顺便说一句——您应该使用Math.atan2(dy, dx) 而不是Math.atan(dy/dx)。 (想想dx == 0时会发生什么。)

【讨论】:

作为记录,JavaScript 的 Math.atan(1/0) 正确返回 pi/2(因为 1/0Infinity 而不是抛出某种 DivideByZeroError),所以你不需要 atan2() . atan2 确实 为您提供了正确处理 Math.atan2(-1, -1) 等参数的象限。 en.wikipedia.org/wiki/Atan2#Motivation @MattBall - 关于 Infinity 的要点。我想我的 Fortran 日子正在显现。 :) 这里唯一需要注意的是,这将使您的实际结果取决于所使用机器的速度。例如,如果假设帧之间的线性运动,两个带电粒子之间的碰撞会得出不同的答案。因此,如果您需要结果独立于帧速率,则必须非常小心对象的路径。浮点错误也会以不同的方式累积,最终导致您对帧速率的敏感度变得混乱。 @PhilH - 同意。使用实际时间测量,物理模拟将取决于机器速度。另一方面,如果假设每个动画循环总是一个单位时间,那么游戏体验将取决于机器速度。 (机器越慢,时间过得越慢。)然而,OP描述的物理相当简单,要解决的问题正是游戏体验随帧率而变化。【参考方案2】:

理想情况下,您应该做的是将您的时间帧作为自上一帧以来经过的时间,然后根据实际增量计算比例因子,并将其用于基于时间的计算。

考虑理想情况下,您的游戏以 60 fps 运行。 还要考虑到,你的游戏很少会在每一帧游戏过程中以精确的 1000ms/60f 运行。

所以你的理想是1000/60。 你的实际值是current_timestamp - previous_timestamp。 你的时间范围是actual/ideal

现在,您只需要使用您的比例来转换您对时间敏感的值。

任何时间函数都可以使用其“每帧理想值”(magnitude = 8; current_magnitude = magnitude * scale; vec.x *= current_magnitude; vec.y *= current_magnitude; vec.z *= current_magnitude;)。

您只需要小心了解何时乘以,何时不乘。 如果计算是基于时间的,则进行预乘。如果不是,那就不要。

如果您的游戏以 15fps 运行,那么您的时间尺度将是 4 倍,对吗? 这不应该影响任何东西的力量。例如,它不会影响汽车的扭矩——发动机正在产生的被压抑的能量。它影响的是在确切的时间跨度内发生了多少加速度(线性或其他)。

如果汽车应加速 0.5m/s^2 或您决定的任何速度,那么您只查看该加速度的分数(添加到当前速度),它适用于特定分数您当前所处的第二个。 然后在下一次更新中,汽车应该以您设置的速度行驶,乘以您计算的时间尺度,即当前帧与该点上一帧之间的差异。

旋转应该以同样的方式计算。

这允许您将不一致的帧速率与实际动作区分开来,因为您始终根据百分比而不是硬数字(涉及时间)进行操作。

这也允许像子弹时间这样的事情很容易愚蠢地完成。 为一切添加子弹时间因素,瞄准除外。 或者,如果您想制作忍者反射,请对玩家应用不同的时间尺度,而不是敌人。

对于冻结时间,您有两种选择:

    将所有内容设置为 0 并忘记之前的值(让所有内容在恢复时从 0 开始累积动力) 将所有值设置为 0,但保留之前所有值的“框架”。之后忘记 0,但将下一个时间跨度视为自暂停发生后仅过去了 1 帧。

当您谈论如何处理 drag 时——您自己说过:drag 在您的模拟中是 constant。 不管你说的时间跨度有多长或多短,阻力在那个时间跨度内对一个对象的影响与它在任何其他时间段内对任何对象的影响是一致的时间跨度。

【讨论】:

【参考方案3】:

加倍力,保持阻力不变。

编辑:

数学:

运动可以完全用两个参数来描述:初始加速度和最终速度。如果这两个看起来正确,则动作看起来正确。

对于初始加速,阻力(这种阻力)无关紧要。由于force 是一个加速度,我们所要做的就是将它与一秒内的帧数相加,以获得一秒的加速度:

30 * 30 = 力60 * 60 力30 = 力60 * 60/30 = 2.0 * 力60 = 2.0 (0.35) = 0.7

当力和阻力平衡时,会出现最终速度。

Vterm * 拖动 = 力 拖动 = 力 / Vterm

我们想要缩放 Vterm,但我们也在缩放 force,因此取消了缩放项; drag 无需更改。

【讨论】:

【参考方案4】:

当您根据时间进行积分时,力的积分是速度,速度的积分是位置。如果你让你的时间步长等于两帧之间的时间,那么如果你有一个碰撞后检测器,你的子弹物理就可以工作。

两帧之间的时间与 FPS 成反比。

FPS = 总帧数/总时间

时间步长=( 1.0/(float)FPS 秒数)*K。 K 是一个常数,可以使您的时间步长足够小,从而使您的物理场足够稳定。

【讨论】:

以上是关于如何将运动物理函数缩放到每秒帧数(在游戏引擎中)?的主要内容,如果未能解决你的问题,请参考以下文章

UE4 性能及分析

Egret中使用P2物理引擎

Qt 5.10 集成 Box2D 物理引擎

如何在 GLFW 窗口中限制我的每秒帧数? (使用亲爱的 ImGui)

关于Unity中物理引擎的使用

如何在opencv中计算每秒帧数?