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

Posted

技术标签:

【中文标题】这是 FPS 独立游戏循环的良好实现吗?【英文标题】:Is this a good implementation of a FPS independant game loop? 【发布时间】:2010-10-18 16:05:21 【问题描述】:

我目前有一些接近于以下实现的 FPS 独立游戏循环,用于基于物理的游戏。它在我测试过的几乎每台计算机上都运行良好,在帧速率下降时保持游戏速度一致。但是,我将移植到嵌入式设备,这可能会在视频方面更加困难,我想知道它是否仍然会减少芥末。

编辑:

对于这个问题,假设 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
        
    

【问题讨论】:

我知道我可能不应该将时间的东西存储在整数中,但这并不是我在这里真正要问的 【参考方案1】:

我只有几个 cmets。首先是你没有足够的cmets。有些地方不清楚您要做什么,因此很难说是否有更好的方法可以做到这一点,但我会在遇到这些问题时指出这些。首先,虽然:

#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 函数时会发生什么,这可能超出您的控制范围,这可能会决定该函数的作用和外观,但如果不是,我希望您将重新考虑结构。我相信更好的方法是拥有一个重复调用但当时只有一个事件的函数(在相对较短的时间内定期发布)。这些事件可以是用户输入事件或定时器事件。用户输入事件只是设置了对下一个计时器事件作出反应。 (当你没有任何事件来处理你的睡眠时)

您应该始终假设每个计时器事件在同一时间处理,即使如果处理落后,这里可能会出现一些偏差。您可能会注意到这里的主要奇怪之处是,如果游戏在处理计时器事件时落后,然后又赶上来,那么游戏中的时间可能会变慢(低于实时),然后加快(到实时),并且然后放慢速度(到实时)。

处理这个问题的方法包括一次只允许一个计时器事件在事件队列中,这会导致时间看起来变慢(低于实时)然后加速(回到实时)而没有超速区间。

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

【讨论】:

绝妙的答案 - 这里有很多值得深思的地方。我会添加一些cmets。

以上是关于这是 FPS 独立游戏循环的良好实现吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何为游戏循环制作计时器?

这是为 iPhone 游戏制作游戏循环的好方法吗?

FPS 和 UPS 有啥区别?我应该在游戏循环中跟踪 UPS 吗?

小骨Steam销量破10万套,韩国人高潮:“韩国独立游戏的壮举”!

在 GLUT 中与可变 FPS 无关的恒定游戏速度?

Java 游戏循环口吃