可变时间步长和重力/摩擦力

发布于 2024-12-21 17:45:40 字数 1331 浏览 3 评论 0原文

我正在尝试复制 Sonic 物理引擎 的逻辑,该引擎是为固定的-时间步长系统(60 FPS),处于可变时间步长年龄(准确地说是 Slick2D)。

在原版中,按下跳跃按钮后,玩家的 velocity.y 设置为 -6.5,并且每个刻度 0.21875 都会添加到 velocity.y 以模拟重力。

每次调用我的逻辑更新时,都会传递一个时间增量参数,指定已经过去了多少毫秒。如果经过的毫秒数比我预期的多,那么我会重复更新逻辑,传递最多为 1 的“内部增量”,或者如果我们正在处理目标帧的“剩余部分”,则传递更少的增量。

例如,如果我们预计一帧需要 16 毫秒,而它确实需要 16 毫秒,则循环将迭代一次并将 thisMiniTick 作为 1 传递。如果增量不是 16 毫秒而是 40 毫秒,循环将执行三次,传递 1、1,最后传递 0.5。

我错误地认为在每个内部更新循环中我都可以执行 velocity.y += (gravity * thisMiniTickRelative),但这不起作用。在较快的帧速率下,没有施加足够的重力,导致较高的跳跃,而在较慢的帧速率下,跳跃较低(尽管没有那么明显)。

有没有一种方法可以适用于几乎所有帧速率,或者我必须为 delta 设置上限和下限?

“内部更新”循环:

    float timeRemaining = delta/1000f; 
    while(timeRemaining > 0)
    {
        float thisMiniTick = Math.min(timeRemaining, 1f / FRAMES_PER_SECOND);
        float thisMiniTickRelative = thisMiniTick / (1f / FRAMES_PER_SECOND);

        updateInput(container, game, thisMiniTickRelative);
        if (playerAirState)
        {
            playerVelocity.y += (GRAVITY * thisMiniTickRelative);
        }
        clampPlayerVelocity();
        playerPosition.add(playerVelocity);
        doCollisions();
        timeRemaining -= thisMiniTick;
    }

I'm trying to copy the logic of the Sonic physics engine, which was written for a fixed-timestep system (60 FPS), in a variable timestep age (Slick2D, to be precise).

In the original upon pressing the jump button, the player's velocity.y is set to -6.5, and each tick 0.21875 is added to velocity.y to model gravity.

Each time my logic update is called, a time delta parameter is passed specifying how many millis have passed. If more millis have passed than I was expecting, then I repeat the update logic, passing an 'inner delta' that is at most 1, or less if we're dealing with the 'remainder' of a target frame.

E.g. if we expect a frame to take 16ms, and it does take 16 ms, the loop will iterate once and pass thisMiniTick as 1. If the delta was not 16ms but 40ms, the loop will execute three times, passing 1, 1, and finally 0.5.

I mistakenly thought that in each of these inner update loops I could do velocity.y += (gravity * thisMiniTickRelative), but this doesn't work. On faster framerates not enough gravity is applied causing a higher jump, and on slower framerates the jump is lower (although not anywhere near as noticeably).

Is there a way of doing this that will work for virtually all framerates, or must I resort to setting an upper and lower bound for delta?

The 'inner update' loop:

    float timeRemaining = delta/1000f; 
    while(timeRemaining > 0)
    {
        float thisMiniTick = Math.min(timeRemaining, 1f / FRAMES_PER_SECOND);
        float thisMiniTickRelative = thisMiniTick / (1f / FRAMES_PER_SECOND);

        updateInput(container, game, thisMiniTickRelative);
        if (playerAirState)
        {
            playerVelocity.y += (GRAVITY * thisMiniTickRelative);
        }
        clampPlayerVelocity();
        playerPosition.add(playerVelocity);
        doCollisions();
        timeRemaining -= thisMiniTick;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

紫﹏色ふ单纯 2024-12-28 17:45:40

不要将其视为“为 delta 设置上限和下限”。您的应用程序和线程受应用程序的操作系统调度时间的影响,以及对系统的所有其他要求,以及您只需要注意的事情。这个挑战在 PC 游戏中自我们从单任务操作系统转向多任务操作系统以来就一直存在。

使用 Slick,您可以(并且应该)断开逻辑更新与渲染更新的连接,这就是 delta 值在您的应用程序中传递的原因。通过使用 .setMinimumLogicUpdateInterval 和 .setMaximumUpdateInterval< 来执行此操作/a> 方法。

在我参与过的项目中,包括 Slick 中的一个项目,我发现每秒 30-60 次逻辑更新(更新之间 30.3 毫秒到 16.6 毫秒)的任何​​内容都效果很好,并为您提供运动所需的平滑度,物理和碰撞计算。

从字面上看,这意味着,对于每秒 30-60 次逻辑更新的范围,您需要执行以下操作:

container.setMinimumLogicUpdateInterval(16);  // max 60 logic updates per second
container.setMaximumLogicUpdateInterval(31);  // min 30 logic updates per second

此外,尝试计算 timeRemaing 值是一个常见错误,但您不这样做想做这个。您只需将移动量乘以经过的时间即可。如果过去了 30 毫秒,则大约为 1/33 秒,因此您应该将游戏对象移动 1 秒内移动量的 1/33。

float timeElapsed = delta/1000f;

playerVelocity.y += (GRAVITY * timeElapsed);

按照上面指定的上限/下限设置后,您可以确定 timeElapsed 将始终为 0.030.06 之间的值。如果您的游戏陷入困境并且帧速率变慢,您的逻辑更新仍然不会超出这些范围。相反,将会发生的情况是整个游戏看起来会变慢(这是应该的,就像过去的世嘉时代,屏幕上有太多东西一样),但碰撞和物理计算仍然会按预期进行。

Don't think of it as "resorting to setting an upper and lower bound for delta". Your application and threads are subject to the OS scheduling time for your app, among all the other demands on the system, and something you just need to be aware of. This challenge is as old in PC gaming as the day we moved from single-tasking operating systems to multi-tasking operating systems.

With Slick, you can (and should) disconnect your logic updates from your rendering updates, which is why the delta value gets passed around your application. Do this by using the .setMinimumLogicUpdateInterval and .setMaximumUpdateInterval methods.

On projects that I've worked on, including one in Slick, I find that anything in the 30-60 logic updates per second (30.3 milliseconds to 16.6 milliseconds between updates) works great, and gives you the smoothness you need from your movement, physics, and collision calculations.

Literally what that means is, for a 30-60 logic updates per second range, you want to do the following:

container.setMinimumLogicUpdateInterval(16);  // max 60 logic updates per second
container.setMaximumLogicUpdateInterval(31);  // min 30 logic updates per second

Also, it's a common mistake to try to calculate the timeRemaing value, but you don't want to do this. You simply want to multiply how much you move, by how much time has passed. If 30 milliseconds have passed, that's about 1/33rd of a second, so you should move your game object by 1/33rd of the amount it would move in 1 second.

float timeElapsed = delta/1000f;

playerVelocity.y += (GRAVITY * timeElapsed);

With the upper/lower bounds set as specified above, you're sure that timeElapsed will always be a value between 0.03 and 0.06. If your game gets bogged down and your frame rate slows down, your logic updates still won't go outside these bounds. What will happen instead is the whole game will appear to slow down (as it should, just like on the old Sega days when there was too much on the screen), but collisions and physics calculations will still work as expected.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文