主循环是一款游戏或者框架的核心以及基础,它会让游戏以及动画看起来是在做实时的运行。几乎所有游戏(除了回合制等几种类型以外)都要基于主循环以及精确的时间控制。
下面就是一个最基本的主循环示例代码:
注意:这里采用的所有代码均是伪代码,仅仅是为了讲解而使用。
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()
,可以精确的控制游戏的运行状态。它可以保证游戏在任何机器上都以同样的速度运行。现在很多主流的游戏框架都支持通过时钟来触发事件。
赞,更新和循环是游戏能够推进的核心机制