我发现大多数游戏开发都需要一个主游戏循环,但我不知道为什么它是必要的。难道我们不能实现一个事件监听器并响应每个用户操作吗?然后可以在事件发生时播放动画(等)。
主游戏循环的目的是什么?
我发现大多数游戏开发都需要一个主游戏循环,但我不知道为什么它是必要的。难道我们不能实现一个事件监听器并响应每个用户操作吗?然后可以在事件发生时播放动画(等)。
主游戏循环的目的是什么?
您“需要一个循环,因为否则调用事件侦听器”的论点是站不住脚的。诚然,在任何主流操作系统上,您确实有这样的循环,并且事件侦听器确实以这种方式工作,但是完全有可能制作一个没有任何类型循环的中断驱动系统。
但是您仍然不想以这种方式构建游戏。
使循环成为最吸引人的解决方案的原因在于,您的循环成为实时编程中所谓的“循环执行程序”。这个想法是,您可以使各种系统活动的相对执行率相对于彼此具有确定性。循环的总速率可能由计时器控制,并且该计时器最终可能是一个中断,但是对于现代操作系统,您可能会看到该中断的证据作为等待信号量(或其他一些同步机制)的代码你的“主循环”的一部分。
那么为什么你想要确定性的行为呢?考虑用户输入和坏人 AI 的相对处理速率。如果你把所有东西都放在一个纯粹的基于事件的系统中,就不能保证 AI 不会比你的用户获得更多的 CPU 时间,或者反过来,除非你对线程优先级有一些控制,即使那样,你也是往往难以保持时间一致。
但是,将所有内容放在一个循环中,您就可以保证您的 AI 时间线将按照与用户时间相关的固定关系进行。这是通过从你的循环中调用来实现的,给 AI 一个时间片来决定做什么,调用你的用户输入例程,轮询输入设备以找出你的用户想要的行为,以及呼唤做你的渲染。
使用这样的循环,您必须注意处理每一次传递所花费的时间不会超过实时实际经过的时间。如果您尝试以 100Hz 的频率循环您的循环,那么您的所有循环处理最好在 10 毫秒内完成,否则您的系统会变得不稳定。在实时编程中,它被称为超出您的时间范围。一个好的系统可以让你监控你离超限有多近,然后你可以减轻你认为合适的处理负载。
无论您是否看到,事件侦听器还依赖于某个调用循环。还有谁会打电话给听众?
构建一个显式的游戏循环可以让您完全控制正在发生的事情,这样您就不会依赖于某些工具包/事件处理库在其事件循环中所做的任何事情。
一个游戏循环(高度简化如下)
初始化 做 输入 更新 使成为 环形 清理
这将在游戏绘制的每一帧发生。因此,对于以 60fps 运行的游戏,上述操作每秒执行 60 次。
这意味着游戏运行顺畅,游戏保持同步,并且每个周期的更新/绘制足够频繁。动画只是一种视觉技巧,对象在位置之间移动,但是当播放得足够快时,它们似乎在这些位置之间移动。
如果您只更新用户输入,那么游戏只会在用户提供输入时做出反应。其他游戏组件(例如 AI 游戏对象)不会自行做出反应。因此,循环是更新游戏的最简单和最好的方式。
并非所有类型的游戏都需要专门的主游戏循环。
由于频繁的对象更新和游戏输入精度,动作游戏需要这样的循环。
另一方面,我实现了一个扫雷游戏,并使用窗口
消息作为通知。
这是因为当前的操作系统不是完全基于事件的。即使事物通常表示为事件,您仍然必须创建一个循环,在其中等待下一个事件并无限期地处理它(例如 Windows 事件循环)。Unix 信号可能是您在操作系统级别上最接近事件的东西,但对于这样的事情,它们的效率还不够高。
实际上,正如其他人所指出的那样,需要一个循环。
但是,您的想法在理论上是合理的。你不需要循环。您需要基于事件的操作。
在一个简单的层面上,您可以将 CPU 概念化为具有多个计时器;
当然这些定时器是可以调整的。
实际上,会发生的事情是你会(有点被省略)是这样的:
void int_handler1();
//...
int main()
{
//install interrupt handlers
//configure settings
while(1);
}
游戏的本质是它们通常是模拟,并且不仅根据外部事件做出反应,而且还根据内部过程做出反应。您可以通过重复事件而不是轮询来表示这些内部流程,但它们实际上是等效的:
schedule(updateEvent, 33ms)
function updateEvent:
for monster in game:
monster.update()
render()
与:
while 1:
for monster in game:
monster.update()
wait(33ms)
render()
有趣的是,pyglet实现了基于事件的方法,而不是更传统的循环。虽然这在很多时候都很有效,但有时它会导致性能问题或由时钟分辨率、垂直同步等引起的不可预测的行为。循环更可预测且更容易理解(除非你来自专门的网络编程背景,也许)。
任何可以无限期地坐在那里并响应用户输入的程序都需要某种循环。否则它只会到达程序的末尾并退出。
主循环调用事件监听器。如果你有幸拥有一个事件驱动的操作系统或窗口管理器,那么事件循环就在那里。否则,您编写一个主循环来调解基于 I/O、poll
或的系统调用接口select
与传统事件驱动应用程序之间的“阻抗不匹配”。
PS 由于您使用函数式编程标记了您的问题,您可能想查看Functional Reactive Programming,它在将高级抽象连接到低级、基于事件的实现方面做得很好。
游戏需要实时运行,所以如果它在一个 CPU/核心上连续运行,效果最好。当队列中没有事件时,事件驱动的应用程序通常会将 CPU 让给另一个线程。在 CPU 切换回您的进程之前,可能需要等待相当长的时间。在游戏中,这意味着短暂的停顿和紧张的动画。
两个原因——
即使是事件驱动的系统通常也需要某种循环,它从某种队列中读取事件并将它们分派给处理程序,因此无论如何您最终都会在 Windows 中得到一个事件循环,并且可能会很好地扩展它。
出于动画的目的,您甚至需要对动画的每一帧都进行某种处理。您当然可以使用计时器或某种空闲事件来执行此操作,但您可能最终会在某种循环中创建它们,因此直接使用循环更容易。
我见过确实使用事件处理这一切的系统,它们有一个帧侦听器,用于侦听在每个帧开始时分派的事件。他们在内部仍然有一个很小的游戏循环,但它只是处理窗口系统事件和创建帧事件,