实现用户输入和处理器速度在游戏行进时间上的解耦。
(摘自《游戏编程模式》)
游戏作为一个实时互动软件,实时是其核心。在程序里,总有那么一段代码一直在软件运行周期里不断重复着以监听着用户的输入,并根据输入进行对应的响应,这就是游戏循环代码。要注意的是,在游戏循环中不会出现类似UI事件循环中的阻塞,即使你不输入,游戏依旧在运行着游戏循环。
试着将游戏逻辑简化为输入处理ProcessInput()、逻辑更新LogicUpdate()、画面渲染Render()、物理更新PhysicUpdate()三个部分(事实上还会有很多模块的循环需要运行),则最简单的游戏循环代码如下:
while(true) { ProcessInput() LogicUpdate(); PhysicUpdate(); Render(); }
如果是单机游戏且是指定设备游玩的情况下,我想这样的游戏循环写法或许不会出现太多问题。但是,当今游戏运行设备、平台众多,性能不同导致其运行的效率也不同。我们在编写游戏循环代码时也要考虑到此游戏运行在低性能和高性能机器上会有什么样的差异、这些差异会导致什么问题。接下来我们将逐个进行讨论。
FPS(Frame Per Second)这是一个频度单位,例如对于屏幕来说,fps代表的就是每秒屏幕刷新多少次。对于游戏循环来说,fps即每秒游戏循环执行多少次。
游戏循环是整个游戏的主干部分。程序大部分的时间在运行着游戏循环中的内容,因此,需要优化好游戏循环中调用的函数。
看看我们上面最简单的游戏代码,你能发现它的问题所在吗?在不同设备运行上述代码时是不一致的。对于性能好的机器,它会在1秒内快速的运行三个函数很多次(例如100次);而对于性能稍差的机器,它只能在1秒内运行5次。这是游戏不能接受的,这会使得游戏对象在不同硬件上所表现的游戏状态不同步(高性能电脑游戏进程快一些;低端电脑游戏进程慢一些)。
让游戏状态同步,我们可以将距离上一次更新的时间传递给状态更新函数,让游戏状态更新函数根据这个差值进行计算并更新游戏状态。这样编写的游戏循环会针对不同设备的不同时间差进行更新,以确保状态的一致性。
double lastedTime=Time.time; while(true) { double current=Time.time; double deltaTime=current-lastedTime; LogicUpdate(deltaTime); PhysicUpdate(deltaTime); Render(); lastedTime=current; }
然而这样的方法同样存在缺陷。下面我们针对缺陷进行分析。
既然要保证不同设备下状态更新次数一致。我们需要限制状态更新相关函数的调用以保证任何机器运行时调用更新函数的频率是一致的(即恒定FPS)。代码如下:
double timeTag=Time.time; double MS_UPDATE_INTERVAL=1000/60; double accumulation=0; while(true) { double current=Time.time; double deltaTime=current-timeTag; timeTag=current; accumulation+=deltaTime; if(accumulation>MS_UPDATE_INTERVAL) { LogicUpdate(MS_UPDATE_INTERVAL); PhysicUpdate(MS_UPDATE_INTERVAL); accumulation=0; } Render(); }
MS_UPDATE_INTERVAL值的设定有一定的讲究。首先,这个值最好是小于 1000 /60 ms (1秒中刷新次数大于60次),刷新频率过低可导致画面跳帧;其次,MS_UPDATE_INTERVAL设定的时间要大于Update处理的时间——我们需要Update完成后再准备进行下一次Update。
我们还有哪些问题可以优化。从上面代码可以看到:Render()代码是没有固定更新步长的,也就是说,状态更新和画面渲染不是在一个时序上,因此,下图的更新次序会导致一些问题的出现。当一次渲染指令在两次更新的中间时,即使前后更新了两次状态,但渲染只执行了一次!这就导致在下一次更新时游戏状态的变化幅度过大,从而导致跳帧。
我们可以借助之前对Update的思路:将时间差传递给渲染引擎,让渲染引擎知道应该渲染到何种程度。
Render(accumulation/MS_UPDATE_INTERVAL);
四种游戏循环的实现:https://blog.csdn.net/qq_38134452/article/details/88738879?spm=1001.2014.3001.5501