zhouqijie

第一部分:渲染循环

在传统的图形用户界面(GUI)中,画面一般是静止不动的,只有某一时刻会更新外貌。传统的GUI会利用一个称为矩形失效的技术,仅让屏幕上有改动的内容重绘。

而实时渲染的软件需要在屏幕上快速连续地显示图像,即使用渲染循环

示例:

while(!quit)
{
    //...

    //把静止的场景渲染至屏幕外的帧缓冲(背景缓冲)
    renderScene();
    //交换背景缓冲和前缓冲(或是视窗模式下,把背景缓冲复制到前缓冲)
    swapBuffers();
}

第二部分:游戏循环

游戏由很多子系统构成,包括输入输出设备、渲染、动画、碰撞检测、动力学模拟、多人网络等。
在游戏运行时多数子系统都需要周期性提供服务,但是它们所需的频率各不相同,例如动力学模拟需要频繁更新,而高级的人工智能系统只需要1、2秒更新一次。
有很多办法实现游戏子系统的周期性更新。其中最简单的方法就是单一循环更新所有子系统,称为游戏循环

第三部分:游戏循环架构风格

Windows消息泵:Windows上的游戏一般都有一段代码称为消息泵。其基本原理先处理来自Windows的消息,无消息才执行引擎任务。这种方式玩家在移动或者缩放视窗时会卡住游戏。

回调驱动框架:有些游戏引擎是由框架组成的。基于框架的引擎,主循环已经准备好了,但是循环里大部分是空的,需要程序员编写回调函数来填充缺少的细节。

基于事件的更新:事件(event)是游戏状态发生的改变例如按下按钮、角色状态切换等。多数游戏拥有一个事件系统,让哥哥引擎子系统注册其关注的某类事件,事件发生时就可以一一回应。可以使用事件队列。

第四部分:抽象时间线

一、真实时间

可以直接用CPU的高分辨率计时寄存器来度量时间。这种时间线原点定义为计算机上次启动或者重置之时。

二、游戏时间

游戏时间线在技术上独立于真实时间。在正常情况下,游戏时间和真实时间是一致的。

如果要暂停游戏,就可以简单地临时停止对游戏时间的更新。如果要把游戏变成慢动作,可以把游戏时钟更新得慢于实时时钟。

暂停和减慢游戏是非常有用的调试工具。

三、局部时间线和全局时间线

局部时间线一般用于音频或者动画片段。该时间线的原点(t=0)定义为片段的开始。

当在游戏中播放片段时,我们可以加速减速甚至反向播放。所有这些效果都可以视觉化为局部和全局时间线之间的映射。

第五部分:测量及处理时间

一、帧率和时间测量

帧率通常用每秒的帧数(FPS)度量。两帧之间的时间称为时间增量(delta time)或者帧时间(frame time)。

时间增量等于帧率的倒数($Δt = 1/f$)。

二、从帧率到速率

受CPU速度影响的早期游戏:

早期的电视游戏中,并不会尝试在游戏循环中准确度量真实经过的时间。而是忽略时间增量以米每帧设定速率。

这种简单方法的后果是游戏物体看上去的速度完全依赖运行机器的帧率。

基于经过时间的更新:

要开发和CPU速度脱钩的游戏,必须以某些方法度量Δt。

大多数引擎测量的方法就是在帧开始和帧结束时分别用cpu高分辨率计时器取值两次然后作差。

这种方法的问题是上一帧的Δt对于这一帧来说不一定准确。还可能会产生一些问题例如低帧率恶性循环。

使用平均时间增量:

计算连续几帧的平均时间,用来估计下一帧。这种方法能使游戏适应变化的帧率,缓解瞬时效能尖峰的影响。

调控频率:

另一个解决办法是与其估算下一帧的经过时间,不如尝试保证每帧都耗时一样。
如果本帧的耗时比理想时间短,就让主线程休眠直至到达目标时间。如果耗时比理想时间长,就只好等下一个目标时间。

使帧率维持稳定,对一些游戏系统很重要。(例如录播功能)

垂直同步:

如果CRT显示器电子枪在扫描中途交换两个缓冲区,屏幕上半部分显示旧的影像而下半部分显示新的影像,就会导致画面撕裂。

很多引擎会在交换缓冲区之前等待显示器的垂直消隐区间(vertical blanking interval),即电子枪重新回到屏幕上角的时间区间。

垂直同步是另一种帧率调控,它限制了主游戏循环的帧率。



三、高分辨率计时器

大多数操作系统都提供获取系统时间的函数,例如标准C程序库的time()函数,返回从1970年1月1日以来的秒数。但是这类函数的分辨率太低,不适合在实时游戏中度量时间。

现代CPU都有高分辨率计时器,这种计时器通常实现为硬件寄存器。计算自启动或者重置计算机之后经过的CPU周期数(或者倍数周期)。大多数CPU的高分辨率计时器都是64位的,对于3GHz奔腾处理器来说其寄存器每195年才会溢出归零,而32位整数时钟在3GHz下每1.4秒就会归零。

Windows封装的Win32API函数QueryPerformanceCounter()可以读取本CPU的64位寄存器。

高分辨率计时器的漂移:

有些情况下高分辨率计时器也会造成不精确时间测量。例如多核处理器每个核有其独立寄存器,这些计时器会彼此漂移。



时间单位和时钟变量

32位和64位整数时钟

以机器周期(ticks)测量的无符号整数时钟。

64位整数时钟在3GHz的CPU需要大约195年才循环一次。

32位浮点数时钟以及浮点时钟的极限

另一种常见的方法。把较小的持续时间以秒为单位存储为浮点数。

但是应该避免用浮点时钟变量存储很长的持续时间。若使用浮点变量存储自游戏开始至今的秒数,最后会变极不准确,无法使用。适合存储相对较短的持续时间,最多能测量几分钟。

其他时间单位

有些引擎支持把时间设定为游戏自定义单位。

其中一个常见选择是以1/300s为时间单位。



应对断点

当程序员在断点处查看代码时,挂钟时间同时大量流逝。直至程序员继续执行程序时,该帧的持续时间可能会被测量为几秒、几分钟、甚至几小时。

一个简单的解决办法是是,如果检测到某帧的持续时间超过某个预设值,则可假设游戏刚从断点处恢复执行,把增量时间人工设置为1/30s或者1/60s或其他数值。

(END)