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;