unity中awak,start,update之間的關係

轉載地址
正式開始學習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函數進行初始化,這樣可以自己控制初始化的順序)。

二.幾個實驗

1.關於Awake,Start,Update的執行時機,以及不被enable時的情況
在場景中創建一個空的Object,把下面的腳本掛上去:
[csharp] view plain copy
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運行情況

我們把之前的腳本去掉,然後在對象上掛上這樣一個腳本:
[csharp] view plain copy
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)了,那麼這個函數是找不到對應物體的。比如下面的一段代碼:
[csharp] view plain copy
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對象上的腳本:
[csharp] view plain copy
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對象上的腳本:
[csharp] view plain copy
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還會在之前被調用嗎?不多說,上代碼。
在場景中建立一個對象,掛上下面的腳本:
[csharp] view plain copy
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();  
}  

}
然後準備另一個腳本,供動態生成的對象使用:
[csharp] view plain copy
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了是
不會被調用的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章