Unity3D開發中 使用C# 很多文檔都有提到這個「避免使用構造函數 不要在構造函數中初始化任何變數」為什麼?

「7.避免使用構造函數 不要在構造函數中初始化任何變數,使用Awake或Start實現這個目的。即使是在編輯模式中Unity也自動調用構造函數,這通常發生在一個腳本被編譯之後,因為需要調用構造函數來取向一個腳本的默認值。構造函數不僅會在無法預料的時刻被調用,它也會為預設或未激活的遊戲物體調用。」

很多文檔都有提到這個,但不是很理解什麼意思?

構造函數不要初始化變數?

這樣的算嗎:GUI.Button(new Rect(20, 40, 80, 20), "Level 1")

new出來的變數算初始化嗎? 最好能舉出來具體例子,謝謝


MonoBehaviour有兩個生命周期,一個是作為C#對象的周期,一個是作為Component的周期。

構造函數代表第一個,Awake代表第二個。

Editor環境下Editor的代碼和腳本代碼在同一個AppDomain里,對象的生命周期會表現的跟Player環境下不一樣。比如Editor中構造函數被調用的次數和時機跟build出來的遊戲不一樣,這樣就不容易保證正確性。

另外一個關鍵原因是構造函數是在Unity內部的Loading線程上執行的,一是不能使用Unity API,二是需要考慮同步問題,就更難保證正確了。

所以沒什麼事還是別用構造函數,readonly欄位之類只能在構造階段初始化的成員,盡量不要用,設計上需要用也要保證初始化代碼儘可能簡單。


這段話的確是Unity官方文檔(Unity Script Reference)提到的,請著重記憶並在繼承MonoBehaviour的類中養成把Awake()當做構造函數的習慣。但這個也僅如 @eldereal提到的,「只適用在繼承MonoBehaviour的類上」。

箇中理由:Unity可能會在多個時間呼叫該構造函數,所以可能把你的變數的值給衝掉,即使是prefab和inactive的東西都可能被呼叫構造函數。


首先你應該明確這個規定的限制範圍:只是針對MonoBehaviour的派生類,為什麼,因為其他的類就是正常的C#類,你在裡面寫Awake方法,就是個普通的叫Awake的方法,並不會被自動調用。

原因也很簡單,MonoBehaviour代表的是一個組件,在Unity3D裡面組件是必須依附於GameObject存在的,對於這種類你永遠都不會使用new來創建一個實例,而是使用AddComponent或GetComponent方法來獲取。Unity3D的開發人員並不想讓你知道這個類是具體什麼時候new出來的,你只要關心我想要的時候能夠拿到一個引用就可以了。

你引用的這條限制,在我理解來看實際是說:「MonoBehaviour的生命周期並不是從構造函數開始的,而是從Awake開始的」。在構造函數中初始化變數,一般來說目的是將類的狀態設置到一個「初始狀態」,對於MonoBehaviour來說,你相當於在這個類的生命周期之外設定了它的狀態,此時這個代碼就是不嚴謹的,而應該放在Awake裡面去。

因此,在我的理解來看,你應該避免做的是在構造函數里設定任何跟類的內部狀態有關的事情。舉例來說,如果我在做一個計數器,有一個count變數,這個變數就明顯是內部狀態,count=0這句話就應該放在Awake中。另外,程序不是一個類組成的,在實際工程中,有時候一個類的狀態依賴於其他類的狀態,你舉例的GUI.Button方法可以看做設定了GUI類的內部狀態,這個代碼也不應該放在構造函數中(這個例子不好之處在於GUI類的方法只能在onGUI中調用,不能在任何其他地方,包括Awake中使用,如果換用一個其它全局變數可能更能說明問題)

另一方面,我們可以做一些不牽扯到類的內部狀態的事情,舉個例子:

class A{
public int count;
public int price;
public string name;
}

class AComponent : MonoBehaviour{
A inner;

public int Count {get{return inner.count;} set{ inner.count = value;}}
public int Price {...}
public String Name {...}

public AComponent(){
inner = new A();
}

public Awake(){
Count=0;
Price=10;
Name="Something";
}
}

在這樣一個類的構造方法中我創建了一個A對象給了inner,原因在於我這個類的內部狀態並不在inner本身,而是在於inner的內部狀態,此時創建出來inner不是為了設定狀態,而是為了讓幾個屬性永遠都能用而已。實際狀態設定還是在Awake中。

當然這個new也可以寫在Awake中,因為畢竟實際用到這幾個屬性的時候Awake應該是已經調用過了,但這兩者還是有略微的區別,就是何時創建這個對象的問題。如果你做過手游,應該會知道手機環境上new一個東西的效率並不是很高(由於這個東西不是臨時對象,所以不討論GC問題),寫在構造函數里會在資源載入時調用,寫在Awake里會在第一次使用(例如它依附的GameObject第一次變成active)時調用。

假設這個A類佔用很大內存,甚至可能會出現內存不夠的crash,那麼這兩者就有區別了,寫在new中,這個申請內存會在資源載入時,此時遊戲大概會有一個loading的界面,卡一點是可以接受的,如果crash掉了,也會給用戶一個明顯的暗示就是內存不夠導致的crash,是不是關一些後台程序再來試試。而放在Awake中,可能發生的是用戶第一次觸發某個功能,放某個技能,就會卡一下,如果crash掉了,那就是在說我這個功能沒做好,所以崩了,這種體驗比起上一種是要差一些的。

結論:大部分情況下把所有的初始化寫在Awake里就行,寫在構造函數里並不是完全沒有應用場景,但比較少見,是一種具體問題具體分析的優化策略,寫的時候需要腦子清楚,知道自己為什麼要這麼寫。


MonoBehaviour派生出來的類會作為Unity3D中的Component掛載在GameObject上,而GameObject會在編輯器的多個地方被顯示,如場景編輯器內、Prefab選中時等,這些時候都需要調用它們的構造函數來初始化成員變數的默認值,以便在編輯器中顯示它們。也就是說,構造函數不光在遊戲運行時會被調用,它的調用時機是「未知的」。而Awake和Start只會在遊戲運行時被調用,並嚴格定義了它們的調用時機和順序。

所以,構造函數不可以描述遊戲邏輯,請用Awake和Start。


monobehaviour派生出來的類,不允許有構造函數,因為他的構造是unity engine說了算的。類構造之後的參數初始化,是在接入到場景中的時候完成的,這個時候unity會用場景中賦予的變數重新對其賦值。

沒有從monobehaviour派生的類型,可以有構造函數

PS這種問題你可以在官網或者貼吧問啊


想了半天答案,還是覺得貼原文是最好的回答,反覆讀。

概括起來,最關鍵的點,就是很多地方都會涉及到構造函數,對於程序上層來說,這樣的調用時機對上層而言,應該視為未知。都不知道什麼時候被調用的函數,自然不能隨便用。

Never initialize any values in the constructor or variable initializers in a MonoBehaviour script. Instead use Awake or Start for this purpose. Unity automatically invokes the constructor even when in edit mode. This usually happens directly after compilation of a script because the constructor needs to be invoked in order to retrieve default variable values. Not only will the constructor be called at unforeseen times, it might also be called for prefabs or inactive game objects.

Using the constructor when the class inherits from MonoBehaviour, will make the constructor to be called at unwanted times and in many cases might cause Unity to crash.

Only use constructors if you are inheriting from ScriptableObject.

In the case of eg. a singleton pattern using the constructor this can have severe consequences and lead to seemingly random null reference exceptions.

So if you want to implement eg. a singleton pattern do not use the the constructor, instead use Awake. Actually there is no reason why you should ever have any code in a constructor for a class that inherits from MonoBehaviour.


這應該是從外文翻譯來的,那麼生澀的語句。

繼承自MonoBehaviour的類,可以稱為Component,組件的構造函數歸系統管了。暴露給我們的是Awake和Start。

其實在update代碼中應該避免重複的new對象。重複使用的對象只要是可變類型的,盡量用公用變數自己管理。避免構造時間和析構聲明並減少GC壓力。


看上去和ioc容器有關


然而u3d代碼是最讓我頭疼的東西。。入門,碼了下午代碼。。卻發現南轅北轍了。。次奧


推薦閱讀:

Unity3D開發中如何用好單例模式?
開發遊戲的時候 UI是由哪些人來拼的 ?
為什麼用Unity3D開發遊戲是用C#,JS開發而不是用C++?U3D的設計者是怎樣考慮的?
像缺氧、環世界那些小人自動分工機制是怎麼做到的?
unity 2d 點光源/路燈/手電筒效果怎麼做?

TAG:遊戲開發 | Unity遊戲引擎 | C# |