这是 FPS 独立游戏循环的良好实现吗?
我目前有一些接近于以下基于物理游戏的 FPS 独立游戏循环的实现。它在我测试过的几乎每台计算机上都运行良好,当帧速率下降时保持游戏速度一致。然而,我将移植到嵌入式设备,这些设备可能会在视频方面更加困难,我想知道它是否仍然能满足要求。
编辑:
对于这个问题,假设 msecs() 返回程序运行的时间(以毫秒为单位)。 msecs在不同平台上的实现是不同的。这个循环在不同的平台上也以不同的方式运行。
#define MSECS_PER_STEP 20
int stepCount, stepSize; // these are not globals in the real source
void loop() {
int i,j;
int iterations =0;
static int accumulator; // the accumulator holds extra msecs
static int lastMsec;
int deltatime = msec() - lastMsec;
lastMsec = msec();
// deltatime should be the time since the last call to loop
if (deltatime != 0) {
// iterations determines the number of steps which are needed
iterations = deltatime/MSECS_PER_STEP;
// save any left over millisecs in the accumulator
accumulator += deltatime%MSECS_PER_STEP;
}
// when the accumulator has gained enough msecs for a step...
while (accumulator >= MSECS_PER_STEP) {
iterations++;
accumulator -= MSECS_PER_STEP;
}
handleInput(); // gathers user input from an event queue
for (j=0; j<iterations; j++) {
// here step count is a way of taking a more granular step
// without effecting the overall speed of the simulation (step size)
for (i=0; i<stepCount; i++) {
doStep(stepSize/(float) stepCount); // forwards the sim
}
}
}
I currently have something close to the following implementation of a FPS independent game loop for physics based games. It works very well on just about every computer I have tested it on, keeping the game speed consistent when frame rate drops. However I am going to be porting to embedded devices which will likely struggle harder with video and I am wondering if it will still cut the mustard.
edits:
For this question assume that msecs() returns the time passed in milliseconds which the program has run. The implementation of msecs is different on different platforms. This loop is also run in different ways on different platforms.
#define MSECS_PER_STEP 20
int stepCount, stepSize; // these are not globals in the real source
void loop() {
int i,j;
int iterations =0;
static int accumulator; // the accumulator holds extra msecs
static int lastMsec;
int deltatime = msec() - lastMsec;
lastMsec = msec();
// deltatime should be the time since the last call to loop
if (deltatime != 0) {
// iterations determines the number of steps which are needed
iterations = deltatime/MSECS_PER_STEP;
// save any left over millisecs in the accumulator
accumulator += deltatime%MSECS_PER_STEP;
}
// when the accumulator has gained enough msecs for a step...
while (accumulator >= MSECS_PER_STEP) {
iterations++;
accumulator -= MSECS_PER_STEP;
}
handleInput(); // gathers user input from an event queue
for (j=0; j<iterations; j++) {
// here step count is a way of taking a more granular step
// without effecting the overall speed of the simulation (step size)
for (i=0; i<stepCount; i++) {
doStep(stepSize/(float) stepCount); // forwards the sim
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我只是有几点评论。首先是你的评论不够多。有些地方不清楚你想要做什么,所以很难说是否有更好的方法来做到这一点,但我会在谈到这些地方时指出这些地方。但首先:
这些没有初始化为任何内容。可能会显示为 0,但您应该已经初始化它们。此外,您可能需要考虑将它们放入通过引用传递到循环中的结构中,而不是将它们声明为静态。
由于
lastMsec
没有(初始化并且可能为 0),这可能是一个很大的增量。此行与最后一行一样,调用
msec
。这可能意味着“当前时间”,并且这些调用足够接近,两次调用的返回值可能相同,这可能也是您所期望的,但您仍然调用该函数两次。您应该将这些行更改为int now = msec();
int deltatime = now - lastMsec;
lastMsec = now;
以避免调用此函数两次。当前获取函数的开销通常比您想象的要高得多。您应该在此处发表评论,说明其作用,以及上面的评论
这说明了变量的含义。
这个循环需要注释。它也不需要存在。看来它可以被替换为迭代+=累加器/MSECS_PER_STEP;累加器%= MSECS_PER_STEP;。除法和模数应该比任何具有硬件除法的机器(许多机器都这样做)上的循环运行更短且更一致的时间。
在独立于输入的循环中执行步骤,如果游戏执行缓慢并落后,将会导致游戏无响应。至少,如果游戏落后,所有输入将开始堆积并一起执行,并且所有游戏时间将在一个块中传递。这是一种不太优雅的失败方式。
此外,我可以猜测 j 循环(外循环)的含义,但内循环我不太清楚。另外,传递给 doStep 函数的值——这是什么意思。
这是最后一个花括号。我觉得它看起来很孤独。
我不知道调用您的
loop
函数会发生什么,这可能超出您的控制,并且可能决定该函数的功能和外观,但如果不是的话,我希望您将重新考虑结构。我认为更好的方法是有一个被重复调用但一次只有一个事件的函数(在相对较短的时间内定期发布)。这些事件可以是用户输入事件或计时器事件。用户输入事件只是设置对下一个计时器事件做出反应。 (当您没有任何事件来处理您的睡眠时)您应该始终假设每个计时器事件都是在同一时间段处理的,即使如果处理落后,这里可能会出现一些漂移。您可能会注意到的主要奇怪之处是,如果游戏在处理计时器事件方面落后,然后再次赶上,则游戏中的时间可能会变慢(低于实时),然后加速(达到实时),并且然后放慢速度(实时)。
处理此问题的方法包括一次只允许一个计时器事件位于事件队列中,这将导致时间看起来减慢(低于实时),然后又加速(回到实时),而没有超速间隔。
执行此操作的另一种方法(在功能上与您所拥有的类似)是让处理每个计时器事件的最后一步是将下一个计时器事件排队(请注意,其他人不应该发送计时器事件{除了第一个one}如果这是您选择实现游戏的方式)。这意味着取消计时器事件之间的规则时间间隔,并且还限制程序休眠的能力,因为至少每次检查事件队列时都会有一个计时器事件需要处理。
I just have a few comments. The first is that you don't have enough comments. There are places where it's not clear what you are trying to do so it is difficult to say if there is a better way to do it, but I'll point those out as I come to them. First, though:
These are not initialized to anything. The probably turn up as 0, but you should have initialized them. Also, rather than declaring them as static you might want to consider putting them in a structure that you pass into
loop
by reference.Since
lastMsec
wasn't (initialized and is probably 0) this probably starts out as a big delta.This line, just like the last line, calls
msec
. This is probably meant as "the current time", and these calls are close enough that the returned value is probably the same for both calls, which is probably also what you expected, but still, you call the function twice. You should change these lines toint now = msec();
int deltatime = now - lastMsec;
lastMsec = now;
to avoid calling this function twice. Current time getting functions often have much higher overhead than you think.You should have a comment here that says what this does, as well as a comment above
that says what the variables were meant to mean.
This loop needs a comment. It also needs to not be there. It appears that it could have been replaced with
iterations += accumulator/MSECS_PER_STEP;
accumulator %= MSECS_PER_STEP;
. The division and modulus should run in shorter and more consistent time than the loop on any machine that has hardware division (which many do).Doing steps in a loop independent of input will have the effect of making the game unresponsive if it does execute slow and get behind. It appears, at least, that if the game gets behind all of the input will start to stack up and get executed together and all of the in-game time will pass in one chunk. This is a less than graceful way to fail.
Additionally, I can guess what the
j
loop (outer loop) means, but the inner loop I am less clear on. also, the value passed to thedoStep
function -- what does that mean.This is the last curly brace. I think that it looks lonely.
I don't know what goes on as far as whatever calls your
loop
function, which may be out of your control, and that may dictate what this function does and how it looks, but if not I hope that you will reconsider the structure. I believe that a better way to do it would be to have a function that is called repeatedly but with only one event at the time (issued regularly at a relatively short period). These events can be either user input events or timer events. User input events just set things up to react upon the next timer event. (when you don't have any events to process you sleep)You should always assume that each timer event is processed at the same period, even though there may be some drift here if the processing gets behind. The main oddity that you may notice here is that if the game gets behind on processing timer events and then catches up again the time within the game may appear to slow down (below real time), then speed up (to real time), and then slow back down (to real time).
Ways to deal with this include only allowing one timer event to be in the event queue at one time, which would result in time appearing to slow down (below real time) and then speed back up (to real time) with no super speed interval.
Another way to do this, which is functionally similar to what you have, would be to have the last step of processing each timer event be to queue up the next timer event (note that no one else should send timer events {except for the first one} if this is the way you choose to implement the game). This would mean doing away with the regular time intervals between timer events and also restrict the ability for the program to sleep, since at the very least every time the event queue were inspected there would be a timer event to process.