正式开始学习Unity了。当然,第一个遇到的问题就是Awake和Start的问题,之前在网上查过一下这两者的区别,简单记忆了一下,认为自己知道了两者的区别。不过实际用起来,发现对于这两者到底是什么区别,心里还是没底,而且最关键的是木有Unityt的源代码,所以我们只能是通过文档或者是别人的blog来了解,当然,还有一个办法就是自己做一下实验,实践是检验真理的唯一标准。
先来看看Unity官方对于这两个函数的解释:
Awake is called when the script instance is being loaded.
Awake is used to initialize any variables or game state before the game starts. Awake is called only once during the lifetime of the script
instance. Awake is called after all objects are initialized so you can safely speak to other objects or query them using
eg. GameObject.FindWithTag. Each GameObject's Awake is called in a random order between objects. Because of this, you should use
Awake to set up references between scripts, and use Start to pass any information back and forth. Awake is always called before any
Start functions. This allows you to order initialization of scripts. Awake can not act as a coroutine.
Start is called on the frame when a script is enabled just before any of the Update methods is called the first time.
Like the Awake function, Start is called exactly once in the lifetime of the script. However, Awake is called when the script object is
initialised, regardless of whether or not the script is enabled. Start may not be called on the same frame as Awake if the script is not
enabled at initialisation time.
The Awake function is called on all objects in the scene before any object's Start function is called. This fact is useful in cases
where object A's initialisation code needs to rely on object B's already being initialised; B's initialisation should be done in Awake
while A's should be done in Start.Where objects are instantiated during gameplay, their Awake function will naturally be called after
the Start functions of scene objects have already completed.
解释一下:
Awake在脚本被实例化的时候就会被调用(不管脚本是不是enable的),而且在脚本的生命周期中只会被调用一次。Awake是在所有对象实例化之后,所以我们可以放心大胆地去使用诸如GmeObject.Fine之类的方法来在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函数进行初始化,这样可以自己控制初始化的顺序)。
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都开始被执行了。
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不会再被执行。
当我们再次按下F2,让对象第二次被激活,Start函数还会调用吗?(这也是我最关心的)
可见,虽然Object被第二次激活,但是Start函数不会再被调用了!说明Start函数只有在第一次被激活的时候才会被调用!!!
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!"); }结果:
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"); } }
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"); } }运行结果如下:
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函数中,结果浪费了大量的时间,这也是我写这篇文章的重要原因之一,希望大家少走弯路!