【UE4】加載資源的方式(一)使用引用進行簡單加載


【UE4】加載資源的方式(一)使用引用進行簡單加載

參考資料&原文鏈接

UE4資源加載方式

UE4中資源的引用

UE4資源加載方式

UE4的資源管理

UE4資源簡介

以下內容來自於UE4的資源管理

UE4的資源,就是在工程文件夾下的那些非代碼文件,比如Content下面的網格,材質,藍圖等這些文件,大部分資源是以uasset作為后綴的,也有其他后綴如地圖關卡的umap等。在打包時,這些文件可能會根據平台需要,被cook成更小的平台專用文件,然后被放在后綴是pak的壓縮包里。游戲運行時,程序就會掛載解壓這些pak包,然后加載包中的資源文件來使用。打個不是很合適的比方,Pak包就類似於Unity的AssetBundle包,uasset文件就類似於unity的.meta管理的那些資源文件。

程序在用資源的時候,並不是直接在用這些文件。而是要把這些文件轉化為UObject或其他程序可以用的內存對象。比如網格資源文件,程序用的實際是UStaticMesh對象。而把資源文件,轉變為內存里的UObject對象,就是資源管理做的事情。

UE4資源索引簡介

UE4是通過路徑來關聯索引資源的,就跟操作系統下面的文件一樣,每個資源都有他的唯一路徑。但是這個路徑又和文件路徑稍微有點區別。

v2-3d01f0faa8a4949ac97c924d7d29aad4_720w

比如像上圖這樣的一個靜態網格資源,點擊Copy Reference,再粘貼到文本,可以看到他們路徑是這樣的:StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'

v2-85372c13737c85fa7ade48123dddebcc_720w

我把路徑用顏色標了一下方便描述:

  • 紅色部分:(StaticMesh)

是資源的類型,平時加載資源的時候也可以不要,加載函數內部會截掉這部分只留單引號里面的路徑,所以其實前面這個StaticMesh或Blueprint可以不寫,在編輯器Content Browser里右鍵點資源拷路徑是有這部分的,我猜引擎留着這部分可能是為了用戶清楚他的類型是什么,更方便識別一些,這個只是給人看的,程序不看這個。

  • 綠色部分:(/Game)

是資源的分區。大部分資源的路徑都是以/Game開頭,這個其實表示這個資源是在游戲的Content目錄下面,也可以是/Engine就表示引擎下面的,比如下圖,或者對應插件目錄

  • 藍色部分:(/Geometry/Meshes)

就跟操作系統文件管理器中的一樣,表示資源在哪個文件夾下面

  • 黃色部分:(1M_Cube)

資源的包(Package)名,也就是這個資源所在的真實物理資源文件(uasset/umap)的名字,包其實就是UE4將對象按照自己的規則序列化到磁盤上的文件,在Content Browser里看到的每一個文件都是一個包

  • 紫色部分:(1M_Cube)

資源的對象名,因為物理的資源文件里面可能有多個對象,這個名字可以唯一標識包的內部每個對象的唯一名字。比如藍圖資源里有多個UObject,一個關卡文件里有多個Actor,一個UI藍圖里有多個控件。如果不寫,UE4的某些接口會默認以包名補充到后面,也就是說默認使用和包名相同的對象名,但有的接口又可能不做處理,所以還是建議寫。如果用的不是默認對象,而是資源對象的類,就要在后面加一個_C,如果是CDO對象,就要在前面加Default__

  • 冒號后面的部分

有些資源路徑后面會帶冒號:接一個文件名,這種其實是對象的子對象名,有的資源對象內部有子對象,比如C++類里的子類,這個不常用,知道即可。

v2-0df11bbde02ad5d25212947179372fac_720w

資源加載的大致步驟

程序在用資源的時候,並不是直接在用這些文件。而是要把這些文件轉化為UObject或其他程序可以用的內存對象。比如網格資源文件,程序用的實際是UStaticMesh對象。而把資源文件,轉變為內存里的UObject對象,就是資源管理做的事情。

對於UE4來說,這個過程大概有這幾個步驟:

  1. 讀取資源文件的數據到內存。
  2. 根據內存的二進制數據,把空殼對象反序列化成實際的對象。
  3. 如果這個對象有依賴其他對象,就遞歸的去做1和2的操作,直到這個對象完整可用。
  4. 調用對象的初始化函數,並將對象加入到引擎的對象管理中。

資源的卸載

默認情況下,加載中的資源由引擎持有引用,不會被卸載,加載完成后的資源會依賴引擎的gc卸載。如果沒有被使用到,會在下次gc的時候釋放掉。如果需要立即釋放可以手動強制引擎gc。

但是可能有時候由於內存或其他各種原因,只想立即釋放掉指定資源的內存,不想調用全局gc導致游戲產生卡頓,這時候要怎么辦呢?我查了引擎官方文檔以及網上各種文章,基本沒有明確給出答案,下面我會根據以往我的經驗介紹一些做法(野路子),不代表正確,但實測確實能立即釋放掉內存,只是要自己額外花一些功夫保證安全。

  1. 大部分UObject,可以手動調用ConditionalBeginDestory,這里會主動先把對象的資源清理掉,留下一個空殼UObject。可以看引擎源碼,這個函數相當於主動調用BeginDestroy,在對象gc時候也會調用,但是因為是Conditional的,如果之前已經調用過,在gc的時候就不會再次調用BeginDestroy從而避免了邏輯錯誤。通過這種方式,業務需要自行保證對象不再被使用,否則會出BUG。
  2. 如果是貼圖,材質或Mesh等資源,可以直接調用ReleaseResource,這里內部會先把貼圖或網格內存先釋放,執行結束之后留下一個空殼UObject,對於空殼UObject其實就不怎么占用內存了,等待gc的時候銷毀即可。另外因為有的ReleaseResource會Flush渲染命令隊列可能產生卡頓,所以可以盡量把ReleaseResource這個函數提交到渲染命令隊列,雖然內存釋放不那么及時,但也比gc快很多,大部分情況一幀內就會釋放掉。通過這種方式,業務也需要自行保證對象不再被使用,否則會出BUG。
  3. 如果是RenderTarget,引擎有提供專門的池,可以回收復用。
  4. 如果是動態材質,也有專門的函數可以刪除。
  5. 如果是Actor或ActorComponent等,有專門的刪除函數,可以調用DestroyActor或DestroyComponent,基本上能釋放掉大部分資源。對於SceneComponent可以主動調用DestroyPhysicsState和DestroyRenderState釋放掉物理或場景中的對象,也能立即釋放一些內存。

硬性引用、軟性引用、構造時引用

ue4提供了許多種機制來控制引用資產的方式並通過擴展將其裝入內存。

這些引用分為兩種方式:

硬性引用:即對象 A 引用對象 B,並導致對象 B 在對象 A 加載時加載。例如:被“硬”指針指向的資源都會被UE4在啟動的時候進行載入,要注意性能問題。有關硬性引用的一個注意事項是,當對象加載並實例化時,以硬性方式引用的資產會自動加載,可能導致內存使用量迅速增加。

軟性引用:即對象 A 通過間接機制(例如字符串形式的對象路徑)來引用對象 B,軟引用的好處是可以按照需要加載。

構造時引用:在構造時加載資產並賦給變量。

直接屬性引用

這是最常見的資產引用情況,並通過 UPROPERTY 宏公開,面板會公開一個UPROPERTY。

EditDefaultsOnly說明此屬性可通過屬性窗口進行編輯,但只能在原型上進行。此說明符與所有"可見"說明符均不兼容。

UPROPERTY(EditDefaultsOnly, Category = "Test Texture")
TSoftObjectPtr<UTexture2D> TestImg;

image-20211103150434741

間接屬性引用

間接引用有兩種方式:FStringAssetReferencesTAssetPtr

美術師或設計師引用資源的最簡單的方法是,創建一個UProperty 強指針,並賦予其一個類目。在虛幻引擎4中,如果您使用強指針UObject 屬性引用一個資源,那么當加載包含該屬性的對象時將會加載那個資源(通過把對象放置在地圖中,或者通過從類似於游戲信息這樣的東西中進行引用該對象)。

FStringAssetReferences

FStringAssetReferences是一個簡單的結構體,包含了一個具有資源完整名稱的字符串。如果在類中創建一個那種類型的屬性,那么它將會顯示在編輯器中。

在Editor中,他的表現就是一個允許異步加載的UObject*。它還可以正確的處理烘焙和重定向。

示例:

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	FStringAssetReference SAR;

image-20211103115749978

可以看出這個的確是支持UObject的,什么都能選。

或者你也可以在代碼里面這樣寫,動態加載。

void UWC_TestUI::Test()
{
	FStringAssetReference MyCharacterBP = "Blueprint'/Game/Core/BP_MyCharacter.BP_MyCharacter'";
	//嘗試從路徑中查找資源
	UObject* MyCharacterObj = MyCharacterBP.ResolveObject();
	//轉成藍圖類型
	UBlueprint* MyCharacterPtr = Cast<UBlueprint>(MyCharacterObj);
	if (MyCharacterPtr != nullptr)
	{
		GetWorld()->SpawnActor<AMyCharacter>(MyCharacterPtr->GeneratedClass);
	}
}

FStringAssetReference類的作用主要是通過一個字符串,找到該字符串所對應的資源。或者通過給定的資源,找到該資源所對應的在項目中的路徑,也就是前面所說的字符串。

其中,asset.ResolveObject就是查找字符串對應的資源,返回一個UObejct,我們通過將其轉化成UBlueprint類型然后再去的他的GenerateClass即可。

UObject常用的函數有:

	/**
	 * Attempts to find a currently loaded object that matches this path
	 *
	 * @return Found UObject, or nullptr if not currently in memory
	 */
	UObject* ResolveObject() const;

嘗試查找當前加載的與此路徑匹配的對象。

返回找到的UObject,如果當前不在內存中則返回nullptr。

	/**
	 * Attempts to load the asset, this will call LoadObject which can be very slow
	 * @param InLoadContext Optional load context when called from nested load callstack
	 * @return Loaded UObject, or nullptr if the reference is null or the asset fails to load
	 */
	UObject* TryLoad(FUObjectSerializeContext* InLoadContext = nullptr) const;

嘗試加載資產,這將調用LoadObject,這可能是非常慢的(LoadObject是同步加載)。

InLoadContext:可選加載上下文時,從嵌套的加載callstack調用。

返回一個已經加載的UObject的引用,或者是空或資產加載失敗。

	/** Returns string representation of reference, in form /package/path.assetname[:subpath] */
	FString ToString() const;

返回引用的字符串表示形式,格式為/package/path.assetname[:subpath]

TAssetPtr

TAssetPtr基本上就是一個封裝了FStringAssetReferencesTWeakObjectPtr,它使用一個特定的類作為模板,以便限制編輯器用戶界面,使策划僅能選擇特定的類。

使用IsPendding()方法可檢查資產是否已准備好可供訪問。

如果所引用資源存在於內存中,那么TAssetPtr.Get()將返回該資源。如果該資源不在內存中,那么可以調用ToStringReference()來查找它引用的資源,並可使用模板化LoadObject<>()方法、StaticLoadObject()FStreamingManager來加載對象。

前兩個方法以同步方式加載資產,這可能會導致幀速率突降。再次調用TAssetPtr.Get()可以解除該資源的引用。

示例:

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	TAssetPtr<UTexture2D> AP;

image-20211103115810254

如果你非要寫代碼以實現按需加載的話可以繼續往下看。

FStringAssetReference和TAssetPtr的按需加載

前面已經用過它們來作為一個簡單引用以加載資源,現在再來看一下它的定義:

可以看到FStringAssetReference的本質是FSoftObjectPath

// Not deprecating these yet as it will lead to too many warnings in games
//UE_DEPRECATED(4.18, "FStringAssetReference was renamed to FSoftObjectPath as it is now not always a string and can also refer to a subobject")
typedef FSoftObjectPath FStringAssetReference;

//UE_DEPRECATED(4.18, "FStringClassReference was renamed to FSoftClassPath")
typedef FSoftClassPath FStringClassReference;

TAssetPtr的本質是TSoftObjectPtr

// Not deprecating these yet as it will lead to too many warnings in games
//UE_DEPRECATED(4.18, "TAssetPtr was renamed to TSoftObjectPtr as it is not necessarily an asset")
template<class T=UObject>
using TAssetPtr = TSoftObjectPtr<T>;

//UE_DEPRECATED(4.18, "TAssetSubclassOf was renamed to TSoftClassPtr")
template<class TClass = UObject>
using TAssetSubclassOf = TSoftClassPtr<TClass>;

以下內容來自官方文檔 - FSoftObjectPath

FSoftObjectPath

A struct that contains a string reference to an object, either a top level asset or a subobject.

包含對對象(頂級資產或子對象)的字符串引用的結構。

Remarks

A struct that contains a string reference to an object, either a top level asset or a subobject. This can be used to make soft references to assets that are loaded on demand. This is stored internally as an FName pointing to the top level asset (/package/path.assetname) and an option a string subobject path. If the MetaClass metadata is applied to a FProperty with this the UI will restrict to that type of asset.

包含對對象(頂級資產或子對象)的字符串引用的結構。這可以用於對按需加載的資產進行軟引用。它在內部存儲為FName,指向頂級資產(/package/path.assetname)和一個選項,一個字符串子對象路徑。如果MetaClass元數據被應用到FProperty, UI將限制在該類型的資產。

以下內容來自官方文檔 - TSoftObjectPtr

TSoftObjectPtr

TSoftObjectPtr is templatized wrapper of the generic FSoftObjectPtr, it can be used in UProperties

TSoftObjectPtr是通用的模板化包裝器,它可以用於UProperties。

以下內容來自官方文檔 - 異步資源加載

FSoftObjectPaths和TSoftObjectPtr

讓美術或設計師引用資源的最簡單方法是創建硬指針的UProperty並為它指定一個類別。在UE4中,如果有一個硬UObject指針屬性引用了一個資源,則加載包含這個屬性的對象(放在貼圖中,或者從gameinfo等引用)時,就會加載這個資源。如果處理不當,就會在游戲開始時加載全部資源。如果您希望美術/設計師能夠使用同一個UI作為硬指針來引用特定資源,而不必總是加載被引用資源,可以使用FSoftObjectPathTSoftObjectPtr

FSoftObjectPath是一個簡單的結構體,其中有一個字符串包含資源的完整名稱。如果您在類中添加這個類型的屬性,它就會像UObject *屬性一樣顯示在編輯器中。它還會正確處理烘焙和重定向,因此如果您使用SoftObjectPath,就一定能在設備上正確工作。

TSoftObjectPtr基本上是包含了FSoftObjectPathTWeakObjectPtr,將用於設置特定類的模板,這樣就可以限制編輯器UI僅允許選擇特定類。如果被引用資源存在於內存中,則TSoftObjectPtr.Get()將返回這個資源。如果不存在,可以調用ToSoftObjectPath()來找出它引用的資源,使用下述方法加載這個資源,然后再次調用TSoftObjectPtr.Get()來取消引用。

如果美術或設計師要手動設置引用,則TSoftObjectPtrs和Soft Object Paths十分有用,但如果想要通過查詢等功能來查找符合特定要求的資源,而不加載所有資源,則可以使用資源注冊表和對象庫。

總結:

FSoftObjectPath(FStringAssetReference ):包含Asset位置字符串的結構體,可以指向UObject*的資源類型。

TSoftObjectPtr(TAssetPtr):使用FSoftObjectPath構造出來的結構體,用於監視資源狀態,指向尚未加載但可以根據請求加載的資產的指針。

TAssetSubclassOf :指向已定義的基類的子類的指針,該子類尚未加載,但可以根據請求加載。用於指向藍圖而不是基本component(大概是因為藍圖是Asset)。

在C++里面使用:

//.h
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	FStringAssetReference SAR;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	TAssetPtr<UTexture2D> AP;

//.cpp
void UWC_TestUI::OnBtnClickCommonBtn_SARLoad()
{
	FStringAssetReference SAR_SoUnrealPath;
	//檢查是否指向一個有效的UObject,返回false則不是一個有效的UObject或者被初始化為null
	if (SAR.IsValid())
	{
		UE_LOG(LogTemp, Warning, TEXT("SAR.AssetName:%s"), *SAR.GetAssetName());
		UE_LOG(LogTemp, Warning, TEXT("SAR.AssetPathName:%s"), *(SAR.GetAssetPathName().ToString()));
		UE_LOG(LogTemp, Warning, TEXT("SAR.AssetPathString:%s"), *SAR.GetAssetPathString());
		SAR_SoUnrealPath = SAR.GetAssetPathString();
	}else
	{
		//指定路徑
		SAR_SoUnrealPath = TEXT("Texture2D'/Game/UI/Images/SoUnreal.SoUnreal'");
	}
	//資源同步加載
	UObject * UE4PathObj = UAssetManager::GetStreamableManager().LoadSynchronous(SAR_SoUnrealPath);
	//強轉一下
	UTexture2D* UE4PathPtr = Cast<UTexture2D>(UE4PathObj);
	if (UE4PathPtr != nullptr)
	{
		if (Img_SAR != nullptr)
		{
			Img_SAR->SetBrushFromTexture(UE4PathPtr);
		}
	}
}

void UWC_TestUI::OnBtnClickCommonBtn_APLoad()
{
	FStringAssetReference AP_UE4Path;
	//檢查是否指向一個有效的UObject,返回false則不是一個有效的UObject或者被初始化為null
	if (AP.IsValid())
	{
		AP_UE4Path = AP.ToSoftObjectPath();
	}else
	{
		//指定路徑
		AP_UE4Path = TEXT("Texture2D'/Game/UI/Images/GetUnreal.GetUnreal'");
	}
	//注意這里可以傳模板進去,下面就不用再強轉了
	UTexture2D * UE4PathPtr = UAssetManager::GetStreamableManager().LoadSynchronous<UTexture2D>(AP_UE4Path);
	//強轉一下
	//UTexture2D* UE4PathPtr = Cast<UTexture2D>(UE4PathObj);
	if (UE4PathPtr != nullptr)
	{
		if (Img_AP != nullptr)
		{
			Img_AP->SetBrushFromTexture(UE4PathPtr);
		}
	}
}

image-20211104213618250

注意AP是一個TAssetPtr,是一個用UTexture2D包起來的模板,所以在加載的時候已經有了它的類型,就不用強轉了。

特點

此方法的特點是:

  • 簡單、對美術或者UI設計師十分友好,可以不動代碼而預覽效果。
  • 在使用之前需要先進行手動指定引用,並且若是沒有手動指定引用則可以用代碼來指定。代碼指定的時候注意資源名或者路徑變更的時候會發生錯誤,因為是寫死在代碼里面的。這個時候維護就要花額外的時間。

本文標簽

游戲開發游戲開發基礎Unreal EngineUE資源加載


免責聲明!

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



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