这是一个好的FPS独立游戏循环实现吗?

5

我目前有一个接近以下实现的物理游戏独立于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
        }
    }
}

我知道在整数中存储时间相关的内容可能不太合适,但这并不是我在这里询问的问题。 - Nick Van Brunt
1个回答

6

我有几点评论。首先是你的评论不够。在某些地方,你想做什么并不清楚,因此很难说是否有更好的方法去做,但我会在遇到这些情况时指出来。不过,先说:

#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;

这些变量没有被初始化。它们可能会显示为0,但你应该初始化它们。另外,与其将它们声明为静态的,不如考虑将它们放在一个结构体中,并通过引用传递到loop中。

    int deltatime = msec() - lastMsec;

由于lastMsec未初始化(或可能为0),因此这个差值可能很大。

    lastMsec = msec();

这一行和上一行一样,调用了msec函数。这个函数可能是指“当前时间”,而且这两次调用非常接近,返回值很可能相同,这也是你期望的。但是,你调用了两次这个函数。为了避免多次调用此函数,应将这些行更改为int now = msec();int deltatime = now - lastMsec;lastMsec = now;。获取当前时间的函数通常比你想象的要慢得多。

    if (deltatime != 0) {
        iterations = deltatime/MSECS_PER_STEP;
        accumulator += deltatime%MSECS_PER_STEP;
    }

这里应该有一个注释,说明这段代码的作用,以及上面的注释应该解释变量的含义。

    while (accumulator >= MSECS_PER_STEP) {
        iterations++;
        accumulator -= MSECS_PER_STEP;
    }

这个循环需要注释。同时,它也不应该存在。看起来可以用iterations += accumulator/MSECS_PER_STEP; accumulator %= MSECS_PER_STEP;替代。在任何有硬件除法的机器上,除法和取模运算应该比循环更短更一致。

    handleInput(); // gathers user input from an event queue

    for (j=0; j<iterations; j++) {
        for (i=0; i<stepCount; i++) {
            doStep(stepSize/(float) stepCount); // forwards the sim
        }
    }

在循环中独立于输入执行步骤会导致游戏在执行缓慢且落后时变得不响应。看起来,如果游戏落后了,所有输入都会开始堆积并一起执行,所有的游戏时间都将在一个块中过去。这是一种不太优雅的失败方式。
此外,我可以猜测外部循环中的j循环的含义,但是内部循环我不太清楚。另外,传递给doStep函数的值是什么意思呢?
}

这是最后一个花括号。我觉得它看起来很孤单。

关于调用您的loop函数的操作,我不知道发生了什么,可能超出了您的控制,并且可能决定此函数的执行方式和外观,但如果不是,请重新考虑结构。我认为更好的方法是每次只调用一个事件的函数(以相对较短的时间间隔定期发出),而不是像现在一样处理所有事件。这些事件可以是用户输入事件或计时器事件。用户输入事件只是设置下一次计时器事件的反应。 (当没有要处理的事件时,可以睡觉)

您应该始终假设每个计时器事件在同一时间段内处理,即使处理落后可能会有一些漂移。您可能注意到的主要奇怪之处是,如果游戏在处理计时器事件时落后,然后又赶上了,游戏中的时间可能会出现减速(低于实际时间),然后加速(到实际时间),然后再次减速(到实际时间)。

处理此问题的方法包括仅允许一个计时器事件在事件队列中,这将导致时间似乎减速(低于实际时间),然后加速(到实际时间),而没有超级速度间隔。

另一种做法与您现在的功能类似,即每个计时器事件处理的最后一步是将下一个计时器事件排队(请注意,如果您选择实现游戏,则除第一个计时器事件外,其他人都不应发送计时器事件)。这意味着取消计时器事件之间的定期时间间隔,并限制程序的休眠能力,因为至少每次检查事件队列时都会有一个计时器事件需要处理。


非常棒的回答 - 这里有很多值得思考的内容。我会添加一些评论。 - Nick Van Brunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接