[UE4++] 深入unreal asset loading

posted in: C/C++程式設計, UnrealEngine | 0

本文章使用引擎版本:4.11

當我們將png或其他格式的圖檔放到Content這個folder下面,我們會發現引擎會自動把它轉成uassets的格式,之後我們就可以在editor中很方便的對它進行各種操作。但是,我們到底該怎麼樣用C++把這個檔案動態的讀進來?官方文件對這方面的文件實在是少的可憐,不過沒關係,原始碼就是我們最好的文件,深入引擎源碼閱讀之後,還是可以找到相關的方法。

簡單來說,引擎中所有的asset分為被控管(managed)以及沒被控管(unmanaged)二種:

被控管(managed)的asset副檔名為.uasset、.umap,會出現在editor的content browser中,我們可以直接在editor中對它進行各種操作,這些檔案包UMG、blueprint、material或匯進來的圖檔……等等。另外一些則是沒被控管(unmanaged)的檔案,如png、json……等等其他不是經過引擎處理所產生的檔案。

被控管(managed)跟沒被控管(unmanaged)的檔案其實都是可以動態的用C++讀進引擎裡面的,只是不同的是它們所要求的檔案路徑不同。前者要求的是LongPackageName,而後者要求的是實際的檔案路徑。

被控管檔案(managed)的讀取方法

讀取的方法目前我所知的有二種。

第一種是先在constructor中先拿到Class,然後再NewObject,下面的範例展示如果把一個做好的UMG讀進來:

https://gist.github.com/dorgonman/b669fbc6382e938dc4262d80cb46b56e

其中CreateWidget只是對NewObject做一些檢查跟Widget相關的設定,最終還是會呼叫UUserWidget* NewWidget = NewObject<UUserWidget>(Outer, UserWidgetClass);。widget->AddToViewport()這個方法可以讓這個UMG顯示在營幕上。

只是這個方法有個缺點,就是所有需要用到的uasset都必須要先在constructor中先把其class讀進來。若是在其他地方呼叫ConstructorHelpers::FClassFinder的話則會馬上噴出一個錯誤:UE_CLOG(!ThreadContext.IsInConstructor, LogUObjectGlobals, Fatal, TEXT(“FObjectFinders can’t be used outside of constructors to find %s”), ObjectToFind);

因此要做到真正的動態讀取的話,我們可以使用第二種方法:FStringAssetReference。以下的code試著去讀取UMG、umap、flipbook跟UTexture2D:

 

https://gist.github.com/dorgonman/700b83f04da2ca8865745d9430759e6e

我們可以看到FPackageName::LongPackageNameToFilename,從範例中應該大概可以看出來這個方法的用途是什麼。它把我們餵入的package path轉換成檔案路徑。

餵入PackageFileNamePath:  /Game/UMG/NewWidgetBlueprint
轉成FileNamePath: ../../../../../../Users/dorgon/Documents/Unreal Projects/MyProject/Content/UMG/NewWidgetBlueprint.uasset

上述的方法雖然我們可以發現在editor模式下可以運行的很好,但是在打包出來的版本卻發現會無法讀入UMG或blueprint這二個asset。這是因為在cooked build中,引擎會把這二種assets編譯成帶有_C suffix的Blueprint generated class。因此為了讀取這二類的檔案,我們需要換用下面的方法才行:

https://gist.github.com/dorgonman/b11e2b8fa4900f5ca5e2196521ffcb50

 

必須要注意的是,檔名後面必須帶有_C才行,它指的是該assets是由blueprint所產生的class。

沒被控管檔案(Unmanaged)的讀取方法

其實要讀取這類檔案主要的概念是要PackageFileNamePath轉成FileNamePath,例如:

FPackageName::TryConvertLongPackageNameToFilename(LongPackageName, outRealFilePath, TEXT(“”), false);

之後再拿outRealFilePath去將檔案讀進來:

TArray<uint8> RawFileData;
FFileHelper::LoadFileToArray(RawFileData, *outRealFilePath));

其中RawFileData拿進來之後看要做什麼對應的處理就看自己了,下面提供一個plugin有將png或其他格式的圖檔讀進來的範例:

VictoryPlugin

 

PackageName

或許有人會問,為什麼/Game/開頭就代表讀取Content底下?其實這是因為UnrealEngine4對於檔案命名系統做了一層抽象的管理,我們其實可以複蓋掉/Game/所指向的是哪個folder,從上面的範例我們可以看到FPackageName::TryConvertLongPackageNameToFilename這個方法,其實引擎內部管理了一個TArray<FPathPair> ContentRootToPath;,其預設的內容如下:

https://gist.github.com/dorgonman/5f914e3e68fc072c7384f6d9a354540e

我們可以用下面這個方法來覆概掉/Game/所指向的是哪個folder:

FPackageName::RegisterMountPoint(“/Game/”, FPaths::GameContentDir() + “UGameAssets/Assets/”);

由於TryConvertLongPackageNameToFilename是尋序去match,我們新加入的會再最上面,因此/Game/路徑就會先讀到指向我們所設定的folder了。

基於這個概念,我們也可以加入我們自己的package name,例如,如果我們有dlc被在dlc folder下面的話該怎麼辦?我們只要Register該關鍵字就行了:

FPackageName::RegisterMountPoint(“DLC”, FPaths::GamePersistentDownloadDir());

之後就可以這樣用:

FStringAssetReference testAssetRef = “/DLC/UMG/NewWidgetBlueprint”;
UObject* pObject = testAssetRef.TryLoad();

 

當然若該folder沒用的話,我們還可以UnRegister它:

FPackageName::UnRegisterMountPoint(“DLC”, FPaths::GamePersistentDownloadDir());

Leave a Reply

Your email address will not be published. Required fields are marked *