有时候即便是官方的文档手册也会让人产生误解,比如本文将要讨论的Unity引擎中的FixedUpdate方法。

This function is called every fixed framerate frame, if the MonoBehaviour is enabled.

而大家似乎也都认为FixedUpdate是在固定的时间间隔执行,不受游戏帧率的影响。其实这么说并不准确,甚至有些误导。因此,我就写这篇文章来聊聊Unity的FixedUpdate方法,或者说来聊聊游戏引擎的定时器实现吧。

0x01 直观印象

我相信各位对FixedUpdate的直观印象首先来自于它的名字。并且常常会和一般Update方法进行对比,以突显它的,呃,特殊性。

其次,有一些实验精神的小伙伴也许会亲自来测试一下这个方法的时间间隔。而Console输出的内容也十分符合他的预期,简直毫厘不差。

170747zt8vltrtvtv8t3p8.png (图片来自网络)
不过,我相信很多笃信FixedUpdate的朋友一定有过类似这样的疑虑:默认情况下FixedUpdate的更新频率是50FPS(0.02s),如果当游戏的更新频率较大时——例如60FPS——那么FixedUpdate有固定的更新频率还稍微可以理解,但是如果游戏本身的更新频率就很低——例如30FPS——那么是怎么保证FixUpdate的更新频率呢?

而Unity的主要逻辑是单线程的(参见Aras的回答),所以也不存在有不同的逻辑循环可能,换句话说Update和FixedUpdate是在同一个线程上调用的。

问题似乎开始变得有趣了。

如果我们来重复一下各位测试FixedUpdate时经常采用的方式:打印两次调用之间的时间间隔,或者是计算每次调用的累积时间,但区别在于我直接使用了真实的时间:

void FixedUpdate()

{Debug.Log(FixedUpdate realTime: Time.realtimeSinceStartup);}

void Update()

{Debug.LogError(Update realTime: Time.realtimeSinceStartup);}

为了以示区别,正常的Log是FixedUpdate,LogError是Update,并且设定整个游戏的更新频率为30FPS。

170748z4ztng2khc3lu4hg.png
通过上图可以发现,FixedUpdate并不是间隔0.02s才调用一次,相反,它有可能在Update之前调用多次。

例如刚启动时,在某次Update之前连续调用了11次FixedUpdate,而之后每次Update调用之前都会调用1~2次FixedUpdate方法,这也很好理解,因为FixedUpdate的频率是50FPS,而我们设定的更新频率为30FPS,FixedUpdate的调用次数势必要多于Update。

所以,FixedUpdate除了用来处理物理逻辑之外并不适合处理其他模块的逻辑,尤其是当大家的潜意识里都笃定它的更新频率是固定的时候。因为这很危险,比如一个需要状态同步的游戏要求按照固定的频率向目标同步状态,如果贸然采用FixedUpdate方法,就会出现上图那样可能在短时间内多次调用的问题。所以最好只在物理逻辑的处理中使用FixedUpdate,而不要滥用。而由于FixedUpdate主要是用于物理逻辑的,因此下文的讨论也主要围绕物理逻辑。

0x02 可变增量时间

ok,那我们来看看游戏引擎的定时器是如何来实现的吧。假设我们手头没有一个现成的游戏引擎,一切都需要自己来实现,那么一个最简单的游戏循环大概就是下面这个样子的。

double lastTime = Timer.GetTime();

...

while (!quit()){

double currentTime = Timer.GetTime();

double frameTime = currentTime - lastTime ;UpdateWorld(frameTime);

RenderWorld();

lastTime = currentTime;

}

这种实现方式使得增量时间和具体的机器设备相关,并且它每一次的时间增量都不一定相同。

当机器十分快时,引擎可能通过线程休眠来保证固定的FPS。

while(timeout > 0.001 deltaTime<timeout)

{//...休眠后计算新的增量时间

}

所以看上去整个游戏保证一个大致的更新频率似乎不难。但是现在问题的关键在于每次更新时的时间增量无法保证相同。而在物理模拟中,保证一个固定的增量时间是十分重要的。这是因为在游戏引擎进行物理模拟时要使用数值积分,而作为最简单的数值积分方法——欧拉法在游戏引擎中大量使用。

170749ii1kz3ugpgpqqo5i.png
上图标明了FixedUpdate是属于物理模拟模块的,同时在主循环的内部,物理模拟的部分还有一个二级循环。

0x04 总结及建议

好了,到了需要总结一下的时候了。

经过上文的分析,我想各位应该都能了解一个游戏引擎是如何实现定时器的了吧,而且在物理模拟时使用一个固定的增量时间也并不神秘,只需要让物理模拟和真实时间解耦,使用一个常量作为其增量时间即可。

同时还要提醒各位,不要滥用Unity的FixedUpdate方法,因为它是用来处理物理模拟的,更重要的是它并非根据真实时间的间隔执行。


锐亚教育

锐亚教育,游戏开发论坛|游戏制作人|游戏策划|游戏开发|独立游戏|游戏产业|游戏研发|游戏运营| unity|unity3d|unity3d官网|unity3d 教程|金融帝国3|8k8k8k|mcafee8.5i|游戏蛮牛|蛮牛 unity|蛮牛