誠如以前所說,虛幻4主要的一些特性都是由UObject穿針引線在一塊兒的,想把虛幻玩到比較深的程度,UObject是早晚要面對、迴避不得的問題,因此,準備在其它主題以前,先把UObject好好弄一下。UObject主要完成了哪些工做呢?私覺得: 編程
UObject體系構建了整個虛幻反射系統的核心,每一個UObject都來自於一個UClass,這個Class能夠是Unreal Header Tool(之後統一遵循官網命名:UHT)生成的,也能夠是來自於Blueprint生成的(UBlueprintGeneratedClass)。反射能夠說是如今主流引擎的構建基礎,對國內多數人而言,可能更熟悉的是Unity透過Mono構建出來的反射,它的重要性不言而喻。 網絡
反射很大一坨的東西,具體就不說了,它最大的做用,至關於在運行時動態生成代碼,能夠省掉不少手寫代碼的工做量。不然像UE這樣複雜的界面,所有Hardcode,100人是絕對不夠的,改一次所需的時間也是沒法接受的。有了反射以後,剩下的不少就是很好理解的一條路就順下來了:屬性編輯器自動生成、自動消息包收發、自動序列化、自動生成BP節點、BP和C++的自動接口交互、自動淺拷貝深拷貝、甚至按照設定規則來進行拷貝……不勝枚舉。 多線程
共通性都是同樣:Get Class,Get Property,或者Get Function,分析Property和Function的屬性,而後,設值、獲取值、Invoke函數…… 異步
UObject構建了虛幻的垃圾回收(GC)系統。GC這東西衆說紛紜,但博主本人持樂觀態度。最近的公司業務就遇到這麼個事兒:腳本里須要發動態包,因而就須要在腳本里手動生成一個動態包,並掛接在包上面。爲了完成這個目的,我就必須在腳本中製做一個生成動態包的節點,而後問題來了,咱們必須還得要一個回收動態包的節點,不然這個動態包就沒法回收……因而最後放棄了,回到了包里加各類Reserved的老路上去……有了GC,多數狀況下都不用管這事兒……你讓一個策劃去理解回收這種事情,就是在給他們添麻煩,那本不是他們業務內的範疇。 編輯器
GC的存在價值,並不是是讓事情變得簡單這麼簡單,更多時候它能讓你節省下不少編程心力,把精力花在真正該關注的地方。真GC出問題時去查錯所帶來的成本,未必比忘寫delete帶來的成本要大,說不定反而更小。 函數
虛幻的垃圾回收系統,基本上就是從Root開始,不斷遍歷全部的Property,標記其爲使用中。最後再遍歷一遍,確認哪些Object沒有標使用中就給它刪掉。基本上,你不須要管這個過程,由於反射的做用,因此相關的信息都是UE自動就幫咱們處理好的。有幾個要注意的: spa
"Singleton",須要一直存在的,直接AddToRoot。 線程
F類自己是不走垃圾回收的,可是F 類內部又有U類,這種狀況下你須要注意AddReferencedObjects。把F類內部的U類給加入到GC樹上。 code
Classes裏的類,標UPROPERTY的UObject屬性會被自動加入到當前類的GC列表裏,但不標的會不會,沒有具體跟,反正習慣隨手寫個就好了。 對象
TArray和TMap裏面的UObject會被自動加入GC列表,可是若是寫的是std::vector和std::map,則應該是不會的,須要手動用AddReferencedObjects加進去。
UObject最後一個做用是構建了虛幻的資源管理體系。包括資源的搜索、資源之間的引用管理,下面詳細展開。
首先要先說一下虛幻的Object命名,因爲資源也都是UObject,因此其命名與UObject是同一個標準。按照如今的要求,是[類名']路徑名/路徑名/Asset名.[包內路徑.]Object本名:[屬性名]['](通常是Object所在類名+一個數字後綴)。好比:
Brush'/Script/Engine.Default__Brush'
BillboardComponent'/Script/Engine.Default__TextRenderActor:Sprite'
/Engine/TemplateResources/MI_Template_BaseGray_03_Metal.MI_Template_BaseGray_03_Metal
這個名稱解析跟UE3和UDK略有不一樣,UE3因爲基於upk來對包進行管理,而又限制Content文件夾下的Upk包不能重名,因此不須要前面的路徑名。UE4基於Asset,Content不一樣子目錄下能夠有同名UAsset,因此路徑名就是不可或缺的了。除此以外,Asset跟UPK沒有太多不一樣,咱們後面說包,也是指的UAsset,雖然看起來這個不像包。
理論上,全部的UObject均可以交由StaticLoadObject來加載(事實上也確實是這麼作的),可是不少類是有基於LoadObject的特殊實現的,好比UClass(Blueprint Class),就必須用StaticLoadClass,而地圖必須使用LoadMap,LevelStreaming這樣地圖相關的加載流程。這些變種的主要區別是會針對相應的狀況作一些特殊的處理和操做。可是核心都繞不開StaticLoadObject。因此搞明白這個StaticLoadObject,實際上就搞明白了虛幻主要的資源組織結構。
主要的流程以下:
解析路徑,找到對應包(UAsset或者UPK),若是還沒加載則加載包。
判斷Object是否已經加載,若是已經加載則直接返回。
對資源包的加載,會把整個資源包的全部Object所有預加載的(建立並調用PreLoad,對於資源等須要PostLoad的調用PostLoad)。同時,加載包時建立ULinkerLoad,這個LinkerLoad會自動分析每一個包與其餘包之間的關聯,經過Imports來記錄本包對其它包的引用,經過Exports記錄本包內的Object。
加載後,看看目標對象是否是個Redirector,若是是Redirector,則說明"曾經有個包在這裏,可是被移動到新地方去了",就重定位到必要的地方。
說了這麼多,其實你明白原理就很簡單:虛幻全部對象都是按照一個包含了路徑、包內路徑、對象類名的惟一名稱來命名的。而虛幻全部的包都是會記錄對其它包的引用的。
因此一旦一個包的路徑、對象的包內路徑、以及對象類名自己發生變化,均可能會致使舊有資源的丟失和重定向。
固然,相關也都有一些機制能夠幫助你過後修正(好比Redirector,Engine.ini裏的Redirector config),可是,那都是補救措施,不能100%保證成功補救。好的狀況,從新定位一下資源引用什麼的就能夠解決,可是最糟糕的狀況下,有可能會致使數據丟失(其中最容易發生的就是由於BP類名修改,致使子BP類沒法找到父BP類而致使子類沒法正常使用只能刪了重來……)
因此,在作UE4的包路徑轉移、資源名修改以前,必定要作好備份工做。最好是把全部原型迭代完畢後,統一進行相似操做,並常常存檔或發SVN、GIT。
Redirector須要提醒一點,虛幻裏進行資源文件從一個文件夾到另外一個文件夾的移動,必定要在編輯器中進行。由於虛幻資源之間的引用關係是經過前面說的Object命名來保證的,而路徑名又是Object的一部分,名稱不對等很容易發生問題。而編輯器移動資源後,有時候會發現移動前所在的路徑下多了一個1KB字節左右小尾巴,這個小尾巴就是Redirector,一樣不要手動刪除,而是要在資源查看器裏,經過對Redirector(須要Filter開啓)的Fix up命令來進行刪除。
先把Redirector打開
而後Fixup,或者
右鍵直接文件夾,Fix up Redirectors in Folder。
因爲包是"Link Load"的,加載過程當中會分析引用,因此若是包比較碎,這裏就會因爲作了更多的文件訪問而致使速度變慢。單若是單個包內數據量較大,則也會致使加載單個包時速度變慢的狀況,歸根到底,就是權衡啊權衡。(通常說來,10個1k > 1個10k)
Transient對象不會被存盤,Transient包(GetTransientPackage)是一個特殊的包,全部臨時對象都應該建立在這個包裏面。
異步實際並不是真正異步。LoadObject加載過程當中有一系列的全局變量,並且這些全局變量維護時沒有任何鎖,因此也沒法真正作到異步加載。因此雖然您看到接口上有LoadPackageAsync,但那個的實現是在主線程每幀區分時間片來實現的。不過話說回來,多線程讀包真的有必要嗎?機械硬盤的訪問速度自己是最大的限制因素,讀包過程當中的多線程,CPU其實幫不上任何忙。
真正大量磁盤或網絡數據的異步加載能夠參考Texture Streaming(爲什麼只有Texture作了Streaming就是由於這玩意兒如今是遊戲最吃資源的了,十個模型的資源量不見得比得上一張貼圖啊),先把Object和少許基本信息看成佔位符加載進來,Object的實際數據則放到其餘線程裏慢慢加載。若是您有相似需求,能夠考慮這個方案。不過感受是不必,好比咱們遊戲經常使用的角色異步加載什麼的,其實走主線程時間片徹底夠了。
編輯器中的資源會被標記爲Standalone,在無引用時仍然存在,其它還有一系列不會被GC的狀況,須要注意。