关于Unity在运行时使用AddComponent添加脚本后,新脚本的生命周期执行时机探究

今日上班摸鱼水群时,某群友提出了本文标题涉及的问题,即在运行时通过调用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插入这种情况发生

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值