Unity3D中指令碼的執行順序和編譯順序
在Unity中可以同時建立很多指令碼,並且可以分別繫結到不同的遊戲物件上,它們各自都在自己的生命週期中執行。與指令碼有關的也就是編譯和執行啦,本文就來研究一下Unity中指令碼的編譯和執行順序的問題。
事件函式的執行順序
先說一下執行順序吧。
官方給出的指令碼中事件函式的執行順序如下圖:
我們可以做一個小實驗來測試一下:
在Hierarchy檢視中建立三個遊戲物件,在Project檢視中建立三條指令碼,如下圖所示,然後按照順序將指令碼繫結到對應的遊戲物件上:
三條指令碼的程式碼完全一樣,只是做了一點名稱上的區分:
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; } } }
播放遊戲,看看它們的執行順序。如下圖所示,Awake、Update、LateUpdate,無論執行遊戲多少次,它們的執行順序是完全一樣的。
接著我們再做一個測試,把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; } } }
再次執行遊戲,看看它的結果。指令碼的執行順序和以前完全一樣,Script0即便刪除掉了Update方法,但是它也不會直接執行LateUpdate方法,而是等待Script1和Script2中的Update方法都執行完畢以後,再去執行所有的LateUpdate方法。
通過這兩個例子,我們就可以很清楚地斷定,Unity後臺是如何執行指令碼的了。每個指令碼的Awake、Start、Update、LateUpdate、FixedUpdate等等,所有的方法在後臺都會被彙總到一起:
後臺的Awake() { // 這裡暫時按照上圖中的指令碼執行順序,後面會談到其實可以自定義該順序的 指令碼2中的Awake(); 指令碼1中的Awake(); 指令碼0中的Awake(); }
後臺的方法Awake、Update、LateUpdate等等,都是按照順序,等所有遊戲物件上指令碼中的Awake執行完畢之後,再去執行Start、Update、LateUpdate等方法的。
後臺的Update() { // 這裡暫時按照上圖中的指令碼執行順序,後面會談到其實可以自定義該順序的 指令碼2中的Update(); 指令碼1中的Update(); 指令碼0中的Update(); }
指令碼的執行順序
然後我們來看看這樣一種情況:在指令碼0的Awake方法中建立一個立方體物件,然後在指令碼2的Awake方法中去獲取這個立方體物件。程式碼如下:
// 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); } }
如果指令碼的執行順序是先執行Script0,然後再執行Script2,那麼Script2中的Awake就可以正確地獲取到該立方體物件;可是如果指令碼的執行順序是先執行Script2,然後是Script0,那麼Script2肯定會報空指標錯誤的。
那麼實際專案中的指令碼會非常多,它們的先後執行順序我們誰也不知道(有人說是按照棧結構來執行的,即後繫結到遊戲物件上的指令碼先執行。這一點可以從上面的例子中得到,但官方並沒有這麼說,還得進一步深入研究)。但一般的,建議在Awake方法中建立遊戲物件或Resources.Load(Prefab)物件,然後在Start方法中去獲取遊戲物件或者元件,因為事件函式的執行順序是固定的,這樣就可以確保萬無一失了。
另外,Unity也提供了一個方法來設定指令碼的執行順序,在Edit -> Project Settings -> Script Execution Order選單項中,可以在Inspector皮膚中看到如下圖所示:
點選右下角的”+”將彈出下拉視窗,包括遊戲中的所有指令碼。指令碼新增完畢後,可以用滑鼠拖動指令碼來為指令碼排序,指令碼名後面的數字也越小,指令碼越靠上,也就越先執行。其中的Default Time表示沒有設定指令碼的執行順序的那些指令碼的執行順序。
按照上面這張圖的設定,我們再來看一下控制檯的輸出結果,來確認一下我們的設定是否起作用(注意:把Script0指令碼中的Update方法取消註釋):
指令碼的編譯順序
關於指令碼的編譯順序很是頭疼,官方的說法有點模糊,請看官方的解釋:
由於指令碼的編譯順序會涉及到特殊資料夾,比如上面提到的Plugins、Editor還有Standard Assets等標準的資原始檔夾,所以指令碼的放置位置就非常重要了。下面用一個例子來說明不同資料夾中的指令碼的編譯順序:
實際上,如果你細心的話會發現,如果在你的專案中建立如上圖所示的資料夾層次結構時,編譯專案之後會在專案資料夾中生成一些檔名中包含Editor、firstpass這些字樣的專案檔案。比如按照上圖的資料夾結構,我們開啟專案資料夾來看一下產生的專案檔案是什麼樣的?
下面就來詳細探討一下這些個字樣是什麼意思?它們與指令碼的編譯順序有著怎樣的聯絡?
1、首先從指令碼語言型別來看,Unity3d支援3種指令碼語言,都會被編譯成CLI的DLL
如果專案中包含有C#指令碼,那麼Unity3d會產生以Assembly-CSharp為字首的工程,名字中包含”vs”的是產生給Vistual Studio使用的,不包含”vs”的是產生給MonoDevelop使用的。
專案中的指令碼語言 | 工程字首 | 工程字尾 |
C# | Assembly-CSharp | csproj |
UnityScript | Assembly-UnityScript | unityproj |
Boo | Assembly-Boo | booproj |
如果專案中這三種指令碼都存在,那麼Unity將會生成3種字首型別的工程。
2、對於每一種指令碼語言,根據指令碼放置的位置(其實也部分根據指令碼的作用,比如編輯器擴充套件指令碼,就必須放在Editor資料夾下),Unity會生成4中字尾的工程。其中的firstpass表示先編譯,Editor表示放在Editor資料夾下的指令碼。
在上面的示例中,我們得到了兩套專案工程檔案:分別被Virtual Studio和MonoDevelop使用(字尾包不包含vs),為簡單起見,我們只分析vs專案。得到的檔案列表如下:
Assembly-CSharp-filepass-vs.csproj
Assembly-CSharp-Editor-filepass-vs.csproj
Assembly-CSharp-vs.csproj
Assembly-CSharp-Editor-vs.csproj
根據官方的解釋,它們的編譯順序如下:
(1)所有在Standard Assets、Pro Standard Assets或者Plugins資料夾中的指令碼會產生一個Assembly-CSharp-filepass-vs.csproj檔案,並且先編譯;
(2)所有在Standard Assets/Editor、Pro Standard Assets/Editor或者Plugins/Editor資料夾中的指令碼產生Assembly-CSharp-Editor-filepass-vs.csproj工程檔案,接著編譯;
(3)所有在Assets/Editor外面的,並且不在(1),(2)中的指令碼檔案(一般這些指令碼就是我們自己寫的非編輯器擴充套件指令碼)會產生Assembly-CSharp-vs.csproj工程檔案,被編譯;
(4)所有在Assets/Editor中的指令碼產生一個Assembly-CSharp-Editor-vs.csproj工程檔案,被編譯。
之所以按照這樣建立工程並按此順序編譯,也是因為DLL間存在的依賴關係所決定的。
好了,到此為止,我們可以很容易地判斷出上面舉的例項中,指令碼的編譯順序(實際上,我已經把順序寫在了指令碼的檔名中了)
相關文章
- Sql執行順序SQL
- JavaScript的執行順序JavaScript
- DISTINCT 和 TOP合用的執行順序
- JavaScript執行順序分析JavaScript
- 任務執行順序
- for語句執行順序
- laravel Event執行順序Laravel
- 關於describe和test執行順序的翻譯
- mysql 中sql語句關鍵字的書寫順序與執行順序MySql
- linux編譯時和執行時,庫搜尋路徑和順序Linux編譯
- Go包中程式碼執行順序Go
- Jmeter的元件作用域和執行順序JMeter元件
- SQL中rownum和order by的執行順序的問題SQL
- 關於 Promise 的執行順序Promise
- Spring Aop的執行順序Spring
- SQL 語句的執行順序SQL
- CSS規則的執行順序CSS
- Java類的基本執行順序Java
- SQL 執行順序 你懂的SQL
- pipeline的執行順序
- mysql 語句的執行順序MySql
- sql mysql 執行順序 (4)MySql
- SQL語句執行順序SQL
- js執行順序Event LoopJSOOP
- JavaScript for迴圈 執行順序JavaScript
- JavaScript 執行順序淺析JavaScript
- Java中,類與類,類中的程式碼執行順序Java
- sql中的or與and的執行順序問題SQL
- mySQL 執行語句執行順序MySql
- JavaScript程式碼執行順序和資料型別JavaScript資料型別
- Java中如何保證執行緒順序執行Java執行緒
- Java for迴圈中語句執行的順序Java
- C#中Page執行順序:OnPreInit()、OnInit()……C#
- java類內部程式碼執行順序Java
- SQL語句中的AND和OR執行順序問題SQL
- 列定義的順序和列儲存的順序
- wait和notify在鎖競爭中的執行順序AI
- 路由的中介軟體執行順序路由