Asset Bundles
Asset Bundles(资源束)可以理解为给特定主资源中的次级资源设置“组”,方便你通过“组名”对加载指定组的次级Asset。
Asset Bundles设置方法
设置的方法有两种:
1)反射标记法
//在UPROPERTY宏中加入meta = (AssetBundles = "TestBundle")
//你就创建了名为TestBundle的Asset Bundles,并且添加了该资源到该资源束中
//AssetRegistrySearchable 说明符说明此属性与其值将被自动添加到将此包含为成员变量的所有资源类实例的资源注册表。不可在结构体属性或参数上使用。
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Display, AssetRegistrySearchable, meta = (AssetBundles = "TestBundle"))
TAssetPtr<UStaticMesh> MeshPtr;
2)运行时注册
//这里我复制了官方文档中的代码
//关键就在于AddDynamicAsset函数
//这段代码比较旧了,仅作参考
UFortAssetManager& AssetManager = UFortAssetManager::Get();
FPrimaryAssetId TheaterAssetId = FPrimaryAssetId(UFortAssetManager::FortTheaterInfoType, FName(*TheaterData.UniqueId));
//展开下载数据
TArray<FStringAssetReference> AssetReferences;
AssetManager.ExtractStringAssetReferences(FFortTheaterMapData::StaticStruct(), &TheaterData, AssetReferences);
//创建所有展开数据的Asset Bundle
FAssetBundleData GameDataBundles;
GameDataBundles.AddBundleAssets(UFortAssetManager::LoadStateMenu, AssetReferences);
// 递归延展引用,获得区域中的图块蓝图
AssetManager.RecursivelyExpandBundleData(GameDataBundles);
//注册动态Asset
AssetManager.AddDynamicAsset(TheaterAssetId, FStringAssetReference(), GameDataBundles);
// 开始预载入
AssetManager.LoadPrimaryAsset(TheaterAssedId, AssetManager.GetDefaultBundleState());
PS.文档中说了个比较骚的思路,那就是由服务器生成Asset数据(比如随机地图)然后下载到本地再加载。个人猜测:如需持久化,需要使用Pak文件方案来解决,不需持久可以考虑设置一个TAssetPtr,之后指向反序列下载的资源,再加载。
按照Asset Bundles名称载入主资源中的次级资源
首先看一下载入函数:
//预加载使用
TSharedPtr<FStreamableHandle> PreloadPrimaryAssets(const TArray<FPrimaryAssetId>& AssetsToLoad, const TArray<FName>& LoadBundles, bool bLoadRecursive, FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = FStreamableManager::DefaultAsyncLoadPriority);
//快速载入非主资源的函数
virtual TSharedPtr<FStreamableHandle> LoadAssetList(const TArray<FSoftObjectPath>& AssetList, FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = FStreamableManager::DefaultAsyncLoadPriority, const FString& DebugName = TEXT("LoadAssetList"));
virtual TSharedPtr<FStreamableHandle> LoadPrimaryAssets(const TArray<FPrimaryAssetId>& AssetsToLoad, const TArray<FName>& LoadBundles = TArray<FName>(), FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = FStreamableManager::DefaultAsyncLoadPriority);
virtual TSharedPtr<FStreamableHandle> LoadPrimaryAsset(const FPrimaryAssetId& AssetToLoad, const TArray<FName>& LoadBundles = TArray<FName>(), FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = FStreamableManager::DefaultAsyncLoadPriority);
virtual TSharedPtr<FStreamableHandle> LoadPrimaryAssetsWithType(FPrimaryAssetType PrimaryAssetType, const TArray<FName>& LoadBundles = TArray<FName>(), FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = FStreamableManager::DefaultAsyncLoadPriority);
有关用法就是指定你想要加载的主资源,如需加载指定Asset Bundles中的次级资源,则填入指定名称。
后面3个函数本质上的调用ChangeBundleStateForPrimaryAssets函数,它可以将Asset Bundles作为排除列表。
有关Pak方面的资料
关于Pak的生成与简单介绍可以参考:
https://blog.ch-wind.com/unrealpak-note/
有关载入Pak的方法可以参考:
https://zhuanlan.zhihu.com/p/79209172
有关加载进度
使用UAssetManager中的 GetResourceAcquireProgress()可以获得加载进度。在为资产获取资源/块时调用委托,如果所有资源都已获取,则参数为true,如果任何资源失败,则为false。
在加载资源时,你可以获得FStreamableHandle的智能指针对象,调用BindUpdateDelegate,绑定update委托或者GetProgress()也可以获取当前资源的载入进度。
待探索的问题
- 文档中所注册过的主资源会在扫描后加载,那么其内部的次级资源会加载么?如果不加载,那如何解答umap会加载地图中所有asset?如果加载那么Asset Bundles有什么意义?
- 文档中并没有说明如何对Asset进行分块,仅仅说明了ShooterGame的所有数据分了3个块。分块是不是就等于分成3个Pak呢?
结语
本文仅供抛砖引玉,内容仅供参考。因为本人工作与UE4无关,且仅作为个人爱好,目前尚无没有精力对该篇文章所提的观点进行严格论证。而且Asset的加载逻辑本身就是一个经验性工程,恕本人无大型项目开发经验,遂无法撰写一个综合性的案例,还请见谅。
测试结果
- 如果没有注册指定PrimaryAsset,那么从GetPrimaryAssetDataList是无法获取到指定的PrimaryAsset,同时也无法使用LoadPrimaryAsset等函数进行载入。(默认不会载入注册的PrimaryAsset)
- 使用LoadPrimaryAsset函数载入PrimaryAsset,会让内部的SecondaryAssets也一起载入。(不填写LoadBundles形参)
- 在LoadPrimaryAsset函数中添加LoadBundles形参后,有AssetBundles标记的资源将不会加载。
- 想要对AssetBundles标记的资源进行更多的控制请使用ChangeBundleStateForPrimaryAssets函数。
测试代码
这里是我测试用的代码,方便大家用来测试,如果错误还请指正:
UE_LOG(LogActionRPG, Warning, TEXT("%s"), *FString("Start Try!"));
URPGAssetManager& AssetManager = URPGAssetManager::Get();
FPrimaryAssetId AssetId = AssetManager.GetPrimaryAssetIdForPath(FString("/Game/ActionRPG/DataAsset/NewDataAsset1.NewDataAsset1"));
FSoftObjectPath AssetPath=AssetManager.GetPrimaryAssetPath(AssetId);
TArray<FAssetData> AssetDataList1;
//virtual UObject* GetPrimaryAssetObject(const FPrimaryAssetId& PrimaryAssetId) const;
UObject* AssetPtr = nullptr;
AssetPtr=AssetManager.GetPrimaryAssetObject(AssetId);
if (AssetPtr == nullptr) {
UE_LOG(LogActionRPG, Warning, TEXT("--- result:%s "),*FString("false"));
}
else {
UE_LOG(LogActionRPG, Warning, TEXT("--- result:%s "),*FString("true"));
}
AssetManager.GetPrimaryAssetDataList(FPrimaryAssetType(FName("Test")),AssetDataList1);
for (FAssetData data : AssetDataList1)
{
UE_LOG(LogActionRPG, Warning, TEXT("Name:%s"), *data.AssetName.ToString());
UE_LOG(LogActionRPG, Warning, TEXT("Class:%s"), *data.AssetClass.ToString());
}
AssetPtr = AssetManager.GetPrimaryAssetObject(AssetId);
if (AssetPtr == nullptr) {
UE_LOG(LogActionRPG, Warning, TEXT("--- result:%s "), *FString("false"));
}
else {
UE_LOG(LogActionRPG, Warning, TEXT("--- result:%s "), *FString("true"));
}
TArray<FName> LoadBundles = { FName("TestBundle") };
TArray<FPrimaryAssetId> AssetsToLoad = {AssetId};
//AssetHandle=AssetManager.LoadPrimaryAsset(AssetId,LoadBundles);
//AssetHandle=AssetManager.LoadPrimaryAssets(AssetsToLoad,LoadBundles);
AssetHandle = AssetManager.ChangeBundleStateForPrimaryAssets(AssetsToLoad, TArray<FName>(), LoadBundles,false);
//AssetHandle = AssetManager.PreloadPrimaryAssets(AssetsToLoad, LoadBundles, false);
AssetPtr = AssetManager.GetPrimaryAssetObject(AssetId);
if (AssetPtr == nullptr) {
UE_LOG(LogActionRPG, Warning, TEXT("--- result:%s "), *FString("false"));
}
else {
UE_LOG(LogActionRPG, Warning, TEXT("--- result:%s "), *FString("true"));
UPrimaryDataAssetTest* ptr=Cast<UPrimaryDataAssetTest>(AssetPtr);
if (ptr)
{
TMap<FPrimaryAssetId, TArray<FName>> BundleStateMap;
AssetManager.GetPrimaryAssetBundleStateMap(BundleStateMap);
bool result1=ptr->MeshPtr.IsPending();
bool result2=ptr->MeshPtr2.IsPending();
bool result3=ptr->MeshPtr3.IsPending();
UE_LOG(LogActionRPG, Warning, TEXT("--- subAsset:%s %s %s "), *FString(result1 ? TEXT("ture") : TEXT("false")),*FString(result2 ? TEXT("ture") : TEXT("false")),*FString(result3 ? TEXT("ture") : TEXT("false")));
}
}