主循环是一款游戏或者框架的核心以及基础,它会让游戏以及动画看起来是在做实时的运行。几乎所有游戏(除了回合制等几种类型以外)都要基于主循环以及精确的时间控制。
下面就是一个最基本的主循环示例代码:
注意:这里采用的所有代码均是伪代码,仅仅是为了讲解而使用。
void main()
{
bool running = true;
init(); // 初始化整个体系,框架、图形、声音等等
while(running)
{
update(); // 执行游戏逻辑
draw(); // 绘制游戏
if(KeyDown("Escape"))
running = false;
}
exit(); // 退出,关闭各种接口等
}
主循环每次执行的时候,都会调用指定好的函数来执行相应的工作,比如在上面代码中我们设置 update() 来处理游戏逻辑,设置 draw() 来绘制游戏当前的画面。比如下面这个例子
void update()
{
player.x += 1;
}
每一次游戏逻辑函数被触发的时候,都会将玩家角色的水平 x 位置加 1,这样,经过相应的 draw() 方法处理,就会看到角色在横向运动。
但是,上面这个主循环有着明显的问题,那就是:主循环能够被执行的次数是取决于机器配置的,越快的机器,主循环执行的次数越多,那么角色也就运动得越快。
当然,如果我们知道运行游戏的硬件系统是一致的,比如说都运行在某种主机平台上,那么,还是可以直接使用这样的主循环的。
那么,既然这种主循环不是很合理,我们希望游戏在任何系统上都保持一致的速度,那就需要引入基于时间的主循环了。
基于时间的主循环
下面这个主循环例子基于度过的时间,那么,会在不同机器上表现达到一致:
void main()
{
float totaltime = GetTime();
float lasttime = 0;
float deltatime = 0;
init();
while(running)
{
lasttime = totaltime;
totaltime = GetTime();
deltatime = totaltime - lasttime;
update(deltatime);
draw();
if(KeyDown("Escape"))
running = false;
}
shutdown();
}
void update(float deltatime)
{
player.x += deltatime
}
我们通过 GetTime() 取得程序运行的时间,每一次主循环执行的时候我们都取得时间差,然后通过时间差来决定更新距离。
我们用 lasttime 来记录上一次的时间,然后通过 GetTime() 取得当前的时间,然后减去上一次的时间,就得到了 deltatime ——时间差。
totaltime = GetTime(); deltatime = totaltime - lasttime; lasttime = totaltime;
那么还有一种是通过时钟来直接触发的:
基于时钟的主循环
有一些语言或者框架提供了按照时钟触发的方式,可以设定固定的触发时间间隔,比如下面的例子:
var interval; // 声明 interval,用来标识触发器
void main()
{
init();
interval = setInterval(update, 1/30); // 设定 1/30 秒触发一次,执行 update
}
void update()
{
if(KeyDown("Escape")) {
clearInterval(interval); // 退出的话,停止事件跟踪
shutdown();
}
player.x += 1;
draw();
}
可见,这种方式可以通过系统时钟来不断的触发主循环 update(),可以精确的控制游戏的运行状态。它可以保证游戏在任何机器上都以同样的速度运行。现在很多主流的游戏框架都支持通过时钟来触发事件。


赞,更新和循环是游戏能够推进的核心机制