Unity中Awake和Start的区别分析

发表于2018-11-06
评论0 1.66w浏览
Unity3D初学者经常把Awake和Start混淆,不能清楚的知道这两者之间的区别,为此,本篇文章就给大家详细分析下Awake和Start,帮助大家。

一、官方解释

Awake在脚本被实例化的时候就会被调用(不管脚本是不是enable的),而且在脚本的生命周期中只会被调用一次。Awake是在所有对象实例化之后,所以我们可以放心大胆地去使用诸如GmeObject.Find之类的方法来在Awake中给各个组件之间添加引用 关系。Awake会在所有对象的Start之前调用,但是注意不同对象之间的Awake顺序是不得而知的。

Start是在对象被第一次enable之后,在Update之前调用的,Start在脚本的生命周期中也只可能被调用一次。Start可能不会被立刻调用,比如我们之前没有让其enable,当脚本被enable时,Start才会被调用。

官方文档的建议是:尽量在Awake函数中进行初始化操作,除非有A依赖B,B必须在A实例化之前完成初始化,那么A在Start,B放在Awake中可以保证A在B之后才被初始化(不过个人感觉还是应该尽量都在Awake中进行对象间的引用,然后手动调用Init函数进行初始化,这样可以自己控制初始化的顺序)。

二、几个实验

1.关于Awake,Start,Update的执行时机,以及不被enable时的情况

在场景中创建一个空的Object,把下面的脚本挂上去:
public class StartAwakeTest : MonoBehaviour {  
// Use this for initialization  
void Start () {  
       Debug.Log("Start is called!");  
}  
void Awake()  
{  
    Debug.Log("Awake is called!");  
}  
// Update is called once per frame  
void Update () {  
       Debug.Log("Update is called!");  
}  

直接运行游戏时,输出如下:

和官方文档所说的一致,这个我们早就知道了。不过,如果我们一开始让脚本对象不被激活,最简单的方法就是在编辑器的Inspector面板上,找到对应的脚本前面有一个小的勾选框,默认是被勾选的,就是被激活的,如果取消勾选,那么这个脚本组件就不会被激活。

我们试一下,取消脚本的激活,然后运行:
  
我们看到,Start和Update函数都没有被执行,而Awake函数仍然被执行了。可见,不管Object被不被激活,Awake函数都会被执行。这时,我们手动勾选一下Start Awake Test前面的勾选框,结果就和第一幅图一样啦,Start和Update都开始被执行了。

2.通过脚本创建的对象的Awake和Start运行情况

我们把之前的脚本去掉,然后在对象上挂上这样一个脚本:
using UnityEngine;  
using System.Collections;  
public class CreateObj : MonoBehaviour {  
    //此处通过一个引用来保存对象,因为被取消激活的对象是不能被find函数找到的!!!  
    private GameObject go = null;  
    void Awake()  
    {  
        go = new GameObject("game object");  
    }  
    void Update()  
    {  
        //添加脚本组件,默认不激活  
        if (Input.GetKeyUp(KeyCode.F1))  
        {    
            go.AddComponent<StartAwakeTest>();  
            //只让StartAwakeTest Component 不激活 = 在编辑器里面取消脚本前面的勾选  
            //go.GetComponent<StartAwakeTest>().enabled = false;  
            //直接让Obj不激活  
            go.SetActive(false);  
        }  
        //将其激活  
        if (Input.GetKeyUp(KeyCode.F2))  
        {  
            if (go == null)  
                return;  
            go.SetActive(true);  
        }  
        //将其取消激活  
        if (Input.GetKeyUp(KeyCode.F3))  
        {  
            if (go == null)  
                return;  
            go.SetActive(false);  
        }  
    }  
}  

运行之后,对象虽然创建了,但是没有挂上脚本,我们通过F1按钮,控制其动态添加脚本,这时,会输出Awake,但是由于我们设置了对象是非Active的,所以Start函数并没有调用:

当我们按下F2时,该object被激活,这时Start函数和Update函数会开始执行。输出“Start is called!”,”Update is called”当我们按下F3之后,对象被取消激活,这时,Update不会再被执行。

3.对象第二次被激活时Start会被再次调用吗

当我们再次按下F2,让对象第二次被激活,Start函数还会调用吗?

可见,虽然Object被第二次激活,但是Start函数不会再被调用了!说明Start函数只有在第一次被激活的时候才会被调用!

三、对象初始化的时机

Awake和Start两者都只能在生命周期中被调用一次,而且都是最先调用的,所以研究Awake和Start就是为了研究Unity对象的初始化机制,我们进一步地看一下Uniyt初始化时的流程。

1.Find方法

先来看一个函数,Find。我们知道Unity的Find函数可以根据名字查询到场景中的物体,但是这个物体必须是被激活的,如果我们把这个物体SetActive(false)了,那么这个函数是找不到对应物体的。比如下面的一段代码:
void Awake()  
    {  
        GameObject go = new GameObject("game object");  
        go.SetActive(false);  
        GameObject go1 = GameObject.Find("game object");  
        if (go1 == null)  
            Debug.Log("Can't find!");  
        else   
            Debug.Log("Find!");  
    }  

结果:
为什么要看Find方法呢,因为很多情况下,我们都是通过Find来找到对象之间的对应关系。我们通过Find可以进行下一步的实验:

2.Awake的调用时机

我之前有点搞不懂Awake的调用时机,担心会出现在一个Obj被Awake了,其他的没有被Awake,会造成对象空引用的错误,但是事实上并不是这样,看这样一个例子:

以下两段脚本分别被绑定在两个对象上:
Obj1对象上的脚本:
using UnityEngine;  
using System.Collections;  
public class Component1 : MonoBehaviour {  
    void Awake()  
    {  
        GameObject go = GameObject.Find("Obj2");  
        if (go != null)  
            Debug.Log("Obj2 is found!");  
        go.GetComponent<Component2>().Test();  
    }  
    public void Test()  
    {  
        Debug.Log("Test in Component1 is called");  
    }  
}  

Obj2对象上的脚本:
using UnityEngine;  
using System.Collections;  
public class Component2 : MonoBehaviour {  
    void Awake()  
    {  
        GameObject go = GameObject.Find("Obj1");  
        if (go == null)  
            Debug.Log("Obj1 is  not found!");  
        Debug.Log("Obj1 is found!");  
        go.GetComponent<Component1>().Test();  
    }  
    public void Test()  
    {  
        Debug.Log("Test in Component2 is called");  
    }  
}  

运行结果如下:

可见,在两个对象的Awake函数中,都通过名字查找到了对方的对象,并且调用了对方的函数。这说明在Awake函数调用之前,所有的对象已经创建完毕了!所以我们可以通过这种方式来在Awake函数中放心大胆的设置对象之间的引用关系。

3.Start,Awake,和自定义函数的调用顺序

还有一个疑问,就是如果我们自己通过脚本建立一个对象,然后马上调用其中的一个函数,那么,Start和Awake还会在之前被调用吗?不多说,上代码。
在场景中建立一个对象,挂上下面的脚本:
using UnityEngine;  
using System.Collections;  
public class Component1 : MonoBehaviour {  
    void Awake()  
    {  
        Debug.Log("Awake in Original GameObject is called!");  
    }  
    void Start()  
    {  
        Debug.Log("Start in Original GameObject is called!");  
        GameObject go = new GameObject("new Obj");  
        go.AddComponent<Component2>();  
        go.GetComponent<Component2>().Test();  
    }  
}  

然后准备另一个脚本,供动态生成的对象使用:
using UnityEngine;  
using System.Collections;  
public class Component2 : MonoBehaviour {  
    void Awake()  
    {  
        Debug.Log("Awake in new GameObject is called!");  
    }  
    void Start()  
    {  
        Debug.Log("Start in new GameObject is called!");  
    }  
    public void Test()  
    {  
        Debug.Log("Test in new GameObject is called");  
    }  
}  

结果如图:
Look!Awake函数最先被调用了,然后接着是我们自定义的Test函数,最后才是Start函数!这里应该是很容易出现问题的地方,比如Test函数中要用到一些值,而这些值应该被初始化,如果我们把初始化放在了Start函数中,那么此处这些值还没有被初始化,那么就会出现空引用异常等错误。我之前也是遇到了很多次,查了半天发现都是把对象的初始化放在了Start函数中,结果浪费了大量的时间,这也是我写这篇文章的重要原因之一,希望大家少走弯路!

四、总结

最后总结一下Awake和Start的异同点:
相同点:
  1. 两者都是对象初始化时调用的,都在Update之前,场景中的对象都生成后才会调用Awake,Awake调用完才会调用Start,所有Start调用完才会开始Update。
  2. 两者在对象生命周期内都只会被调用一次,即初始化时被调用,之后即使是在被重新激活之后也不会再次被调用。

不同点:
  1. Awake函数在对象初始化之后立刻就会调用,换句话说,对象初始化之后第一调用的函数就是Awake;而Start是在对象初始化后,第一次Update之前调用的,在Start中进行初始化不是很安全,因为它可能被其他自定义的函数抢先。
  2. Awake不管对象是否是Active,脚本是否enabled都会被调用,可以说是无论如何都会被调用的;而Start如果对象被SetAcive(false)或者enabled= false了是不会被调用的。
来自:https://blog.csdn.net/puppet_master/article/details/50975186

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论