Aery的UE4 C++遊戲開發之旅(4)加載資源&建立對象

在UE4中,項目中的全部資源都是存儲在硬盤中,當須要用到資源時,則須要將其加載進入內存中使用。爲了更好的表示(引用)資源,UE4提供了兩種引用資源的方式——硬引用、軟引用。c++

資源的硬引用


硬性引用,即對象 A 引用對象 B,並致使對象 B 在對象 A 加載時加載。通俗點說,硬引用所表示的資源在引用初始化時就加載進內存,所以硬引用的資源幾乎不須要加載方法。異步

硬指針

引用資源的最簡單方法是建立指針UProperty併爲它指定一個類別,這種稱爲硬指針。
在UE4中,若是有一個硬UObject指針屬性引用了一個資源(每每在藍圖上設置引用),則加載包含這個屬性的對象(放在貼圖中,或者從gameinfo等引用)時,就會加載這個資源。async

UPROPERTY(EditDefaultsOnly, Category=Building)
USoundCue* ConstructionStartStinger;

FObjectFinder<T> / FClassFinder<T>

若須要用C++代碼而非藍圖來設置引用,則每每須要FObjectFinder、FClassFinder。編輯器

#include "UObject/ConstructorHelpers.h" \\須要include的頭文件

在UE4源碼裏面,FObjectFinder構造函數裏經過調用LoadObject()來加載資源,而FClassFinder構造函數裏調用的也是LoadObject()。函數

注意在使用它們的時候還得遵照以下規則:ui

  • 只能在類的構造函數中使用,若是在普通的邏輯代碼中嵌套這份代碼,會引發整個編譯器的crash。(實際上裏面代碼就有檢查是否在構造函數裏,不然crash)
  • 其次,FObjectFinder/FClassFinder變量必須是static的,從而保證只有一份資源實例。
  1. FObjectFinder<T>:通常用來加載非藍圖資源,好比StaticMesh、Material、SoundWave、ParticlesSystem、AnimSequence、SkeletalMesh等資源:
static ConstructorHelpers::FObjectFinder<UTexture2D> ObjectFinder(TEXT("Texture2D'/Game/Textures/tex1.tex1'"));
UTexture2D* Texture2D = ObjectFinder.Object;
  1. FClassFinder<T>:通常用來加載藍圖資源並獲取藍圖Class。這是由於若是C++要用藍圖建立對象,必須先獲取藍圖的Class,而後再經過Class生成藍圖對象:
static ConstructorHelpers::FClassFinder<AActor> BPClassFinder(TEXT("/Game/Blueprints/MyBP"));
TSubclassOf<AActor> BPClass = BPClassFinder.Class;
...//利用Class生成藍圖對象
  • FClassFinder的模版名不能直接寫UBlueprint,例如:FClassFinder<UBlueprint>是錯誤的。建立藍圖時選擇的是什麼父類,則寫對應的父類名,假如是Actor,那麼要寫成:FClassFinder<AActor>,不然沒法加載成功。
  • FClassFinder的模版名必須和TSubclassOf變量的模版名一致,固然也可以使用UClass*代替TSubclassOf<T>。實際上TSubclassOf<T>也是UClass*,只是更增強調這個Class是從T派生出來的。
  • 在啓動遊戲時若報錯提示找不到文件而崩潰(例如:Default property warnings and errors:Error: COD Constructor (MyGameMode): Failed to find /Game/MyProject/MyBP.MyBP)
    這是由於UE4資源路徑的一個規範問題,解決辦法有兩種:
    1. 在copy reference出來的文件路徑後面加_C,例如:"Blueprint'/Game/Blueprints/MyBP.MyBP_C'"(_C能夠理解爲獲取Class的意思)。
    2. 去掉路徑前綴,例如:"/Game/Blueprints/MyBP"

資源的軟引用


軟性引用,即對象 A 經過間接機制(例如字符串形式的對象路徑)來引用對象 B。this

硬引用的問題是在容易一開始就加載所有硬引用表示的資源,這可能致使資源載入時間過長。而軟引用則是可隨時靈活加載資源的一種引用,而不用硬性地一開始就加載。spa

FSoftObjectPaths、FStringAssetReference

  1. FSoftObjectPath:是一個簡單的結構體,其中包含了資源的完整名稱(一個字符串)。它實質就是用一個字符串來表示對應的資源,從而能夠隨時經過字符串找到硬盤上的目標資源,將其載入進內存。.net

    FSoftObjectPath.SolveObject() 能夠檢查其引用的資源是否已經載入在內存中,若載入則返還資源對象指針,不然返還空。
    FSoftObjectPath.IsPending() 可檢查資源是否已準備好可供訪問。而如何利用FSoftObjectPath加載資源進內存,後面還會說到。

  2. FStringAssetReference:其實只是一個聽起來更容易理解的別名,它實際在UE4源碼裏是這樣的:
typedef FSoftObjectPath FStringAssetReference;

TSoftObjectPtr<T>

TSoftObjectPtr是包含了FSoftObjectPath的TWeakObjectPtr,可經過模板參數來設置特定資源類型,這樣就能夠限制編輯器UI僅容許選擇特定的資源種類。

TSoftObjectPtr.Get() 能夠檢查其引用的資源是否已經載入在內存中,若已載入則返還資源對象指針,不然返還空。想要資源加載進內存,則能夠調用ToSoftObjectPath()來獲得FSoftObjectPaths用於加載。

同步加載資源


LoadObject/LoadClass

  1. LoadObject<T>():加載UObject,通常用來加載非藍圖資源。
UTexture2D* Texture2D  = LoadObject<UTexture2D>(nullptr,TEXT("Texture2D'/Game/Textures/tex1.tex1'"));
  1. LoadClass<T>():加載UClass,通常用來加載藍圖資源並獲取藍圖Class。實際上源碼裏LoadClass的實現是調用LoadObject並獲取類型。
    • LoadClass的模版名稱,和上面FClassFinder同樣,不能直接寫UBlueprint。
    • LoadClass路徑規範也和上面的FClassFinder同樣,帶_C後綴或去掉前綴。

另外有兩個函數叫:StaticLoadObject()和StaticLoadClass(),是LoadObject()和LoadClass()的早期版本,前二者須要手動強轉和填寫冗雜參數,後二者則是前二者的封裝,使用更方便,推薦使用後者。

TSubclassOf<AActor> BPClass = LoadClass<AActor>(nullptr, TEXT("/Game/Blueprints/MyBP"));

此外一提,還有一個可能經常使用的全局函數FindObject(),用來查詢資源是否載入進內存,若存在則返還資源對象指針,不然返還空。可是咱們不用先查詢再使用LoadXXX,由於LoadXXX裏自己就有用到FindObject來檢查存在性。

TryLoad/LoadSynchronous

  1. TryLoad():FSoftObjectPaths的方法,直接根據路徑加載資源。
  2. LoadSynchronous():TSoftObjectPtr<T>的方法,也是直接根據路徑加載資源。

因爲軟引用裏包含資源完整路徑名,所以無需再寫一次路徑名,而是調用如上成員方法來加載資源進內存。而軟引用的做用不只如此,它還能夠用於下面要介紹的資源異步加載方式。

異步加載資源


即便能夠控制加載資源的時機,但若是加載的資源對象很大(或者同一時刻加載多個資源),仍是會形成卡頓,爲了不阻塞主線程,異步加載的方式必不可少。

FStreamableManager.RequestAsyncLoad()

首先,須要建立FStreamableManager,官方建議將它放在某類全局遊戲單例對象中,例如使用GameSingletonClassName在DefaultEngine.ini中指定的對象。

  1. FStreamableManager.RequestAsyncLoad():將異步加載一組資源並在完成後調用委託。
void UGameCheatManager::GrantItems()
{      
       //獲取 FStreamableManager的單例對象引用
       FStreamableManager& Streamable = ...;

       //獲得一組軟引用
       TArray<FSoftObjectPath> ItemsToStream;
       for(int32 i = 0; i < ItemList.Num(); ++i)
       ItemsToStream.AddUnique(ItemList[i].ToStringReference());

       //根據一組軟引用來異步加載一組資源,加載完後調用委託
       Streamable.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateUObject(this, &UGameCheatManager::GrantItemsDeferred));
}

void UGameCheatManager::GrantItemsDeferred()
{
       //do something....
}

FStreamableManager其實也有同步加載的方法:SynchronousLoad()方法將進行一次簡單的塊加載並返回對象。

卸載資源


若是資源永再也不使用,想將資源對象從內存上卸載,代碼以下:

Texture2D* mytex; //這裏假設mytex合法有效  

mytex->ConditionalBeginDestroy();  
mytex = NULL;  
GetWorld()->ForceGarbageCollection(true);

建立對象


UE4的對象(即從UObject派生出來的類對象)最好不要用C++的new/delete,而應使用UE4提供的對象生成方法,要否則繼承UObject的垃圾回收能力就無從用處。

建立通常對象

若是有UObject的派生類(非Actor、非Component),那麼可以使用NewObject()模板函數來建立其實例對象:

UMyObject* MyObject = NewObject<UMyObject>();

建立Actor派生類對象

生成AActor派生類對象不要用NewObject或new,而要用UWorld::SpawnActor()

UWorld* World = GetWorld();
FVector pos(150, 0, 20);
AMyActor* MyActor = World->SpawnActor<AMyActor>(pos,FRotator::ZeroRotator);

注意SpawnActor不能放在構造函數,可是能夠放在其餘時期的函數裏,例如BeginPlay()、Tick()...不然可能會編譯後就crash。

建立Component派生類對象

爲Actor建立組件,可以使用UObject::CreateDefaultSubobject()模板函數

UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera0"));
  • CreateDefaultSubobject必須寫在Actor的無參構造函數中,不然crash。
  • CreateDefaultSubobject中的TEXT或者FName參數在同一個Actor中不能重複,不然crash。
  • 必定要添加RegisterComponent(),不然編輯器不會顯示。

建立藍圖對象

藍圖因爲本質是一種腳本,不是直接的C++類,所以每每須要藉助動態類型來生成藍圖對象。全部的加載資源並建立到場景中的方式都離不開SpawnActor這一句代碼。

1.經過已肯定的父類來生成藍圖對象

AMyActor* spawnActor = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass());

若是你的藍圖派生於某個C++類,那麼能夠直接訪問該類的StaticClass()並用於SpawnActor來建立藍圖對象。

2.經過UClass生成藍圖對象

UClass* BPClass = LoadClass<AActor>(nullptr, TEXT("/Game/Blueprints/MyBP"));    //TSubclassOf<AActor>同理
    AActor* spawnActor = GetWorld()->SpawnActor<AActor>(BPClass);

3.經過UObject生成藍圖對象

若獲得UObject則須要先轉換成UBlueprint,再經過GeneratedClass獲取UClass來生成藍圖對象

FStringAssetReference asset = "Blueprint'/Game/BluePrint/TestObj.TestObj'";  
    UObject* itemObj = asset.ResolveObject();  
    UBlueprint* gen = Cast<UBlueprint>(itemObj);  
    if (gen != NULL)   
    {  
        AActor* spawnActor = GetWorld()->SpawnActor<AActor>(gen->GeneratedClass);  
    }

參考


虛幻引擎4 官方文檔 | 引用資源

虛幻引擎4 官方文檔 | 異步資源加載

UE4:四種加載資源的方式

[UE4]C++實現動態加載的問題:LoadClass ()和LoadObject ()

Unreal Cook Book:建立對象的的幾種姿式(C++)

ue4 c++加載藍圖類,資源方式

[UE4]C++實現動態加載的問題

系列其餘文章:Aery的UE4 C++開發之旅系列文章

相關文章
相關標籤/搜索