其他
在 Unreal Engine 中处理 DataAsset…
在 Unreal Engine 中设计物品/NPC/关卡等游戏数据,并处理 DataAsset、DataTable、软引用、异步加载、Asset Manager 与资源注册表时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 57/100 ×0.15
适用场景
在 Unreal Engine 中处理 特定
解决思路
- 读取 .agents/ue-project-context.md,获取项目特定的数据模式、模块布局、插件依赖,以及项目已建立的自定义 AssetManager 子类或 DataAsset 约定。
- 生成代码或建议前先询问:正在存储什么类型的数据(item stats、level config、ability definitions、NPC data 等)。
- 询问数据是由设计师在电子表格中编辑,还是直接在编辑器中配置。
- 询问加载需求:始终在内存中、按关卡加载、还是按需流式加载。
- 询问内存预算是否是问题,以及预期有多少实例。
- 询问项目是否已经使用自定义 UAssetManager 子类。
- 选择 DataAsset 还是 DataTable:DataAsset 的结构是带类型化 UPROPERTY 字段的 C++ 类;DataTable 是行结构,所有行形状相同。
- DataAsset 的设计师工作流是编辑器内创建实例并使用选择器 UI;DataTable 的工作流是电子表格导入 CSV/JSON。
- DataAsset 支持通过 Blueprint 子类实现层级/继承;DataTable 不支持。
- DataAsset 可通过 UPrimaryDataAsset 集成 Asset Manager;DataTable 不直接集成。
- DataAsset 不能按行名批量查找;DataTable 可以通过 FindRow 按行名批量查找。
- DataAsset 最适合逐项配置对象;DataTable 最适合大型扁平表(loot、dialogue、XP curves)。
- UDataAsset 是基类,声明于 Engine/DataAsset.h;资源只在被直接引用或显式加载时加载;应子类化并添加类型化 UPROPERTY 字段。
- 在编辑器创建 DataAsset:在 Content Browser 右键 > Miscellaneous > Data Asset,然后选择 UMyItemData。
- UPrimaryDataAsset 重写 GetPrimaryAssetId(),使 Asset Manager 能跟踪、扫描、加载;Primary Asset Type 派生自层级中第一个 native class;PrimaryAssetType == native class name;PrimaryAssetName == asset name。
- 在 UPROPERTY 上使用 meta = (AssetBundles = "X") 把软引用分组,供 Asset Manager 选择性加载,例如 UI 放 Icon,Game 放 WorldMesh。
- DataTable 行结构:继承 Engine/DataTable.h 中的 FTableRowBase;使用 USTRUCT(BlueprintType);定义 UPROPERTY 字段;可重写 OnPostDataImport 在 CSV/JSON 导入后做自定义修复。
- 在编辑器创建 DataTable:右键 > Miscellaneous > Data Table,并指定 FItemTableRow 作为 row struct。
- 运行时查询 DataTable:FindRow<T> 在行不存在或类型不匹配时返回 nullptr。
- 运行时查询 DataTable:GetAllRows<T> 用所有行的指针填充数组。
- 运行时查询 DataTable:ForeachRow 使用行名键进行迭代。
- 运行时修改:AddRow/RemoveRow 不会持久化到磁盘。
- 运行时从 CSV 导入:调用 CreateTableFromCSVString;RowStruct 必须事先设置。
- 运行时从 JSON 导入:调用 CreateTableFromJSONString;JSON 格式使用 RowName 作为键,结构字段作为属性。
- 导出 CSV/JSON 字符串:GetTableAsCSV/GetTableAsJSON 仅 WITH_EDITOR 可用,在 cooked/shipping 构建中不可用。
- 使用 FDataTableRowHandle 作为 UPROPERTY 友好的类型化行引用;通过 GetRow<T> 获取行。
- 硬引用:TObjectPtr 是 UE5 首选,优于裸指针;引用资源加载时硬引用资源也加载;只要该对象存活,mesh/material 就常驻内存;只对对象存活期间始终需要的资源使用硬引用,例如角色的 skeleton。
- 软引用:TSoftObjectPtr<T> 是对资源实例的软引用;TSoftClassPtr<T> 是对类的软引用,尤其适合 Blueprint 子类;FSoftObjectPath 是无类型路径,适合通用系统;它们存储路径字符串,直到显式解析才加载资源。
- 同步解析:LoadSynchronous 会阻塞直到加载完成;避免在游戏线程加载大资源;FSoftObjectPath::TryLoad 在磁盘上不存在时返回 nullptr,且不会 assert。
- 不加载就检查状态:IsNull 表示未设置路径;IsValid 表示已设置路径且资源已加载进内存;IsPending 表示已设置路径、异步加载已开始但未完成;Get 在未加载时返回 nullptr。
- 异步加载:FStreamableManager 声明于 Engine/StreamableManager.h;通过 UAssetManager::GetStreamableManager() 获取。
- RequestAsyncLoad 单资源:传入 FSoftObjectPath 和 FStreamableDelegate 回调;在回调中检查 handle 的 IsValid 和 HasLoadCompleted,然后 Get 资源并应用。
- RequestAsyncLoad 多资源:传入 TArray<FSoftObjectPath>;当每个资源都加载完成后触发一个回调;回调中保证两者都已加载。
- FStreamableHandle 状态与控制:HasLoadCompleted;IsLoadingInProgress;WasCanceled;GetLoadProgress 返回 0.0 到 1.0;WaitUntilComplete 会阻塞游戏线程,仅限加载画面使用;ReleaseHandle 允许已加载资源被 GC。
- 优先级:向 RequestAsyncLoad 传入更高值以紧急加载,默认是 0;对玩法关键资源使用 StreamableManager.h 中的 AsyncLoadHighPriority (100)。
- FStreamableManager 也提供 RequestSyncLoad,用于资源必须立即加载时,例如初始化期间;玩法中优先使用异步,避免卡住游戏线程。
- Asset Manager 设置:在 DefaultGame.ini 的 [/Script/Engine.AssetManagerSettings] 下添加 +PrimaryAssetTypesToScan=(PrimaryAssetType="WeaponDefinition", AssetBaseClass=/Script/MyGame.WeaponDefinition, bHasBlueprintClasses=False, bIsEditorOnly=False, Directories=((Path="/Game/Data/Weapons")), Rules=(Priority=1,bApplyRecursively=True))。
- 自定义 AssetManager 子类:子类化 UAssetManager,重写 StartInitialLoading() 处理启动逻辑,在 DefaultEngine.ini 中用 [/Script/Engine.Engine] AssetManagerClassName=/Script/MyGame.UMyAssetManager 注册。
- 加载 Primary Assets:使用 UAssetManager::Get();用 GetPrimaryAssetIdList 列出某类型所有已注册 ID;用 LoadPrimaryAsset 传入 FPrimaryAssetId、bundle 名称和回调;回调中用 GetPrimaryAssetObject<T> 获取对象;用 LoadPrimaryAssetsWithType 加载某类型所有资源;不再需要时用 UnloadPrimaryAsset 卸载。
- Asset Bundles:用于分组软引用以选择性加载;在 UPROPERTY 上标注 meta = (AssetBundles = "BundleName");Asset Manager 只加载请求 bundle 的资源;用 ChangeBundleStateForPrimaryAssets(ids, AddBundles, RemoveBundles) 切换 bundle。
- Asset Registry:IAssetRegistry 允许在不加载资源的情况下查询资源元数据;通过 IAssetRegistry::GetChecked() 或 FAssetRegistryModule::GetRegistry() 获取。
- 用 GetAssetsByPath 获取某路径下某类的所有资源,bRecursive=true 表示递归。
- 用 GetAssetsByClass 获取某特定类的所有资源,传入 FTopLevelAssetPath 和 bSearchSubClasses=true。
- FAssetData 是轻量的,不会触发资源加载;可读取 Data.AssetName.ToString()、Data.GetSoftObjectPath()、Data.GetTagValue(FName, FString)。
- 按 tag 值查询:构造 TMultiMap<FName,FString> TagFilter,并调用 AR.GetAssetsByTagValues(TagFilter, AssetDataList)。
- 让属性可搜索:用 UPROPERTY(EditDefaultsOnly, AssetRegistrySearchable) 标记 FName WeaponType。
- 避免硬引用一切:不要为很少使用的效果使用 TObjectPtr<UParticleSystem> HitEffect;改用 TSoftObjectPtr<UParticleSystem>,只在效果实际触发时加载。
- 避免在游戏线程加载:不要对大 skeletal mesh 调用 LoadSynchronous;改用 RequestAsyncLoad 并在回调中应用结果。
- 保持 handle 存活:不要把 TSharedPtr<FStreamableHandle> 只作为局部变量;应存为成员变量,直到不再需要这些资源。
- 注册 Primary Asset Types:确保 UPrimaryDataAsset 子类列在 DefaultGame.ini 的 PrimaryAssetTypesToScan 下;否则 GetPrimaryAssetIdList 返回空,LoadPrimaryAsset 会静默失败。
- 避免循环软引用解析:软引用自身又持有指回自身的软引用会形成加载环;用 Asset Bundles 打破循环,只加载当前状态需要的 bundle。
- DataTable 列变更:给已有 DataTable struct 增加列会使序列化行数据失效,除非在表上设置 bPreserveExistingValues 或重新导入;删除列会导致所有已有行丢失该数据;生产环境改 struct 前一定要备份 DataTable 资源。
- Cooked 与 uncooked 路径:uncooked 构建使用 /Game/;cooked 路径不同;不要硬编码路径;使用来自 UPROPERTY 引用的 FSoftObjectPath。
- Asset Manager 与 cook:无法通过 Primary Asset 扫描规则或硬引用触达的资源会被排除出 cook;用 PrimaryAssetRules 或显式 asset labels 确保包含。
- Hot reload:PIE 期间 UDataAsset 变更可能要等资源完全重新加载后才反映;用 PostLoad 或 PostEditChangeProperty 做编辑器时更新。
- 内存预算:通过 UAssetManager::GetPrimaryAssetObjectList 按类型跟踪已加载资源;场景变化时用 ChangeBundleStateForPrimaryAssets 在 UI 与 Game bundle 间切换。
- bStripFromClientBuilds:对包含仅服务器数据的 UDataTable 实例设置该项,例如带掉落率的 loot tables,防止分发到客户端。
- 相关扩展参考:ue-cpp-foundations 涉及 UPROPERTY specifiers、USTRUCT、UObject 生命周期;ue-serialization-savegames 涉及跨会话保存与加载软对象引用;ue-module-build-system 涉及在 Build.cs 添加 AssetRegistry、Engine 模块依赖。
适用边界 · 注意事项
- 不适用于非 Unreal Engine 项目,或不需要 UE 数据资产、DataTable、Asset Manager、异步资源加载系统的场景。
- 如果数据量很小且不需要设计师编辑、异步加载或内存控制,直接硬编码或简单配置可能更合适。
- 如果项目不能修改 DefaultGame.ini/DefaultEngine.ini,或不能使用 Asset Manager,则不要按 PrimaryAsset 与 AssetBundle 方案设计。