我们预备开始设计游戏内核。
通常,游戏和桌面Windows程序不同(扫雷等桌面游戏除外),它不能依靠消息驱动,因为游戏通常只有一个画面,而且需要高速更新。因此,游戏只能靠时钟驱动。大部分游戏都在一个时钟驱动下定时渲染一帧画面。此外,游戏需要接收用户输入,处理游戏事件(比如碰撞检测等),可能还需要AI计算,这些任务都必须巧妙地在两幅画面切换的时间段里处理完毕,因此,一个高效而灵活的调度器就是游戏进程的核心。
调度器由时钟驱动器和任务治理器组成。
时钟驱动器负责在需要的时候向调度器提供准确的虚拟时间,虚拟时间流逝时,游戏就运行,虚拟时间停止时,游戏就暂停,虚拟时间还可以比真实时间慢或者快,以便调节游戏运行速度,所有的任务都依靠虚拟时间,并且它们不知道时钟是否暂停,这样就大大简化了时间的表示和运算,我们可以随时暂停时钟从而能够方便地调试。
任务治理器维护一个任务列表,并且知道下一个即将运行的任务是什么。一共有3种任务:
渲染任务:在游戏运行期间,负责更新画面,在每一帧的结束时间点执行。
帧任务:在每个帧时间段内执行一次的任务,比如绘制背景。
时间任务:在指定的时间点和时间段触发,执行一次或多次,完成后即被删除。
调度器的作用便是把时钟提供的虚拟时间按固定时间段(比如20ms)分成若干帧,在一个帧的时间段里,依次调度所有的帧任务和定时任务,最后执行渲染任务,从而完成一个完整的帧。
我们为什么不用多线程来执行这些任务,把调度功能交给操作系统呢?第一,通常游戏中会有很多个任务,为每个任务创建线程是不经济而且低效的,很多任务只执行一次,大量开销被花在线程的创建和销毁上,而且我们很难控制多线程同步。第二,由于操作系统通常是抢先式多任务,我们无法得知一个任务是否会被另一个任务中断或者能否在一帧内完成。
调度器就像是在模拟一个线程治理器,它必须非常灵活,首先,调度器依次执行队列中的任务,可以精确计算每个任务花费的时间。假如用户硬件配置不高,执行一个完整的帧花费的时间多于预算,则可以在下一个帧里跳过不必要的任务(比如关闭特效,特效任务就是可以跳过的),假如执行一个完整的帧花费的时间小于预算,则可以多执行一些任务,比如多画一些特效或多做一些AI运算等,这些都由调度器自动调节。
调度器的另一个功能是实现平滑地添加和删除任务,通常游戏中的任务更新都很频繁。假如使用多线程,则线程的创建和销毁将耗费大量时间。
调度器执行一帧的流程如下:
从时钟驱动器获得虚拟时间;
执行时间任务;
执行帧任务;
执行渲染任务;
从时钟驱动器获得虚拟时间并计算这一帧的执行时间;
执行清理任务,包括从任务治理器中删除过期任务。
由于只需要用到很少的API,我决定用J2SE来写内核,这样便于调试,等测试无误再移植到MIDP上。
参考:Marshall:"Game Programming Gems 3"
待续 :)