BlueRoseNote/03-UnrealEngine/Gameplay/Gameplay/AssetManager/AssetManager系列之Asset Bundles.md
2023-06-29 11:55:02 +08:00

152 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 如何对Asset进行分块
https://docs.unrealengine.com/zh-CN/SharingAndReleasing/Patching/GeneralPatching/ChunkingExample/index.html
### 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()也可以获取当前资源的载入进度。
### 待探索的问题
1. 文档中所注册过的主资源会在扫描后加载那么其内部的次级资源会加载么如果不加载那如何解答umap会加载地图中所有asset如果加载那么Asset Bundles有什么意义
2. 文档中并没有说明如何对Asset进行分块仅仅说明了ShooterGame的所有数据分了3个块。分块是不是就等于分成3个Pak呢
### 结语
本文仅供抛砖引玉内容仅供参考。因为本人工作与UE4无关且仅作为个人爱好目前尚无没有精力对该篇文章所提的观点进行严格论证。而且Asset的加载逻辑本身就是一个经验性工程恕本人无大型项目开发经验遂无法撰写一个综合性的案例还请见谅。
### 测试结果
1. 如果没有注册指定PrimaryAsset那么从GetPrimaryAssetDataList是无法获取到指定的PrimaryAsset同时也无法使用LoadPrimaryAsset等函数进行载入。默认不会载入注册的PrimaryAsset
2. 使用LoadPrimaryAsset函数载入PrimaryAsset会让内部的SecondaryAssets也一起载入。不填写LoadBundles形参
3. 在LoadPrimaryAsset函数中添加LoadBundles形参后有AssetBundles标记的资源将不会加载。
4. 想要对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")));
}
}
```