liweizhaolili

Unity脚本的生命周期

用过Unity的人基本都会知道,Unity的脚本从运行开始,会自动执行很多自动的方法。比如awake、start的区别、各种update的区别,你又是否知道呢?渲染的各个流程分别执行什么方法,你又是否知道呢?

先来说说脚本启动时候的事情。

很多人都知道,Awake是比Start先执行的。其实在它们两的中间,还会执行OnEnable方法。所以正确的启动流程,是Awake——>OnEnable——>Start——>各种Update。

对于Awake、OnEnable和Start,除了执行顺序以外,其实还有一些细节上的区别的。

首先是Awake,它的执行时间,是承载着脚本的物体初始化的时候会执行一次。所谓的初始化,对应的是GameObject,而不是脚本本身。所以就算一个GameObject身上挂了一个没有激活的脚本(enabled == false),在GameObject的时候,这个脚本的Awake方法还是会执行的。

而Start方法对应的是初始化完成之后,开始Update之前执行一次。从主线程的执行来说,这个过程其实会比Awake慢一些的。我曾经试过出现这样的问题,我实例化一个预设,然后给预设的某个值(假设是值A)赋了新的值,预设身上本身挂有一个脚本,在Start里面也做了给预设的值A赋了一些默认值。出现的结果是,实例化之后赋的值,被Start时赋的值覆盖了,后来改成在Awake里面赋默认值,就是先赋默认值后赋新值,正确了。所以如果你想在物体创建的时候给他赋默认值,最好是在Awake里面完成。

OnEnable方法除了会比Start早执行以外,它还是会执行多次的。比如说你把一个GameObject设置SetActive为false,再设置为true,OnEnabled方法会再执行一次。这个方法是非常有用的。比如做对象池来存放一些生成的预设,预设上面有涉及到要初始化和生命周期的东西,就可以写在OnEnabled里面,这样每次从池里面取出来,SetActive为true之后,就会先进到OnEnabled方法里面,做各种初始化的操作了。

和OnEnable相对的是OnDisable,当SetActive为false时调用。

OnDestroy与OnDisable不同,它是指组件(挂在物体上的脚本也是组件)销毁时调用的。当然整个GameObject销毁时,组件也会销毁,所以OnDestroy也会调用了。


上面说了生成和销毁时的生命周期,下面讲讲几个Update的区别吧。

首先说一下最正常的Update。这个方法会在脚本执行的时候,每帧调用一次。所谓的每帧,比如你的游戏的运行帧率是60帧每秒的,那么就是约16.67毫秒一帧了。这里说的帧时间,是指不掉帧的情况下。如果你的程序每帧执行的东西太多,导致可变跑道拉长了,就会出现掉帧的情况,那么每帧的时间也会相应的变长,Update调用的频率也会变低了。

然后说一下LateUpdate。LateUpdate和Update其实是一样的,也是每帧调用一次。区别是它会比Update慢一步调用。所以如果你想有些东西虽然同样是每帧调用,但有先后之分的,可以考虑用LateUpdate。

最后说FixedUpdate。虽然同样叫做Update,但FixedUpdate的运行机制和上面两个是不一样的。它并不一定是每帧调用一次的。这个问题就涉及到timeScale,时间缩放的问题。有些人喜欢用timeScale来调整整个游戏的速度,或者使游戏暂停。比如timeScale = 0,出现的现象就是几乎所有直接播放的动画都会停住不动了。在这个时候,Update和LateUpdate还是会每帧执行一次,但FixedUpdate就不会执行了。如果timeScale = 0.5,那么FixedUpdate就会每两帧才执行一次。诸如此类。所以回到刚才那个例子,如果你想用timeScale来实现游戏暂停,又想在暂停的时候使某些动画播放起来,可以考虑在Update里面写逻辑让他动。这其实也只是做暂停的一种思路而已了,还有其他的方法可以实现的。


对于整个游戏应用程序而言,还有这些方法:

OnApplicationPause:游戏暂停时调用。这个暂停可以是我们在编辑器里面点暂停按钮,也可以是手机游戏运行时按home键退出到主菜单。

OnApplicationFocus:游戏获取焦点和失去焦点时调用。这个就很好理解了,比如我们鼠标点击出了应用外面,和再点回去,就会触发这个方法。而这个方法调用的时候是有一个bool的参数的,代表了是获得焦点还是失去焦点。

OnApplicationQuit:整个游戏应用退出的时候会调用。比如pc端的关掉exe,或者在手机上面调用Application.Quit退出应用,都会调用到这里来。

上面是和执行顺序有关系的生命周期方法调用。


下面说说一些和渲染有关系的方法。

OnBecameInvisible和OnBecameVisible。这两个方法将会在某些摄像机看得到该物体,和看不到该物体时调用一次。注意的是,这个看得到指的是所有摄像机之中最少有一个摄像机看到。而看不到指的是所有摄像机都看不到。这两个方法就算脚本的enabled==false一样会执行的,但如果GameObject的SetActive为false,就不能执行了。这样我们就可以做一些特殊的处理,让屏幕外的物体身上的脚本不执行,优化效率。


OnPreCull,这个方法会在摄像机进行计算那些物体在场景内之前调用。懂得3D渲染原理的朋友应该都知道,那些东西是摄像机能看到的范围内的,是靠摄像机的透视矩阵(透视、正交)来决定的,OnPreCull会在调用透视矩阵之前调用。在这个时候,你可以改变摄像机的透视矩阵,让它的可视范围发生变化。

OnPreRender,这个方法会在OnPreCull之后,开始正式渲染场景之前调用。在这个方法调用时,你可以设置一些渲染相关的参数(除了透视矩阵),比如你可以设置一些天空盒颜色啊、雾的颜色啊,之类。

OnPostRender,这个方法会在摄像机渲染完整个场景之后调用。所谓的渲染完场景,并不是指的已经显示在屏幕上面,而是在场景里面、摄像机可视范围内的物体在正常的渲染流程应该看到的样子计算完毕了。

OnRenderObject和OnPostRender很类似,也是在摄像机渲染完整个场景之后调用。区别在与,上面的三个方法OnPreCull、OnPreRender和OnPostRender都必须是挂在摄像机上面才能生效的,而OnRenderObject是不需要挂在摄像机上面就能生效的。

OnRenderImage,这个方法是在所有的渲染都已经完成了,准备输出到屏幕的时候调用的。这个方法其实就是所谓的后期合成特效的核心了。之前在OnPostRender(或者OnRenderObject)执行完毕之后,会生成了一张RenderTexture,这张RenderTexture正常来说就是我们将会在屏幕上面看到的画面了。这时候在OnRenderImage方法调用时,我们还可以对这张RenderTexture进行一些特殊的处理,比如用一些特定的shader对RenderTexture进行扭曲、改变亮度颜色等的操作。最后这个方法再会输出一张RenderTexture,作为最终在屏幕显示的画面。


说完了,挺累的。生命周期虽然简单,但很多细节上的东西,会影响到程序的实际运行效果。估计很多朋友早就知道了,也给不知道的朋友看看。

评论