Unity3D中腳本的執(zhí)行順序和編譯順序
事件函數(shù)的執(zhí)行順序
先說(shuō)一下執(zhí)行順序吧。
官方給出的腳本中事件函數(shù)的執(zhí)行順序如下圖:
我們可以做一個(gè)小實(shí)驗(yàn)來(lái)測(cè)試一下:
在Hierarchy視圖中創(chuàng)建三個(gè)游戲?qū)ο?,在Project視圖中創(chuàng)建三條腳本,如下圖所示,然后按照順序?qū)⒛_本綁定到對(duì)應(yīng)的游戲?qū)ο笊希?
三條腳本的代碼完全一樣,只是做了一點(diǎn)名稱上的區(qū)分:
using UnityEngine;
using System.Collections;
public class Scring0 : MonoBehaviour
{
void Awake()
{
Debug.Log("Script0 ======= Awake");
}
bool isUpdate = false;
void Update()
{
if(!isUpdate)
{
Debug.Log("Script0 ======= Update");
isUpdate = true;
}
}
bool isLateUpdate = false;
void LateUpdate()
{
if(!isLateUpdate)
{
Debug.Log("Script0 ======= LateUpdate");
isLateUpdate = true;
}
}
}
播放游戲,看看它們的執(zhí)行順序。如下圖所示,Awake、Update、LateUpdate,無(wú)論運(yùn)行游戲多少次,它們的執(zhí)行順序是完全一樣的。

接著我們?cè)僮鲆粋€(gè)測(cè)試,把Script0的Update方法注釋掉??!
using UnityEngine;
using System.Collections;
public class Script0 : MonoBehaviour
{
void Awake ()
{
Debug.Log("Script0 ========= Awake");
}
// bool isUpdate = false;
// void Update ()
// {
// if(!isUpdate)
// {
// Debug.Log("Script0 ========= Update");
// isUpdate = true;
// }
// }
bool isLateUpdate = false;
void LateUpdate()
{
if(!isLateUpdate)
{
Debug.Log("Script0 ========= LateUpdate");
isLateUpdate = true;
}
}
}
再次運(yùn)行游戲,看看它的結(jié)果。腳本的執(zhí)行順序和以前完全一樣,Script0即便刪除掉了Update方法,但是它也不會(huì)直接執(zhí)行LateUpdate方法,而是等待Script1和Script2中的Update方法都執(zhí)行完畢以后,再去執(zhí)行所有的LateUpdate方法。
通過(guò)這兩個(gè)例子,我們就可以很清楚地?cái)喽?,Unity后臺(tái)是如何執(zhí)行腳本的了。每個(gè)腳本的Awake、Start、Update、LateUpdate、FixedUpdate等等,所有的方法在后臺(tái)都會(huì)被匯總到一起:
后臺(tái)的Awake()
{
// 這里暫時(shí)按照上圖中的腳本執(zhí)行順序,后面會(huì)談到其實(shí)可以自定義該順序的
腳本2中的Awake();
腳本1中的Awake();
腳本0中的Awake();
}
后臺(tái)的方法Awake、Update、LateUpdate等等,都是按照順序,等所有游戲?qū)ο笊夏_本中的Awake執(zhí)行完畢之后,再去執(zhí)行Start、Update、LateUpdate等方法的。
后臺(tái)的Update()
{
// 這里暫時(shí)按照上圖中的腳本執(zhí)行順序,后面會(huì)談到其實(shí)可以自定義該順序的
腳本2中的Update();
腳本1中的Update();
腳本0中的Update();
}
腳本的執(zhí)行順序然后我們來(lái)看看這樣一種情況:在腳本0的Awake方法中創(chuàng)建一個(gè)立方體對(duì)象,然后在腳本2的Awake方法中去獲取這個(gè)立方體對(duì)象。代碼如下:
// Script0.cs
using UnityEngine;
using System.Collections;
public class Script0 : MonoBehaviour
{
void Awake ()
{
GameObject.CreatePrimitive(PrimitiveType.Cube);
}
}
// Script2.cs
using UnityEngine;
using System.Collections;
public class Script2 : MonoBehaviour
{
void Awake ()
{
GameObject go = GameObject.Find("Cube");
Debug.Log(go.name);
}
}
如果腳本的執(zhí)行順序是先執(zhí)行Script0,然后再執(zhí)行Script2,那么Script2中的Awake就可以正確地獲取到該立方體對(duì)象;可是如果腳本的執(zhí)行順序是先執(zhí)行Script2,然后是Script0,那么Script2肯定會(huì)報(bào)空指針錯(cuò)誤的。
那么實(shí)際項(xiàng)目中的腳本會(huì)非常多,它們的先后執(zhí)行順序我們誰(shuí)也不知道(有人說(shuō)是按照棧結(jié)構(gòu)來(lái)執(zhí)行的,即后綁定到游戲?qū)ο笊系哪_本先執(zhí)行。這一點(diǎn)可以從上面的例子中得到,但官方并沒(méi)有這么說(shuō),還得進(jìn)一步深入研究)。但一般的,建議在Awake方法中創(chuàng)建游戲?qū)ο蠡騌esources.Load(Prefab)對(duì)象,然后在Start方法中去獲取游戲?qū)ο蠡蛘呓M件,因?yàn)槭录瘮?shù)的執(zhí)行順序是固定的,這樣就可以確保萬(wàn)無(wú)一失了。
另外,Unity也提供了一個(gè)方法來(lái)設(shè)置腳本的執(zhí)行順序,在Edit -> Project Settings -> Script Execution Order菜單項(xiàng)中,可以在Inspector面板中看到如下圖所示:

點(diǎn)擊右下角的"+"將彈出下拉窗口,包括游戲中的所有腳本。腳本添加完畢后,可以用鼠標(biāo)拖動(dòng)腳本來(lái)為腳本排序,腳本名后面的數(shù)字也越小,腳本越靠上,也就越先執(zhí)行。其中的Default Time表示沒(méi)有設(shè)置腳本的執(zhí)行順序的那些腳本的執(zhí)行順序。

按照上面這張圖的設(shè)置,我們?cè)賮?lái)看一下控制臺(tái)的輸出結(jié)果,來(lái)確認(rèn)一下我們的設(shè)置是否起作用(注意:把Script0腳本中的Update方法取消注釋):

腳本的編譯順序
關(guān)于腳本的編譯順序很是頭疼,官方的說(shuō)法有點(diǎn)模糊,請(qǐng)看官方的解釋:

由于腳本的編譯順序會(huì)涉及到特殊文件夾,比如上面提到的Plugins、Editor還有Standard Assets等標(biāo)準(zhǔn)的資源文件夾,所以腳本的放置位置就非常重要了。下面用一個(gè)例子來(lái)說(shuō)明不同文件夾中的腳本的編譯順序:

實(shí)際上,如果你細(xì)心的話會(huì)發(fā)現(xiàn),如果在你的項(xiàng)目中建立如上圖所示的文件夾層次結(jié)構(gòu)時(shí),編譯項(xiàng)目之后會(huì)在項(xiàng)目文件夾中生成一些文件名中包含Editor、firstpass這些字樣的項(xiàng)目文件。比如按照上圖的文件夾結(jié)構(gòu),我們打開(kāi)項(xiàng)目文件夾來(lái)看一下產(chǎn)生的項(xiàng)目文件是什么樣的?

下面就來(lái)詳細(xì)探討一下這些個(gè)字樣是什么意思?它們與腳本的編譯順序有著怎樣的聯(lián)系?
1、首先從腳本語(yǔ)言類型來(lái)看,Unity3d支持3種腳本語(yǔ)言,都會(huì)被編譯成CLI的DLL
如果項(xiàng)目中包含有C#腳本,那么Unity3d會(huì)產(chǎn)生以Assembly-CSharp為前綴的工程,名字中包含”vs”的是產(chǎn)生給Vistual Studio使用的,不包含”vs”的是產(chǎn)生給MonoDevelop使用的。
項(xiàng)目中的腳本語(yǔ)言 工程前綴 工程后綴 C# Assembly-CSharp csproj UnityScript Assembly-UnityScript unityproj Boo Assembly-Boo booproj
如果項(xiàng)目中這三種腳本都存在,那么Unity將會(huì)生成3種前綴類型的工程。
2、對(duì)于每一種腳本語(yǔ)言,根據(jù)腳本放置的位置(其實(shí)也部分根據(jù)腳本的作用,比如編輯器擴(kuò)展腳本,就必須放在Editor文件夾下),Unity會(huì)生成4中后綴的工程。其中的firstpass表示先編譯,Editor表示放在Editor文件夾下的腳本。
在上面的示例中,我們得到了兩套項(xiàng)目工程文件:分別被Virtual Studio和MonoDevelop使用(后綴包不包含vs),為簡(jiǎn)單起見(jiàn),我們只分析vs項(xiàng)目。得到的文件列表如下:
Assembly-CSharp-filepass-vs.csproj
Assembly-CSharp-Editor-filepass-vs.csproj
Assembly-CSharp-vs.csproj
Assembly-CSharp-Editor-vs.csproj
根據(jù)官方的解釋,它們的編譯順序如下:
(1)所有在Standard Assets、Pro Standard Assets或者Plugins文件夾中的腳本會(huì)產(chǎn)生一個(gè)Assembly-CSharp-filepass-vs.csproj文件,并且先編譯;
(2)所有在Standard Assets/Editor、Pro Standard Assets/Editor或者Plugins/Editor文件夾中的腳本產(chǎn)生Assembly-CSharp-Editor-filepass-vs.csproj工程文件,接著編譯;
(3)所有在Assets/Editor外面的,并且不在(1),(2)中的腳本文件(一般這些腳本就是我們自己寫的非編輯器擴(kuò)展腳本)會(huì)產(chǎn)生Assembly-CSharp-vs.csproj工程文件,被編譯;
(4)所有在Assets/Editor中的腳本產(chǎn)生一個(gè)Assembly-CSharp-Editor-vs.csproj工程文件,被編譯。
之所以按照這樣建立工程并按此順序編譯,也是因?yàn)镈LL間存在的依賴關(guān)系所決定的。
好了,到此為止,我們可以很容易地判斷出上面舉的實(shí)例中,腳本的編譯順序(實(shí)際上,我已經(jīng)把順序?qū)懺诹四_本的文件名中了)
小練習(xí)
一個(gè)Unity3d的工程中,最多可以產(chǎn)生多少個(gè)工程文件呢?
4*3*2=24
相關(guān)文章
C#從foreach語(yǔ)句中枚舉元素看數(shù)組詳解
這篇文章主要給大家介紹了關(guān)于C#從foreach語(yǔ)句中枚舉元素看數(shù)組的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-05-05
C# 批量生成隨機(jī)密碼必須包含數(shù)字和字母并用加密算法加密
這篇文章主要介紹了C# 批量生成隨機(jī)密碼必須包含數(shù)字和字母并用加密算法加密,需要的朋友參考下2017-01-01
unity使用socket編程實(shí)現(xiàn)聊天室功能
這篇文章主要為大家詳細(xì)介紹了unity使用socket編程實(shí)現(xiàn)聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
C#實(shí)現(xiàn)ProperTyGrid自定義屬性的方法
這篇文章主要介紹了C#實(shí)現(xiàn)ProperTyGrid自定義屬性的方法,主要通過(guò)接口ICustomTypeDescriptor實(shí)現(xiàn),需要的朋友可以參考下2014-09-09
C#實(shí)現(xiàn)Word轉(zhuǎn)PDF的方法總結(jié)
這篇文章主要為大家詳細(xì)介紹了C#中實(shí)現(xiàn)Word轉(zhuǎn)PDF的常用方法,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,有需要的小伙伴可以參考下2023-10-10

