随着计算机速度的飞速增长,软件的运行速度越来越快。但这在某些情况下却未必是件好事。游戏在不同的机器上,或者在不同的环境下运行不能保持相同的恰当速度困扰着许多游戏程序员。尤其有些RPG游戏,在比较高档的机器上运行,按一下键盘,游戏中的主角便走的比飞还快。即使这没有给玩家带来麻烦,也是在挑剔的玩家眼中所不能容忍的。
也不是说一个游戏必须在所有的机器上都必须保持相同帧数,即使是想实现也不切实际。其实只要保证游戏角色运动的频率差不多就可以了。马上你就会想到以一定的频率去控制游戏里的物体的运动。一个直接的想法是直接将控制函数放在时钟中断函数中。不过细想一下……别开玩笑了,这样绝对不行。时钟中断函数不能太复杂,必须保证中断函数的简洁。除了这个,想想在保护模式下(现在的游戏都是保护模式编程的)时钟中断函数所触及的数据,函数都要锁住,这会带来多少问题!
所以比较现实的方法是做两个计数器。一个放在时钟中断里以固定频率递增,而另一个随着每次游戏循环递增。前一个量表示到目前为止应当做完了游戏循环的次数,它通常递增速度应当大于前者;而后一个则是已经做完的游戏循环的次数。在游戏程序的主循环里,我们将反复比较这两个量,如果实际做的游戏循环次数落后了,就让它继续做,直到赶上应当做的数量。否则我们就应该马上停止,这个时候可以等待或去做其它的事,比如显示一帧图片。为什么选择画一帧图片?部分原因是,这个工作通常比较慢,需要一段时间,这样使得实际游戏循环数量又落后于应当做的游戏循环数量;另外还有部分原因是这个过程还包括了等待屏幕垂直回扫的过程,在这段时间里,时钟中断函数也在以正确的频率递增计数器。当然,我们就不能在两个计数器相等时马上继续处理了;而必须等到画完了图以后。但是当我们花完了图,也可以清楚的知道需要做多少次游戏循环来跟上应当做的数量。
下面给出上述算法的粗略的例子程序:
(引用 Grorge Foot 所著的 Allegro Vivce 9.4.3 节的例子)
volatile int target_cycle;
void target_incrementor()
{
target_cycle++;
}
END_OF_FUNCTION (target_incrementor);
int actual_cycle;
int end_game;
void game_loop()
{
LOCK_VARIABLE (target_cycle);
LOCK_FUNCTION (target_incrementor);
install_int_ex (target_incrementor,BPS_TO_TIMER(cycles_per_sec));
end_game = 0;
actual_cycle = target_cycle = 0; /* 初始化 */
do {
draw_one_frame(); /* 画一帧图 */
while (target_cycle > actual_cycle) /*如果实际的要做的落后*/
do_one_game_cycle(); /* 进行游戏循环 */
} while (!end_game);
}
draw_one_frame 画一帧画, 如果需要还要进行垂直回扫同步.
do_one_game_cycle 为完成一个游戏循环, 并累加 `actual_cycle'.
cycles_per_sec 变量保存每秒钟进行的目标游戏循环的数量.
如果你用过Allegro. 其他的应该没什么问题, 关于程序中涉及的Allegro函数,
请自行查阅Allegro 手册
本文参考 George Foot 所著的 Allegro Vivace
Section 9.4.3 Regulating game speed