7.7 KiB
7.7 KiB
AssetBundles
- 功能描述: 标明该属性其引用的资产属于哪一些AssetBundles。
- 使用位置: UPROPERTY
- 引擎模块: Object Property
- 元数据类型: strings="a,b,c"
- 限制类型: UPrimaryDataAsset内部的FSoftObjectPtr,FSoftObjectPath
- 关联项: IncludeAssetBundles
- 常用程度: ★★★
用于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的时候,附带的加载该软引用资产。
测试代码:
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,从而最终完成一并加载的这个逻辑。
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());
}
}
}