我正在尝试复制Sonic physics engine的逻辑,这是为固定时间步系统(60 FPS)编写的,在一个可变的时间步长(Slick2D,确切地说)。
在按下跳跃按钮的原始状态下,玩家的velocity.y
设置为-6.5,每个刻度0.21875被添加到velocity.y
以模拟重力。
每次调用我的逻辑更新时,都会传递一个时间delta参数,指定已经过了多少毫秒。如果超过我预期的毫秒数,那么我重复更新逻辑,如果我们正在处理目标帧的“余数”,则传递一个最多为1或更小的“内部增量”。
E.g。如果我们期望一个帧需要16ms,而 花费16ms,则循环将迭代一次并将thisMiniTick
传递为1.如果delta不是16ms而是40ms,则循环将执行三次,传递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;
}
答案 0 :(得分:2)
不要将其视为“设置delta
的上限和下限”。您的应用程序和线程受制于应用程序的操作系统调度时间,以及系统的所有其他要求,以及您需要注意的事项。与我们从单任务操作系统转向多任务操作系统的那一天相比,这一挑战与PC游戏一样悠久。
使用Slick,您可以(并且应该)断开逻辑更新与渲染更新的连接,这就是delta
值在您的应用程序中传递的原因。使用.setMinimumLogicUpdateInterval and .setMaximumUpdateInterval方法执行此操作。
在我参与过的项目中,包括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.03
和0.06
之间的值。如果您的游戏陷入困境并且帧速率变慢,那么您的逻辑更新仍然不会超出这些限制。相反的是,整个游戏似乎会变慢(就像它应该一样,就像在Sega那个日子里屏幕上有太多的东西一样),但是碰撞和物理计算仍然可以按预期工作。