比无限循环+阻止更好的游戏循环?

时间:2009-03-25 09:57:42

标签: language-agnostic

每个游戏教程和游戏框架(甚至是相当新的XNA框架)都以一个永不停止的循环开始,该循环具有等效的DoEvents()以防止操作系统被锁定。

从非游戏角度来看,我觉得这种代码闻起来很时髦 没有更好的选择吗?

- 编辑 -
很多答案说每个程序基本上都是一个循环。没错,但我觉得循环应该由您的操作系统执行,而不是由您执行。只有操作系统具有以最佳方式分配其资源所需的所有信息。或者我在这里错过了一个重点?

9 个答案:

答案 0 :(得分:5)

每个Windows应用程序的核心都是一个如下所示的循环:

BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0 ) 
{
   if (bRet == -1 )
   {
      // handle the error and possibly exit
   }
   else
   {
      TranslateMessage( &msg );
      DispatchMessage( &msg );
   }
}

应用程序的工作 - 而不是操作系统 - 确保分派消息。

正如您可能知道的那样,早期的Windows游戏使用了另一种方法,它不是调用阻塞GetMessage函数,而是调用PeekMessage,然后调用游戏的主处理循环(如果有)没有要处理的消息。使用各种形式的延迟来尝试获得足够的帧速率而不占用100%的CPU。没有一个足够好的计时器来提供平滑的帧速率。

今天,可能没有必要显式编写一个调用DoEvents的循环。现在可能可以通过使用内置的计时器池计时器(由System.Threading.Timer在.NET中公开,并由System.Timers.Timer包装)来获得平滑的帧速率。< / p>

我记得,还有及时获取鼠标和键盘事件的问题。我们使用直接键盘和鼠标访问,而不是依赖于消息循环,因为消息循环通常太慢,有时会导致我们丢失信息。

我多年没有写过游戏了,我不知道.NET作为游戏平台是什么样的。输入仍然是一个问题 - 消息队列的速度不够快,无法提供游戏开发者想要的快速响应。因此,他们绕过了关键任务的消息队列。

答案 1 :(得分:2)

游戏(在大多数情况下)是模拟。传统上,这意味着您更新模拟,呈现其当前状态(例如渲染图形),并重复。这个的自然表示是一个循环。

另一方面,Win32消息泵是一个特定于平台的怪癖,面向构成Windows上大多数应用程序的事件驱动的应用程序。它绝不是所有平台上应用程序的标准,因此并非所有软件都能很好地适应模型并不奇怪。因此,要修改一个典型的程序以适应Win32模型,您通常会使用PeekMessage一次性地耗尽该队列,每次循环一次,直到它为空。或者,您将该逻辑放入单独的线程中并根据需要使用GetMessage。

对于大多数考虑到性能的游戏,没有其他实用的方法可以做到这一点。首先,如果您试图使游戏事件驱动而不是轮询,那么您需要更高的分辨率时间,而Windows可以可靠地提供给您,如果您想要保持许多游戏的高性能。其次,Windows只是游戏编写的平台之一,而重新编写游戏以适应Win32模型只会给专用游戏平台带来不便,而这些游戏平台反而希望规范的游戏循环。

最后,关于“占用100%CPU”的担忧是错误的。大多数现代游戏都设计用于全屏模式,具有独特的硬件访问权限,而不仅仅是其他几个应用程序中的一个。此类游戏的客户实际上要求游戏充分利用他们的硬件,如果故意Sleep()调用或更新依赖于外部计时器唤醒应用程序N次,则无法完成。显然,许多游戏都有例外,例如。设计用于主要在窗口中运行的那些,但重要的是要注意区别。

答案 2 :(得分:1)

正在运行的所有程序永远不会结束循环。虽然在某些情况下你不会与循环交互(如在网络编程或其他小程序中)

您的操作系统是一个等待输入命令的循环。与游戏相同。 Web服务器是一个等待请求的循环。

一个很好的解释来自用户slim on one of my own questions.

答案 3 :(得分:1)

更好的循环是一个无限循环,阻塞调用事件/消息。如果没有什么可以做的,你应该不会睡觉,但在等待操作系统时会阻塞。但是,如果在轮询事件时操作系统确实没有阻塞,则在轮询之间休眠是防止100%CPU使用的唯一解决方案。

答案 4 :(得分:1)

我不知道如何以不同的方式做到这一点?

你的程序执行发生在一个线程内(如果你喜欢的话,就是多个线程)。拥有游戏循环(或win32 GetMessage / DispatchMessage泵)就是等待事件发生然后处理它们。替代方案是注册回调,然后调用HandleAllMyMessages(),这将在内部完成相同的事情......而且这不会真正为你买任何东西。

操作系统无法将程序神奇地分成多个线程,因为它不知道你想在这些线程中做什么。为每个传入的消息生成一个新线程将是性能和同步地狱。

答案 5 :(得分:1)

在Win32上我使用MultiMedia计时器来调用我的gamelogic tick ...

// request 1ms period for timer
g_mmTimer = timeBeginPeriod(1); 
if (g_mmTimer != TIMERR_NOERROR)
    return false;

//start game loop event timer/thread
g_mmTimer = timeSetEvent(10, 10, DoUpdate, 0, TIME_PERIODIC );

或者,您可以在另一个线程中完成所有绘图,以便游戏逻辑自动独立于帧速率。

答案 6 :(得分:0)

消息队列也是无限循环,它使所有程序都成为无限循环。

答案 7 :(得分:0)

MsgWaitForMultipleEventsGetMessage / PeekMessage功能与超时和等待内核对象的能力结合起来,例如WaitableTimer

我已经使用CreateWaitableTimerMsgWaitForMultipleEvents一起设置了我想要的帧速率,以便在过去非常流畅的动画中非常成功地进行消息调度,而无需忙碌等待。我希望其他操作系统有这么好的东西。

答案 8 :(得分:-3)

如果你觉得不舒服, 用类和接口打包它,

这就是我们所说的“开放和关闭原则”: 打开界面永不改变,关闭脏部分。

为什么我们使用循环?因为它是我们电脑的基本结构!