(轉)Unity3D中腳本的執行順序和編譯順序(vs工程引用關系)


自:http://www.cnblogs.com/champ/p/execorder.html

在Unity中可以同時創建很多腳本,並且可以分別綁定到不同的游戲對象上,它們各自都在自己的生命周期中運行。與腳本有關的也就是編譯和執行啦,本文就來研究一下Unity中腳本的編譯和執行順序的問題。

事件函數的執行順序

先說一下執行順序吧。 
官方給出的腳本中事件函數的執行順序如下圖: 
exeOrder

我們可以做一個小實驗來測試一下: 
在Hierarchy視圖中創建三個游戲對象,在Project視圖中創建三條腳本,如下圖所示,然后按照順序將腳本綁定到對應的游戲對象上: 
struct 

三條腳本的代碼完全一樣,只是做了一點名稱上的區分: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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,無論運行游戲多少次,它們的執行順序是完全一樣的。 
exeOrderInstance01


 

接着我們再做一個測試,把Script0的Update方法注釋掉!! 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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方法。 
exeOrderInstance02 



通過這兩個例子,我們就可以很清楚地斷定,Unity后台是如何執行腳本的了。每個腳本的Awake、Start、Update、LateUpdate、FixedUpdate等等,所有的方法在后台都會被匯總到一起: 

1
2
3
4
5
6
7
后台的Awake()
{
     // 這里暫時按照上圖中的腳本執行順序,后面會談到其實可以自定義該順序的
     腳本2中的Awake();
     腳本1中的Awake();
     腳本0中的Awake();
}

后台的方法Awake、Update、LateUpdate等等,都是按照順序,等所有游戲對象上腳本中的Awake執行完畢之后,再去執行Start、Update、LateUpdate等方法的。 

1
2
3
4
5
6
7
后台的Update()
{
     // 這里暫時按照上圖中的腳本執行順序,后面會談到其實可以自定義該順序的
     腳本2中的Update();
     腳本1中的Update();
     腳本0中的Update();
}

腳本的執行順序

然后我們來看看這樣一種情況:在腳本0的Awake方法中創建一個立方體對象,然后在腳本2的Awake方法中去獲取這個立方體對象。代碼如下: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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面板中看到如下圖所示: 
Inspector

點擊右下角的"+"將彈出下拉窗口,包括游戲中的所有腳本。腳本添加完畢后,可以用鼠標拖動腳本來為腳本排序,腳本名后面的數字也越小,腳本越靠上,也就越先執行。其中的Default Time表示沒有設置腳本的執行順序的那些腳本的執行順序。 
Inspector2

按照上面這張圖的設置,我們再來看一下控制台的輸出結果,來確認一下我們的設置是否起作用(注意:把Script0腳本中的Update方法取消注釋): 
exeOrderInstance03


腳本的編譯順序

關於腳本的編譯順序很是頭疼,官方的說法有點模糊,請看官方的解釋: 
phaseFour

由於腳本的編譯順序會涉及到特殊文件夾,比如上面提到的Plugins、Editor還有Standard Assets等標准的資源文件夾,所以腳本的放置位置就非常重要了。下面用一個例子來說明不同文件夾中的腳本的編譯順序:

fileStruct

實際上,如果你細心的話會發現,如果在你的項目中建立如上圖所示的文件夾層次結構時,編譯項目之后會在項目文件夾中生成一些文件名中包含Editor、firstpass這些字樣的項目文件。比如按照上圖的文件夾結構,我們打開項目文件夾來看一下產生的項目文件是什么樣的? 
fileStruct01

下面就來詳細探討一下這些個字樣是什么意思?它們與腳本的編譯順序有着怎樣的聯系?


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間存在的依賴關系所決定的。

好了,到此為止,我們可以很容易地判斷出上面舉的實例中,腳本的編譯順序(實際上,我已經把順序寫在了腳本的文件名中了)

小練習

一個Unity3d的工程中,最多可以產生多少個工程文件呢?

4*3*2=24

寫在最后的話

以上內容參考的文章有: 
1. Unity3D研究院之深入理解Unity腳本的執行順序(六十二) 
2. 全面解析Unity3D自動生成的腳本工程文件

 

工程引用關系:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM