使用 C++ 在游戏循环中模拟时间
Posted
技术标签:
【中文标题】使用 C++ 在游戏循环中模拟时间【英文标题】:Simulated time in a game loop using c++ 【发布时间】:2009-12-01 04:02:59 【问题描述】:我的业余爱好是在 C++ 中使用 OpenGL 和 SDL 在 C++ 中构建一个 3d 游戏,并希望了解更多关于这个编程领域的信息。
想知道在游戏运行时模拟时间的最佳方式。显然我有一个看起来像这样的循环:
void main_loop()
while(!quit)
handle_events();
DrawScene();
...
SDL_Delay(time_left());
我正在使用 SDL_Delay 和 time_left() 来保持大约 33 fps 的帧速率。
我原以为我只需要一些全局变量,比如
int current_hour = 0;
int current_min = 0;
int num_days = 0;
Uint32 prev_ticks = 0;
然后是这样的函数:
void handle_time()
Uint32 current_ticks;
Uint32 dticks;
current_ticks = SDL_GetTicks();
dticks = current_ticks - prev_ticks; // get difference since last time
// if difference is greater than 30000 (half minute) increment game mins
if(dticks >= 30000)
prev_ticks = current_ticks;
current_mins++;
if(current_mins >= 60)
current_mins = 0;
current_hour++;
if(current_hour > 23)
current_hour = 0;
num_days++;
然后在主循环中调用handle_time()函数。
它编译并运行(此时使用 printf 将时间写入控制台)但我想知道这是否是最好的方法。有没有更简单或更有效的方法?
【问题讨论】:
“只需要一些全局变量” - 不要。几乎总是有更好的选择,例如struct
包含信息并被传递。
你到底想做什么?我没有尝试过 SDL,但滴答声会取决于处理器速率还是总是锁定到毫秒?您想每 30 秒模拟一分钟吗?
SDL_GetTicks -- 获取自 SDL 库初始化以来的毫秒数。基于此,我尝试每 30 秒模拟一分钟。
像时间一样的全局状态,它被无处不在的所有事物所使用,真的应该在一个全局变量中。否则,您必须将“当前时间和帧数”结构传递给整个游戏中的每个函数。
假设整个游戏需要当前时间和帧数,我很确定它不需要。以我的经验,我在主循环中获得时间并将其传递给更新函数,就是这样。很少有地方真正需要这些数据。
【参考方案1】:
我之前在其他游戏相关的帖子中提到过这一点。与往常一样,请遵循 Glenn Fiedler 在其Game Physics series
中的建议您想要做的是使用您通过累积时间增量获得的恒定时间步长。如果你想要每秒 33 次更新,那么你的恒定时间步长应该是 1/33。您也可以将其称为更新频率。您还应该将游戏逻辑与渲染分离,因为它们不属于一起。您希望能够使用较低的更新频率,同时尽可能快地进行渲染。下面是一些示例代码:
running = true;
unsigned int t_accum=0,lt=0,ct=0;
while(running)
while(SDL_PollEvent(&event))
switch(event.type)
...
ct = SDL_GetTicks();
t_accum += ct - lt;
lt = ct;
while(t_accum >= timestep)
t += timestep; /* this is our actual time, in milliseconds. */
t_accum -= timestep;
for(std::vector<Entity>::iterator en = entities.begin(); en != entities.end(); ++en)
integrate(en, (float)t * 0.001f, timestep);
/* This should really be in a separate thread, synchronized with a mutex */
std::vector<Entity> tmpEntities(entities.size());
for(int i=0; i<entities.size(); ++i)
float alpha = (float)t_accum / (float)timestep;
tmpEntities[i] = interpolateState(entities[i].lastState, alpha, entities[i].currentState, 1.0f - alpha);
Render(tmpEntities);
这可以处理欠采样和过采样。如果您使用此处所做的整数运算,您的游戏物理应该接近 100% 确定性,无论机器有多慢或多快。这是以固定时间间隔增加时间的优点。用于渲染的状态是通过在先前状态和当前状态之间进行插值计算的,其中时间累加器内的剩余值用作插值因子。这确保了渲染是平滑的,无论时间步长有多大。
【讨论】:
这基本上就是我们所做的(在我们的商业产品中)。我们以 10hz 的恒定时间步长运行我们的游戏逻辑,但让渲染线程尽可能快地运行,并在 GPU 准备好时尽快绘制帧。这意味着物理和 AI 和实体逻辑不受渲染速度的影响(否则当渲染陷入困境时,世界将进入缓慢时间)。渲染不是在一个恒定的时间步长上,它只是尽可能快地进行(当然最高 60hz)。 @Crashworks:没错。您不关心渲染的固定时间步长。这会使渲染变得不稳定。 人们也可以认为应该有一个恒定的渲染时间步长,因为可变帧率感觉比一致的帧率更不稳定,而且监视器更新为 30 /60hz(或 PAL 为 25/50),因此任何未与垂直同步间隔对齐的帧都会在屏幕中间“撕裂”。虽然这是非常主观的,所以我们将“等待 vsync”设置为客户可设置的选项。 哈,NTSC 与 PAL 的差异实际上是 SEGA Genesis 欧洲游戏机运行速度比北美游戏机快的原因。所以即使是游戏机也受到了这个问题的困扰,尽管比同一时代的 PC 游戏要少。 @Mads 在上述代码的哪一部分(集成或插值)中完成了碰撞检测?【参考方案2】:除了已经指出的问题(您应该使用时间结构并将其传递给 handle_time() 并且您的分钟将每半分钟递增一次),您的解决方案可以很好地跟踪游戏中运行的时间。
但是,对于需要经常发生的大多数游戏事件,您可能应该将它们基于主游戏循环而不是实际时间,以便它们以不同的 fps 以相同的比例发生。
【讨论】:
【参考方案3】:您真正想阅读的 Glenn 的帖子之一是 Fix Your Timestep!。在查看此链接后,我注意到Mads 将您引导到他的answer 中的同一个地方。
【讨论】:
【参考方案4】:我不是 Linux 开发人员,但您可能想看看使用计时器而不是轮询滴答声。
http://linux.die.net/man/2/timer_create
编辑: SDL好像支持Timers:SDL_SetTimer
【讨论】:
虽然一般来说是个好建议,但这对于游戏开发来说并不是一个好主意。 甚至不像他那样在游戏中显示时间?你真的用轮询代替吗? zildjohn01:为什么计时器对于游戏来说是个坏主意?我假设它们会对性能产生某种影响? 这很糟糕,因为您不希望游戏有一个每 (n) 毫秒执行一次操作的回调——有些帧可能会比 33 早一点完成,而另一些则可能滞后。如果您有一帧需要 37 毫秒,然后将下一帧设置为 33 毫秒,那么您将有效地减慢时间,因为您的两帧(代表 66 毫秒的游戏时间)将超过 70 毫秒的实时时间。最好让游戏循环一直全速运行——从不休眠或暂停——并使用高分辨率实时时钟来标记恒定的时间步长。 我从不建议使用计时器处理帧。他在问时间。以上是关于使用 C++ 在游戏循环中模拟时间的主要内容,如果未能解决你的问题,请参考以下文章