对Unity的生命周期的理解

最近做游戏时遇到了一些问题,经过查询后得知是对Unity生命周期的理解不够到位造成的,特意对Unity的脚本声明周期进行了认真地单项学习,经过学习后也对声明周期有了自己浅显的认识。

首先上一张大家都无比熟悉的一张图

面对这张图的理解每个人可能都是大同小异,首先这是脚本代码的执行编译顺序:

Awake ->OnEable-> Start -> -> FixedUpdate-> Update  -> LateUpdate ->OnGUI ->Reset -> OnDisable ->OnDestroy

看起来很简单明了,但随后一个小小的疑问产生了:什么是Awake什么是Enable 他和Start到底又是怎么样的关系,他们不是都是刚开始的时候进行调用吗,这些疑问一直围绕着我们虽然也不会对我们代码编写产生很大的影响,但是当我们万一遇到了因为生命周期理解不足所导致Debug报错真还是一头雾水。

先说Awake,这是在脚本编译中最先做的事,只要你在物体上挂载了脚本,那么脚本编译的第一件事就是运行Awake,所以Awake本身并没有复杂的含义,他仅仅是脚本实例化的第一件事而已,如果我们没有在脚本中Awake声明方法或者赋值变量那么我们就可以当他是不存在。

但是!!!!!!

我们往往会在Awake进行变量的赋值或者对其他的脚本中的成员进行调用,这是我们有时发现一行经典的报错

!NullReferenceException: Object reference not set to an instance of an object

比如我们场景的物体使用了单例模式后,我们在脚本的Awake中进行调用,这是他就会告诉我物体没有被实例化,只留下一脸懵逼的我问了一句为什么啊???

首先我们浏览下实现单例模式的脚本

public class Singleton<T> : MonoBehaviour where T:Singleton<T>
{
    private static T instance;
    public static T Instance
    {
        get { return instance; }
    }
    protected virtual void Awake()
    {
       
        if (instance != null)
        {
            Destroy(this.gameObject);
        }
        else
            instance = (T)this;
        Debug.Log("父类");
    }
    //判断改单例是否已经生成
    public static bool IsInitialized
    {
        get { return instance != null; }
    }
    protected virtual void OnDestory()
    {
     if(instance==this)
        {
            instance = null;
        }
    }
}

我们会发现 Singleton在Awake中进行了方法的创建,他在Awake中对类的静态Static变量进行了赋值。所以在继承该类的对象中他也会在Awake中进行类的静态变量的赋值。

所以我们由此联想到报错,报错的原因大概率会由于该类中的静态变量并没有进行赋值但我们却进行了调用,从而导致了报错的产生。

那么问题来了,为什么单例类中的Awake为什么没有对静态变量进行赋值或者为什么单例类的Awake没有执行????

答案在于Unity的实例化顺序

看了很多博客查了很多资料我们会发现Unity的实例化顺序是从Hierarchy视图中从对象的树形目录的最底端的最深层开始实例化对象,然后逐个向上实例!其实这种说法并不准确,更准确的来说是脚本的添加或者更新的顺序,由于我们在Unity中创建物体时大多是遵循新物体在Hierarchy视图底层显示,从而导致我们这种不准确的Unity实例化印象。经过实验我们发现:

Test的各个物体分别一次添加Test123三个脚本 ,每个test脚本在Awake输出 Debug.Log("Awake  " + Time.frameCount);经测试确实是按照从下向上的顺序进行实例化的,同时每个脚本中的Enable也Awake是同步进行的,不会分离。当所有的脚本完成了Awake与Enable时才按照实例化顺序进行Start的调用。

随后我们对物体的顺序进行调换

运行后

 得出的输出顺序与实例化顺序依旧没有改变,因此我们得出结论:物体脚本的的实例化顺序只与添加或者更新的顺序有关!!!

说完了Awake下一个就是Enable,Enable顾名思义启动的意思,物体在Unity进行实例化完成后我们要将其激活,因此激活的这一刻就是Enable回调的时机。当我们随后的运行中对其注销取消激活SetActive(false)并且又再次设置为SetActive(true)是Enable将再次进行调用。总结一下Enable可以调用多次只要物体由不被激活状态转向激活状态Enable将被回调。

Start及时在场景所有物体的Awake与Enable全部执行完毕后进行调用,注意是全部的物体实例化完毕并且执行网Awake和Enable后脚本的Start将会依照实例化的顺序一次调用

剩余的就好理解了

FixedUpdate:固定帧更新,在Unity导航菜单栏中,点击“Edit”–>“Project Setting”–>“Time”菜单项后,右侧的Inspector视图将弹出时间管理器,其中“Fixed Timestep”选项用于设置FixedUpdate()的更新频率,更新频率默认为0.02s。


Update:正常帧更新,用于更新逻辑。每一帧都执行,处理Rigidbody时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)FixedUpdate,每固定帧绘制时执行一次,和update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。


LateUpdate:在所有Update函数调用后被调用,和fixedupdate一样都是每一帧都被调用执行,这可用于调整脚本执行顺序。例如:当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。LateUpdate,在每帧Update执行完毕调用,他是在所有update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
OnGUI:在渲染和处理GUI事件时调用。比如:你画一个button或label时常常用到它。这意味着OnGUI也是每帧执行一次。


Reset:在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个默认值。


OnDisable:当物体被销毁时 OnDisable将被调用,并且可用于任意清理代码。脚本被卸载时,OnDisable将被调用,OnEnable在脚本被载入后调用。注意: OnDisable不能用于协同程序。


OnDestroy:当MonoBehaviour将被销毁时,这个函数被调用。OnDestroy只会在预先已经被激活的游戏物体上被调用。注意:OnDestroy也不能用于协同程序。  

总结一下:Unity的生命周期是一个脚本实例化所要经历的所有阶段,我们可以在不同的阶段为其添加不同的任务,但是这些任务要相互协调循序渐进,不能冲突。例如Awake中对其他对象的Awake引用要注意,避免其他被引用对用无实例化导致报错的问题。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值