UAssetManager用法
文章目錄
UAssetManager通過名字就可以瞭解,這個類是對資源進行管理。一般情況下,UE4會自動加載與卸載資源,但是如果開發者(就是我們)想更精確地掌控資源發現、加載與審覈的時機與方法,那這個UAssetManager就可以上場幫忙了。下面,我們一步一步解析,這個UAssetManager如何使用。
一、PrimaryAsset和SecondaryAsset
首先,我們來對資源進行分類。我們把資源分爲PrimaryAsset(主資源)和SecondaryAsset(次資源)兩類。
那什麼是PrimaryAsset和SecondaryAsset資源呢?下邊是網上其他博主給的,大家自行參考。原文鏈接
Primary Assets指的是在遊戲中可以進行手動載入/釋放的東西。包括地圖文件以及一些遊戲相關的物件,例如character classs或者inventory items.
Secondary Assets指的是其他的那些Assets了,例如貼圖和聲音等。這一類型的assets是根據Primary Assets來自動進行載入的。
而AssetManager就是來直接管理這個PrimaryAsset,SecondaryAsset則會在主資源加載後自動載入。
但默認情況下,只有關卡(Map)(/Game/Maps)是PrimaryAsset(可能後邊會發現還有一個PrimaryAssetLabel,這個暫時忽略)。
那如何將SecondaryAsset設爲PrimaryAsset呢?那第一步就是給資源類實現 virtual FPrimaryAssetId GetPrimaryAssetId() const override; 接口方法。
下邊是方法的實現:
//GetPrimaryAssetId是在UObject就聲明的抽象函數,所以不用擔心父類沒有這個抽象方法。
FPrimaryAssetId XXX::GetPrimaryAssetId() const
{
//AssetType是FPrimaryAssetType類型的變量,應該如何定義,我們在下節說明
return FPrimaryAssetId(AssetType, GetFName());
}
AssetManager通過FPrimaryAssetId來對指定的資源對象進行管理,而上述成員方法就是來獲取這個AssetID的,所以說只有實現這個方法,才能對該類型資源進行管理。
AssetType和AssetId是什麼關係?定義上述方法就OK了嗎?其實,還真沒那麼簡單,下面進行更詳細的說明解釋。
小結:UAssetManager可以直接管理PrimaryAsset。將類實現GetPrimaryAssetId方法是成爲PrimaryAsset的第一步。
二、PrimaryAssetId和PrimaryAssetType
我們通過上一節瞭解到,如果要將資源提升爲PrimaryAsset,需要實現GetPrimaryAssetId方法。方法的返回值就是AssetManager對這個資源進行管理的標識符了。
返回FPrimaryAssetId對象時,需要傳入AssetType和GetFName兩個參數。
其中AssetType是FPrimaryAssetType的對象,而GetFName()方法返回的是資源對象名。這兩個參數一起確定了這個資源的Id。
我們可以在UAssetManager類中定義FPrimaryAssetType類型的靜態變量來定義資源類型。
static const FPrimaryAssetType WeaponType = TEXT("Weapon");
也就是說,我們的資源對象可以屬於不同或相同的資源類型,按照資源類型對可以對PriamryAsset進行分類。這裏可能解釋關於繁瑣,並且不好理解,下邊會有個代碼示例。
三、將PrimaryAsset進行註冊
我們通過在C++中通過實現類的GetPrimaryAssetId方法對類進行了Id和Type的設置。如果我們想要在藍圖中對資源進行引用的話,還需要在項目設置裏面對AssetType進行註冊。換句話說,我們在C++中只是對AssetType進行聲明和定義(因爲這個AssetType的定義位置比較隨意,雖然我們推薦定義在AssetManager,但實際任何位置,甚至直接返回TEXT都可以。),如果要讓UE4去識別我們的AssetType,就需要對其進行註冊。
首先打開ProjectSettings,然後找到AssetManager,在設置面板裏面可以看到PrimaryAssetTypestoScan,這個變量就是對AssetType進行註冊的地方了。參數說明請參考官方文檔。
這裏需要注意的是數組元素裏有一項是PrimaryAssetType,這項的值必須與註冊的資源返回的Id參數AssetType的TEXT值一致。可能有點繞口,就是說PrimaryAssetType的值與此項註冊的資源類在C++中賦值的AssetType一致。
當然,還有一種註冊方法,那就是直接在代碼中註冊。
如希望直接在代碼中註冊主資源,則覆蓋資源管理器類中的 StartInitialLoading 函數並從該處調用 ScanPathsForPrimaryAssets。因此,推薦您將所有同類型的主資源放入相同的子文件夾中。這將使資源查找和註冊更爲迅速。
ScanPathsForPrimaryAssets(TEXT("Actor"), TArray<FString>{"/Game/Path"}, UObject::StaticClass(), true);
ScanPathForPrimaryAsset(WeaponType, TArray<FString>{"/Game/Path/Items"}, USGItems::StaticClass(), false);
如果使用熱編譯後,編輯器沒有反應,記得重啓編輯器試試!!
四、AssetManager
一般情況下,我們不需要創建自己的AssetManager。但如果有一些特殊的需要,比如上例中覆蓋StartInitialLoading從而註冊資源的話,就需要創建自己的AssetManager了,創建的AssetManager需要繼承UAssetManager。下例爲ActionRPG中AssetManager的示例:
UCLASS()
class ACTIONRPG_API URPGAssetManager : public UAssetManager
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGAssetManager() {}
virtual void StartInitialLoading() override;
/** Static types for items */
static const FPrimaryAssetType PotionItemType;
static const FPrimaryAssetType SkillItemType;
static const FPrimaryAssetType TokenItemType;
static const FPrimaryAssetType WeaponItemType;
/** Returns the current AssetManager object */
static URPGAssetManager& Get();
/**
* Synchronously loads an RPGItem subclass, this can hitch but is useful when you cannot wait for an async load
* This does not maintain a reference to the item so it will garbage collect if not loaded some other way
*/
URPGItem* ForceLoadItem(const FPrimaryAssetId& PrimaryAssetId, bool bLogWarning = true);
};
const FPrimaryAssetType URPGAssetManager::PotionItemType = TEXT("Potion");
const FPrimaryAssetType URPGAssetManager::SkillItemType = TEXT("Skill");
const FPrimaryAssetType URPGAssetManager::TokenItemType = TEXT("Token");
const FPrimaryAssetType URPGAssetManager::WeaponItemType = TEXT("Weapon");
URPGAssetManager& URPGAssetManager::Get()
{
URPGAssetManager* This = Cast<URPGAssetManager>(GEngine->AssetManager);
if (This)
{
return *This;
}
else
{
UE_LOG(LogActionRPG, Fatal, TEXT("Invalid AssetManager in DefaultEngine.ini, must be RPGAssetManager!"));
return *NewObject<URPGAssetManager>(); // never calls this
}
}
void URPGAssetManager::StartInitialLoading()
{
Super::StartInitialLoading();
//ScanPathsForPrimaryAssets(TEXT("Actor"), TArray<FString>{"/Game/Path"}, UObject::StaticClass(), true);
UAbilitySystemGlobals::Get().InitGlobalData();
}
URPGItem* URPGAssetManager::ForceLoadItem(const FPrimaryAssetId& PrimaryAssetId, bool bLogWarning)
{
FSoftObjectPath ItemPath = GetPrimaryAssetPath(PrimaryAssetId);
// This does a synchronous load and may hitch
URPGItem* LoadedItem = Cast<URPGItem>(ItemPath.TryLoad());
if (bLogWarning && LoadedItem == nullptr)
{
UE_LOG(LogActionRPG, Warning, TEXT("Failed to load item for identifier %s!"), *PrimaryAssetId.ToString());
}
return LoadedItem;
}
並且,我們需要修改配置文件,將AssetManager設置爲此類。
五、資源加載
資源管理器函數 LoadPrimaryAssets、LoadPrimaryAsset 和 LoadPrimaryAssetsWithType 可用於在適當的時間開始加載主資源。之後資源可通過 UnloadPrimaryAssets、UnloadPrimaryAsset 和 UnloadPrimaryAssetsWithType 進行卸載。使用這些加載函數時,可指定一個資源束列表。以此法進行加載將使資源管理器按以上描述的方式加載這些資源束應用的次資源。
藍圖中也提供了許多資源管理的靜態方法,例如GetPrimaryAssetIdList方法可以通過傳入資源類型,返回此類型所有資源對象的ID,然後通過AsyncLoadPrimaryAssetList就可以批量異步加載此類型的資源對象。其它方法的使用自行測試。
六、資源束
資源束的內容後期補充,這裏先參考官方文檔。
資源束(Asset Bundle) 是與主資源相關特定資源的命名列表。用“AssetBundles”元標籤對 UObject 的 TAssetPtr 或 FStringAssetReference 成員的 UPROPERTY 代碼段進行標記即可創建資源束。標籤的數值將顯示保存次資源的束的命名。舉例而言,以下保存在 MeshPtr 成員變量中的靜態網格體資源在 UObject 被保存時將被添加到名爲“TestBundle”的資源束。
/** 模型 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Display, AssetRegistrySearchable, meta = (AssetBundles = "TestBundle"))
TAssetPtr<UStaticMesh> MeshPtr;