我目前有一些接近以下针对基于物理的游戏的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
}
}
}
答案 0 :(得分: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
函数会发生什么事情,这可能是你无法控制的,这可能决定了这个功能是什么以及它看起来如何,但如果没有,我希望你会重新考虑这个结构。我认为更好的方法是拥有一个重复调用的函数,但当时只有一个事件(在相对较短的时间内定期发布)。这些事件可以是用户输入事件或计时器事件。用户输入事件只是设置为对下一个计时器事件做出反应。 (当你没有任何事件来处理你的睡眠时)
您应该始终假设每个计时器事件都在同一时间处理,即使处理落后可能会有一些漂移。你可能会注意到的主要奇怪之处在于,如果游戏落后于处理计时器事件然后再次赶上,游戏中的时间可能会减慢(低于实时),然后加速(到实时),并且然后慢下来(到实时)。
处理此问题的方法包括仅允许一个计时器事件一次进入事件队列,这将导致时间显示为减速(低于实时),然后加速(实时)加速超速间隔。
另一种方法是在功能上类似于你所拥有的,处理每个计时器事件的最后一步是排队下一个计时器事件(请注意,没有其他人应该发送计时器事件{除了对于第一个}如果这是你选择实施游戏的方式)。这意味着取消计时器事件之间的常规时间间隔,并限制程序休眠的能力,因为至少每次检查事件队列时都会有一个计时器事件要处理。