vault backup: 2024-10-12 17:19:45
This commit is contained in:
Binary file not shown.
After Width: | Height: | Size: 457 KiB |
@@ -0,0 +1,166 @@
|
||||
# AssetBundles
|
||||
|
||||
- **功能描述:** 标明该属性其引用的资产属于哪一些AssetBundles。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Object Property
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **限制类型:** UPrimaryDataAsset内部的FSoftObjectPtr,FSoftObjectPath
|
||||
- **关联项:** [IncludeAssetBundles](../IncludeAssetBundles/IncludeAssetBundles.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
用于UPrimaryDataAsset内部的 SoftObjectPtr 或 SoftObjectPath 属性,标明其引用的资产属于哪一些AssetBundles。
|
||||
|
||||
要理解这个的作用,需要先理解一些基本概念:
|
||||
|
||||
- PrimaryAsset指的是在游戏中可以进行手动载入/释放的东西。包括关卡文件(.umap)以及一些游戏相关的物件,例如角色或者背包里的物品。顾名思义,主要资产指的是游戏里的主要根部资产,其引用树下有一大堆其他资产。另一方面,我们往往会主动加载或释放这些主要资产,比如加载关卡,加载怪物角色,加载掉落道具。但我们一般不太会直接去加载材质贴图声音这种资产,因为它们绝大多数是被主要资产引用着。我们在加载主要资产的时候,就会自带的加载这些次要资产了。
|
||||
- SecondaryAsset指的是其他的那些Assets了,例如贴图和声音等。这一类型的assets是根据PrimaryAsset来自动进行载入的。我们一般来说不太需要对次要资产进行管理,其会被主要资产根据引用关系来自动的加载。
|
||||
- AssetBundle可以叫做资产包,其实就是一个Asset的列表,我们对每个资产包起个名字来区分,比如UI,Game,这样其实也是对一些资产进行标签分类。这里的Asset我们不区分是PrimaryAsset还是SecondaryAsset,因为这是从用途上进行区分的,而不是加载方式。AssetBundle的作用是当我们加载PrimaryAsset的时候,这个PrimaryAsset本身可能引用着另外一些SecondaryAsset资产,各自有不同的用途。我们就可以把这些SecondaryAsset资产划分到不同的AssetBundle里,这样我们在加载PrimaryAsset的时候,可以通过额外提供AssetBundleName来更加精细化的控制SecondaryAsset资产的加载。
|
||||
- PrimaryAsset里的指定AssetBundle的Asset属性必须是软引用,否则是硬引用的话无论如何也会被加载进来。软引用的Asset在默认时候需要我们手动的进行加载,通过附加AssetBundle,就可以在加载PrimaryAsset的时候,附带的加载该软引用资产。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(BlueprintType)
|
||||
class INSIDER_API UMyProperty_Asset_Item :public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite,EditAnywhere)
|
||||
FString Name;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere,meta=(AssetBundles="UI,Game"))
|
||||
TSoftObjectPtr<UTexture2D> Icon;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere,meta=(AssetBundles="Game"))
|
||||
TSoftObjectPtr<UStaticMesh> Mesh;
|
||||
public:
|
||||
virtual FPrimaryAssetId GetPrimaryAssetId() const override;
|
||||
};
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
- 首先我们在BP里定义了UMyProperty_Asset_Item 的资产,并相应的配置上了引用的对象。如图所示,有一个图标是给UI和Game用的,有一个Mesh是专门给Game用的。大概设想一下在一些界面我们只需要道具的图标就可以了。
|
||||
- 然后在LoadPrimaryAsset的时候可以指定LoadBundles的名字,从而只加载特定的Bundle。如下图所示。
|
||||
- 当指定Bundle为UI的时候,可以看见Mesh并没有加载进来。
|
||||
- 当指定Bundle为Game的时候,可以看见Icon和Mesh都加载了进来。
|
||||
- 要注意在编辑器下测试时候,如果之前已经加载了Mesh,因为还常驻在编辑器内存里。因此即使是使用名字UI,也仍然会发现Mesh可以被引用到。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
首先UPrimaryDataAsset里有一个AssetBundleData保存着当前引用的AssetBundle的信息,这个信息是在编辑器环境下PreSave的时候保存的,会在UAssetManager::InitializeAssetBundlesFromMetadata里进行meta的分析和映射。之后在UAssetManager的LoadPrimaryAsset时内部调用 ChangeBundleStateForPrimaryAssets,然后检查AssetBundle把其他额外要一并加载的Asset添加到PathsToLoad,从而最终完成一并加载的这个逻辑。
|
||||
|
||||
```cpp
|
||||
void UAssetManager::InitializeAssetBundlesFromMetadata_Recursive(const UStruct* Struct, const void* StructValue, FAssetBundleData& AssetBundle, FName DebugName, TSet<const void*>& AllVisitedStructValues) const
|
||||
{
|
||||
static FName AssetBundlesName = TEXT("AssetBundles");
|
||||
static FName IncludeAssetBundlesName = TEXT("IncludeAssetBundles");
|
||||
|
||||
//根据当前对象的值,搜索拥有AssetBundles的属性的值,最后AddBundleAsset,BundleName就是设置的值,而FoundRef是引用的对象的资产路径
|
||||
TSet<FName> BundleSet;
|
||||
TArray<const FProperty*> PropertyChain;
|
||||
It.GetPropertyChain(PropertyChain);
|
||||
|
||||
for (const FProperty* PropertyToSearch : PropertyChain)
|
||||
{
|
||||
if (PropertyToSearch->HasMetaData(AssetBundlesName))
|
||||
{
|
||||
TSet<FName> LocalBundleSet;
|
||||
TArray<FString> BundleList;
|
||||
const FString& BundleString = PropertyToSearch->GetMetaData(AssetBundlesName);
|
||||
BundleString.ParseIntoArrayWS(BundleList, TEXT(","));
|
||||
|
||||
for (const FString& BundleNameString : BundleList)
|
||||
{
|
||||
LocalBundleSet.Add(FName(*BundleNameString));
|
||||
}
|
||||
|
||||
// If Set is empty, initialize. Otherwise intersect
|
||||
if (BundleSet.Num() == 0)
|
||||
{
|
||||
BundleSet = LocalBundleSet;
|
||||
}
|
||||
else
|
||||
{
|
||||
BundleSet = BundleSet.Intersect(LocalBundleSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const FName& BundleName : BundleSet)
|
||||
{
|
||||
AssetBundle.AddBundleAsset(BundleName, FoundRef.GetAssetPath());
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
void UPrimaryDataAsset::UpdateAssetBundleData()
|
||||
{
|
||||
// By default parse the metadata
|
||||
if (UAssetManager::IsInitialized())
|
||||
{
|
||||
AssetBundleData.Reset();
|
||||
UAssetManager::Get().InitializeAssetBundlesFromMetadata(this, AssetBundleData);
|
||||
}
|
||||
}
|
||||
|
||||
void UPrimaryDataAsset::PreSave(FObjectPreSaveContext ObjectSaveContext)
|
||||
{
|
||||
Super::PreSave(ObjectSaveContext);
|
||||
|
||||
UpdateAssetBundleData();
|
||||
|
||||
if (UAssetManager::IsInitialized())
|
||||
{
|
||||
// Bundles may have changed, refresh
|
||||
UAssetManager::Get().RefreshAssetData(this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void UPrimaryDataAsset::PostLoad()
|
||||
{
|
||||
Super::PostLoad();
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
FAssetBundleData OldData = AssetBundleData;
|
||||
|
||||
UpdateAssetBundleData();
|
||||
|
||||
if (UAssetManager::IsInitialized() && OldData != AssetBundleData)
|
||||
{
|
||||
// Bundles changed, refresh
|
||||
UAssetManager::Get().RefreshAssetData(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//加载asset的时候,如果有FAssetBundleEntry,则一起加到PathsToLoad里
|
||||
TSharedPtr<FStreamableHandle> UAssetManager::ChangeBundleStateForPrimaryAssets(const TArray<FPrimaryAssetId>& AssetsToChange, const TArray<FName>& AddBundles, const TArray<FName>& RemoveBundles, bool bRemoveAllBundles, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority)
|
||||
{
|
||||
if (!AssetPath.IsNull())
|
||||
{
|
||||
// Dynamic types can have no base asset path
|
||||
PathsToLoad.Add(AssetPath);
|
||||
}
|
||||
|
||||
for (const FName& BundleName : NewBundleState)
|
||||
{
|
||||
FAssetBundleEntry Entry = GetAssetBundleEntry(PrimaryAssetId, BundleName);
|
||||
|
||||
if (Entry.IsValid())
|
||||
{
|
||||
for (const FTopLevelAssetPath & Path : Entry.AssetPaths)
|
||||
{
|
||||
PathsToLoad.AddUnique(FSoftObjectPath(Path));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogAssetManager, Verbose, TEXT("ChangeBundleStateForPrimaryAssets: No assets for bundle %s::%s"), *PrimaryAssetId.ToString(), *BundleName.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
参考文档:[https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine?application_version=5.4](https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine?application_version=5.4)
|
Reference in New Issue
Block a user