Unity中Awake和Start的区别分析
发表于2018-11-06
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的异同点:
相同点:
- 两者都是对象初始化时调用的,都在Update之前,场景中的对象都生成后才会调用Awake,Awake调用完才会调用Start,所有Start调用完才会开始Update。
- 两者在对象生命周期内都只会被调用一次,即初始化时被调用,之后即使是在被重新激活之后也不会再次被调用。
不同点:
- Awake函数在对象初始化之后立刻就会调用,换句话说,对象初始化之后第一调用的函数就是Awake;而Start是在对象初始化后,第一次Update之前调用的,在Start中进行初始化不是很安全,因为它可能被其他自定义的函数抢先。
- Awake不管对象是否是Active,脚本是否enabled都会被调用,可以说是无论如何都会被调用的;而Start如果对象被SetAcive(false)或者enabled= false了是不会被调用的。
如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引