今日上班摸鱼水群时,某群友提出了本文标题涉及的问题,即在运行时通过调用AddComponent方法添加一个新脚本A,A的Awake,Start,Update等生命周期方法会在Add的这一帧执行还是等到下一帧执行。
随后我便马上打开Unity,写了两个测试脚本,具体代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Startup : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Z))
{
gameObject.AddComponent<Test>();
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
private bool flag = true;
private void Awake()
{
Debug.Log("Awake");
}
private void OnEnable()
{
Debug.Log("OnEnable");
}
// Start is called before the first frame update
void Start()
{
Debug.Log("Start");
}
// Update is called once per frame
void Update()
{
if (flag)
{
flag = false;
Debug.Log("Update");
}
}
}
虽然我在Test脚本里调用了Log,但并不是准备通过观察控制台日志信息来测试,而是通过Profiler来精准定位帧信息进行测试,打Log只是为了产生GC方便在Profiler定位到方法调用而已。
接下来开始测试,打开Profiler,启动Deep Profile,选择CPU Usage,调出Hierarchy面板,将Startup脚本挂到场景任意物体上,运行,按下Z键
可以看到有处性能尖峰,定位到尖峰那一帧,查看产生了GC的方法调用
可以看到新脚本的Awake和OnEnable方法都在AddComponent的同时被调用了,而Start虽然也在这一帧被调用了,却并不和Awake和OnEnable处于同一调用链上,至于Update的调用,则并未出现在这一帧。
现在让我们移动到下一帧,看看是否有Update的调用。
可以看到Update的确是在Add后的下一帧被调用了。
现在似乎可以说,对于AddComponent添加的脚本,其Awake,Start,OnEnable是在Add的当前帧被调用的,其中Awake,OnEnable与AddComponent处于同一调用链上,Start会在当前帧稍晚一些的时候被调用,Update则是在Add的下一帧被调用。
正当我以为这就是结论的时候,另一名群友也放出了他的测试结果:Update是在Add的当前帧调用的。
而我与他的测试脚本差异在于,他是在Start时进行Add,而我是在Update时进行Add的。
如此看来,Add的时机会影响到Update的执行时机,为了验证这个猜想,我决定修改下测试脚本,修改后的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Startup : MonoBehaviour
{
private bool flag = true;
// Start is called before the first frame update
void Start()
{
Debug.Log("Startup Start");
gameObject.AddComponent<Test>();
}
// Update is called once per frame
void Update()
{
if (flag)
{
flag = false;
Debug.Log("Startup Update");
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
private bool flag = true;
private void Awake()
{
Debug.Log("Test Awake");
}
private void OnEnable()
{
Debug.Log("Test OnEnable");
}
// Start is called before the first frame update
void Start()
{
Debug.Log("Test Start");
}
// Update is called once per frame
void Update()
{
if (flag)
{
flag = false;
Debug.Log("Test Update");
}
}
}
我决定在启动后的第一帧调用Add,并在这一帧暂停运行,由于Profiler无法收集到场景启动第一帧的信息,因此只能靠控制台的日志输出来进行测试,现在点击运行,查看结果:
从日志输出可以看出,Add的新脚本的Update的确和Add在同一帧执行了。
那么现在可以进行测试结果总结了:对于AddComponent添加的脚本,其Awake,Start,OnEnable是在Add的当前帧被调用的,其中Awake,OnEnable与AddComponent处于同一调用链上,Start会在当前帧稍晚一些的时候被调用,Update则是根据Add调用时机决定何时调用:如果Add是在当前帧的Update前调用,那么新脚本的Update也会在当前帧被调用,否则会被延迟到下一帧调用。
根据上面的总结,似乎还可以作出另一个猜想:Unity是在每次进行整体的Update调用前,去收集所有需要Update的脚本来进行调用,而不允许Update中途有新脚本的Update插入这种情况发生。