Unity全面入门笔记15-生命周期

帧与时间

  • 帧长

在Unity中,一帧是执行两次渲染的间隔时间,Unity会在两次渲染间进行逻辑运算。

默认情况下,帧率是50帧每秒,也就是每0.02秒一帧。一帧的帧长还可以在主界面菜单栏的"Edit"->“Project Settings”->“Time”->"Fixed Timestep"进行修改。

帧的存在相当于给了游戏逻辑一个硬性的规定:要想保证游戏的流畅,每一个帧循环的时间内允许进行的计算是有限的。如果在一帧中的计算量过高,我们必须对这个计算进行处理,将其简化或使其延续在多帧内执行,否则这一帧就会产生卡顿。如果连续多帧的计算量都很高,我们必须对游戏逻辑算法进行优化,否则游戏的运行会极大的受影响。

在脚本中可以使用Time.fixedDeltaTime来访问或修改这个数值。

public static Time.fixedDeltaTime{get;set;}

生命周期

  • 介绍生命周期

对每一个组件,从它被实例化到被销毁,遵循一定的生命周期:

生命周期图

生命周期由两重循环组成:外层的激活循环和内层的帧循环。通过自定义生命周期中不同部分的运作逻辑,我们可以实现多样的效果,这就是通过Unity编程的核心。

  • 激活循环

一个组件的初始化有三种情况:

附加在布置在场景中的GameObject上的组件,在场景被加载时与GameObject一起被初始化。

被附加在Prefab上,在使用Instantiate动态实例化这个Prefab时被初始化。

被使用AddComponent动态附加到GameObject上时被初始化。

在组件的初始化过程中会经历Awake->OnEnable->Start三个生命周期过程。

一个组件的销毁有三种情况:

组件被Destroy时被销毁。

组件所附加的GameObject被Destroy时被销毁。

退出当前场景时被销毁。

在组件销毁过程中会经历OnDisable->OnDestroy两个生命周期过程。

当将一个GameObject的activeSelf设置为false时,其上的Component也会进入OnDisable状态并等待,但此时Component并没有被销毁,重新将activeSelf设置为true又会重新进入OnEnable状态,这样进入OnEnable状态后组件会跳过Start阶段直接进入后面的帧循环。

一个组件的Start阶段在整个生命周期中有且只有一次,且这一次一定发生在OnEnable之后。Awake阶段也有且只有一次,且这一次一定发生在OnEnable之前。

所以说,如果一个组件或它附着的GameObject在游戏开始前就是不激活的,或者我们在Awake中将它设置为不激活的,那么在游戏开始时这个组件只会进入Awake而不会进入Start阶段,直到它被其它脚本动态激活。

除了直接修改组件所在GameObject的activeSelf属性外,组件本身的活性也可以单独修改。

public static Component.enabled{get; set;}

修改组件的enabled属性会导致组件不再进行帧循环,对于每个组件来说,enabled被修改后的经历的阶段和修改activeSelf一致。

组件的销毁或失活并非在使用销毁或失活的语句中进行的,如调用Destroy(this)后,Unity底层会将组件加入待销毁列表,等到经历完剩余的帧周期后,才进入OnDisable和OnDestroy过程,这时才会正式将这个对象移除,从此之后的逻辑才会停止运算。

  • 帧循环

在帧循环中,又可以分成物理层、输入层、逻辑层和渲染层。

物理层包括FixedUpdate和碰撞、触发事件。物理层的时间概念和逻辑层是不同的,在物理层中具有一个用于平衡物理时间和逻辑时间的子循环。

输入层包括鼠标按下、拖动、进入、离开、擡起等事件,注意输入层事件和Input类中的事件遵循两套流程。输入层的具体内容我们会在未来和Input类一起讲解。

逻辑层从Update函数开始,再经历协程的一系列调用,在LateUpdate结束。

渲染层会先渲染游戏中的物体,然后在OnGUI中渲染UI物体。

Update和LateUpdate遵循逻辑时间,在每一帧中执行且只执行一次,LateUpdate的执行总在Update之后。

由于Unity框架遍历各个GameObject的顺序是开发者无法预计的,所以如果我们需要使两个组件在每一帧都执行逻辑,但想让一个组件的逻辑总在另一个之后,我们可以让前者使用LateUpdate。

物理时间的一帧和逻辑时间的一帧不同,逻辑时间的一帧中FixedUpdate不一定执行一次,它可能执行很多次,也有可能不执行。物理时间的运算遵循以下原则:

在每一逻辑帧结束前计算物理时间,使其增加Time.deltaTime * Time.timeScale。物理时间的值在脚本中可以由Time.time获得:

public static float Time.time{get; private set;}
public static float Time.deltaTime{get; private set;}
public static float Time.timeScale{get; private set;}

Time.deltaTime是上一帧经过的实际时间,如果上一帧运算量较少,没有达到一帧的额定时间,那么在补帧后Time.deltaTime恰好等于Time.fixedDeltaTime。如果上一帧产生了卡顿,那么这个数值就会超过Time.fixedDeltaTime。

Time.timeScale是物理时间倍率,改变这个数值可以改变Time.time增长的速度。令Time.timeScale等于0时,Time.time就会停止增长,这也会导致游戏的物理层循环暂停。

每当物理时间经过fixedDeltaTime,就会进行一次物理循环,这就导致物理循环在一逻辑帧不止进行一次。

  • 暂停

由于物理循环和FixedUpdate事件的这种特性,我们常利用Time.timeScale来实现暂停效果:

public class GameManager : MonoBehaviour
{
	private void Update()
	{
		if(Input.GetKeyDown(KeyCode.Space))
		{
			Time.timeScale = Time.timeScale == 0 ? 1 : 0;
		}
	}
}

我们在Update而非FixedUpdate中检测键盘输入并暂停游戏,是因为Update不在物理层循环中,不会因为游戏暂停而停止接受用于恢复暂停的输入信息。

根据Time.timeScale只影响物理层循环的特性可以得知,切换Time.timeScale的值具有以下特征:

timeScale不影响Update和LateUpdate,影响FixedUpdate。

timeScale不影响Time.realtimeSinceStartup,会影响Time.timeSinceLevelLoad和Time.time。

timeScale不影响Time.fixedDeltaTime和Time.unscaleDeltaTime。

timeScale影响异步方法,包括Invoke和Coroutine。

timeScale也影响所有的动画和粒子特效。

deltaTime的获取是基于time的,所以在FixedUpdate中调用deltaTime的值是不会随timeScale改变的,而Update和LateUpdate中反而会受timeScale影响。

借此我们也总结出关于游戏暂停的规律:

将UI等不希望被暂停的逻辑放在Update或LateUpdate,将希望被暂停的逻辑放在FixedUpdate。

对不想被暂停的特效需要做特殊处理。

虽然timeScale不能影响Update和LateUpdate的执行速率,但是如果开发者想要改变它们的速率,可以修改Time.captureFramerate这个属性来修改每秒的帧率(默认为50):

uint Time.captureFramerate{get; set;}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章