AssetManager 与 PrimaryAsset
自动关联目录:AssetManager 与 PrimaryAsset
AssetManager 解决的是"怎么把成百上千个资产组织成可管理、可分块、可按需加载的集合"。它引入的核心概念是 PrimaryAsset:一个你可以直接用「类型 + 名字」指认的资产。
一句话定位:PrimaryAsset 是资产的"主键",AssetManager 是主键到路径的索引服务。有了主键,你就不必再在代码里硬写 /Game/xxx/yyy 这样的路径。
两类资产
| Primary Asset | Secondary Asset | |
|---|---|---|
| 标识 | FPrimaryAssetId(Type:Name) | 只有路径 |
| 能否被直接指认 | 能 | 不能,只能通过引用链被带进来 |
| 典型 | 角色、关卡、道具、技能 | 贴图、网格、材质、音效 |
| 管理方 | AssetManager 扫描并索引 | 由 PrimaryAsset 引用 |
| 分块 | 可作为 Chunk 的锚点 | 跟随引用它的 PrimaryAsset |
划分标准:这个资产会不会被"点名要"(玩家选角色、进某个关卡、用某个道具)?会 → Primary;不会 → Secondary。
FPrimaryAssetId
FPrimaryAssetId = Type + ":" + Name
例如:Character:Hero01、Level:Forest 、Item:SwordFPrimaryAssetId Id(TEXT("Item"), TEXT("Sword"));
FPrimaryAssetId Id2 = FPrimaryAssetId::FromString(TEXT("Item:Sword"));
// 取路径
FSoftObjectPath Path = UAssetManager::Get().GetPrimaryAssetPath(Id);| API | 作用 |
|---|---|
GetPrimaryAssetPath(Id) | 主键 → 路径 |
GetPrimaryAssetIdForPath(Path) | 路径 → 主键 |
LoadPrimaryAsset(Id, Bundles, Callback) | 按主键加载 |
GetPrimaryAssetObject<T>(Id) | 取已加载的对象 |
ScanPathsForPrimaryAssets | 按路径扫描注册 |
用主键而不是路径,好处是资产移动位置后代码不用改——路径变了,注册表里的映射自动更新。
让一个类成为 PrimaryAsset
UCLASS()
class UMyItemData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSoftObjectPtr<UTexture2D> Icon;
// 主键的类型部分
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("Item"), GetFName());
}
};UPrimaryDataAsset 相比 UDataAsset 多的是参与 AssetManager 的扫描与分块。
配置扫描路径
在 DefaultGame.ini:
[/Script/Engine.AssetManager]
+PrimaryAssetTypesToScan=(PrimaryAssetType="Item",AssetBaseClass=/Script/MyGame.MyItemData,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Items")),Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True))
+PrimaryAssetTypesToScan=(PrimaryAssetType="Character",AssetBaseClass=/Script/MyGame.MyCharacterData,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Characters")),Rules=(ChunkId=0))| 字段 | 含义 |
|---|---|
PrimaryAssetType | 主键的类型部分 |
AssetBaseClass | 基类,用于筛选 |
Directories | 扫描目录 |
Rules.ChunkId | 默认分块——不打 Chunk 就全进主包 |
Rules.Priority | 优先级 |
bIsEditorOnly | 仅编辑器资产,不打包 |
忘了配 PrimaryAssetTypesToScan 的表现:资产明明在目录里,运行时 GetPrimaryAssetPath 返回空。这是本分支最高频的"低级错误"。
Asset Registry
Asset Registry 是一份"不用加载资产就能查询其元数据"的索引。编辑器启动时扫描所有资产并建索引,运行时从打包好的 AssetRegistry.bin 读。
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
IAssetRegistry& AR = ARM.Get();
TArray<FAssetData> Assets;
AR.GetAssetsByClass(UTexture2D::StaticClass()->GetFName(), Assets);
AR.GetAssetsByPath(FName("/Game/Items"), Assets, /*bRecursive=*/true);
for (const FAssetData& Data : Assets)
{
// 不加载就能读 tag
FString Result;
Data.GetTagValue(TEXT("MyTag"), Result);
}| 用途 | 说明 |
|---|---|
| 编辑器工具批量扫描 | 不加载即可读元数据,速度快几个数量级 |
| 运行时列举资产 | 依赖打包的 AssetRegistry.bin |
| 自定义 tag | 通过 AssetRegistrySearchable 的 UPROPERTY 暴露 |
运行时 Registry 只包含在包里的资产——编辑器里能查到、打包后查不到,多半是资产没被打进去(没有引用链也没有 PrimaryAsset 规则)。
Bundle:按需加载的分组
Bundle 是"一个 PrimaryAsset 里再切一刀"的机制:把它的依赖按用途分组,用到哪组才加载哪组。
UPROPERTY(EditAnywhere, meta = (AssetBundles = "UI"))
TSoftObjectPtr<UTexture2D> Icon; // 归到 UI bundle
UPROPERTY(EditAnywhere, meta = (AssetBundles = "Game"))
TSoftObjectPtr<UStaticMesh> Mesh; // 归到 Game bundleTArray<FName> Bundles{ TEXT("UI") };
UAssetManager::Get().LoadPrimaryAsset(Id, Bundles,
[](FPrimaryAssetId LoadedId){ /* 就绪 */ });| 内置 Bundle | 含义 |
|---|---|
| (空) | 只加载 PrimaryAsset 自身 |
Preload | 紧跟 PrimaryAsset 一起加载 |
Client / Server | 按端区分 |
典型收益:道具列表页只加载几百个 Icon(UI bundle),不加载几 GB 的网格和动画(Game bundle)。
Chunk 划分
ChunkId 决定资产被打进哪个 .pak,是热更新与按需下载的基础。
| 方式 | 说明 |
|---|---|
PrimaryAssetTypesToScan 的 Rules.ChunkId | 按类型统一指定 |
PrimaryAssetLabel(资产标签) | 编辑器里给目录/资产打标签,覆盖默认规则 |
ChunkId = -1 | 不指定,跟引用链走 |
ChunkId = 0 | 主包(第一个 pak) |
标签的优先级高于扫描规则:同一资产被多条规则命中时,取 ChunkId 最大的那条。
常见的划分:
| Chunk | 内容 |
|---|---|
| 0 | 启动必需(Loading 界面、基础 UI、引擎资源) |
| 1..N | 按关卡、按角色、按语言、按活动 |
最容易踩的坑:以为某个资源在 Chunk 5,但它同时被 Chunk 0 里的资产引用,于是被重复打进两个包——包体变大但不会报错。用 SizeMap 和 UnrealPak.exe -List 核对。
常见坑
| 坑 | 说明 |
|---|---|
没配 PrimaryAssetTypesToScan | GetPrimaryAssetPath 返回空 |
| 硬编码资产路径 | 资产移动后全部失效 |
| 把贴图/材质做成 PrimaryAsset | 主键爆炸,索引无意义 |
| ChunkId 全默认 | 全进主包,热更新失去意义 |
| 运行时查不到资产 | 没被打进包,Registry 里没有 |
依赖 AssetBundles 但没在加载时传 bundle 名 | 依赖没被加载,取到空 |
| 资产被多个 Chunk 引用 | 重复打包,包体膨胀 |
| 编辑器能跑、打包后不行 | 多半是 Cook 阶段引用链不同(见 Cook 与打包流程) |