关于 ue-data-assets-tables
面向 UE 5.8 开发者的数据与资源加载 Skill,涵盖设计师创作的数据容器及其运行时加载路径,并说明不同方案的适用场景。
主要功能
- 01选择数据容器
- 02数据资源与数据表
- 03软引用与硬引用
- 04异步资源加载
- 05Asset Manager 与烹饪规则
- 06资源注册表查询与设置
适用场景
- 01为游戏数据选择容器
- 02异步加载资源
- 03不加载资源而查找资源
- 04在运行时跨来源共享行数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-data-assets-tables安装后在 Claude Code 中输入/ue-data-assets-tables确认已加载。
附带文件references/asset-loading-patterns.mdreferences/data-driven-design.md
详细介绍
UE 数据资产与表格
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;旧版形式列在“已弃用 — 请勿使用”下。
本技能涵盖由设计师编写的游戏数据,以及将这些数据载入内存的路径:UDataAsset/UPrimaryDataAsset、UDataTable/UCurveTable、软引用、FStreamableManager、UAssetManager 主资产和烹饪规则、IAssetRegistry 查询、DataRegistry 插件和 UDeveloperSettings。Build.cs 模块:数据资产、表格和软指针需要 Engine 和 CoreUObject;注册表查询需要 AssetRegistry;设置类需要 DeveloperSettings;数据注册表需要 DataRegistry。深入内容请参阅资产加载模式和数据驱动设计模式。
上下文
如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、自定义 UAssetManager 子类、已注册的主资产类型)。如果该文件不存在,也不要停止。
根据请求和代码库确定相关领域。只有当两种合理解读会导致不同代码时才提问。
| 请求内容…… | 前往 |
|---|---|
| 选择游戏数据容器 | 选择数据容器 |
UDataAsset、UPrimaryDataAsset、资产包 | 数据资产 |
行结构体、CSV/JSON 导入、FindRow | 数据表 |
| 曲线、表格组合 | 曲线表与组合表 |
TSoftObjectPtr、FSoftObjectPath、内存预算 | 引用:硬引用与软引用 |
| 异步加载、句柄、优先级 | 异步加载 |
FPrimaryAssetId、扫描、包状态 | 资产管理器 |
烹饪、分块、PrimaryAssetTypesToScan | 烹饪规则 |
| 查找资产但不加载 | 资产注册表查询 |
| 在运行时跨来源共享行 | 数据注册表 |
| 项目设置中的全项目可调参数 | 项目设置 |
选择数据容器
| 需求 | 使用 | 头文件 |
|---|---|---|
| 每个对象的配置,由编辑器编写,每个条目对应一个资产 | UDataAsset | Engine/DataAsset.h |
| 同上,并支持按 ID 加载/卸载和烹饪规则 | UPrimaryDataAsset | Engine/DataAsset.h |
| 形状相同的扁平行,由电子表格编写 | UDataTable + FTableRowBase | Engine/DataTable.h |
| 合并自多个表格的行(DLC、覆盖项) | UCompositeDataTable | Engine/CompositeDataTable.h |
| 以行名称为键的浮点曲线 | UCurveTable、FCurveTableRowHandle | Engine/CurveTable.h |
| 在运行时跨多个来源解析同一个行 ID | UDataRegistrySubsystem | DataRegistrySubsystem.h |
| 项目设置中的全项目可调参数 | UDeveloperSettings | Engine/DeveloperSettings.h |
UDataAsset 支持蓝图子类和继承;UDataTable 的行不支持。UDataTable 开箱即支持按行名称查找;要让数据资产可被发现,需要执行 Asset Manager 扫描或 Asset Registry 查询。
数据资产
UDataAsset 是一种普通的 UObject,可以在 Content Browser 中实例化。UPrimaryDataAsset 添加了 GetPrimaryAssetId(),因此 UAssetManager 可以按 ID 扫描、加载和卸载它。
// MyWeaponDefinition.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "MyWeaponDefinition.generated.h"
class UTexture2D; class USkeletalMesh; // TSoftObjectPtr<T> needs T declared
UCLASS(BlueprintType)
class MYGAME_API UMyWeaponDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
FText WeaponName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
float FireRate = 1.f;
/** Written into the Asset Registry so it can be filtered without loading. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", AssetRegistrySearchable)
FName WeaponClassTag;
/** Bundles group soft references so the Asset Manager can load a subset. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art", meta = (AssetBundles = "UI"))
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art", meta = (AssetBundles = "Game"))
TSoftObjectPtr<USkeletalMesh> WorldMesh;
};
GetPrimaryAssetId() 规则(Engine/DataAsset.h:36-53):
- 默认返回的类型 = 沿继承层级向上查找时遇到的第一个原生类的名称(或最高层级 Blueprint 类的名称);名称 = 资产的 FName。
- Blueprint 子类应创作为纯数据 Blueprint,而不是 Data Asset 实例,这样对父类的编辑才能向下传递。
- 仅在需要不同的类型/名称方案时才重写;在类体内的声明为
virtual FPrimaryAssetId GetPrimaryAssetId() const override;。 UpdateAssetBundleData()会扫描类上的meta=(AssetBundles="Name"),并在PreSave期间填充AssetBundleDataUPROPERTY。Bundle 名称可自由指定;meta=(AssetBundles="Client,Server")会将一个属性放入两个 bundle。- 普通
UDataAsset没有主资产 ID,只能通过引用它的对象加载。
数据表
每个行结构体都继承 FTableRowBase(Engine/DataTable.h:33),并使用 GENERATED_BODY()。
// MyItemTableRow.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "MyItemTableRow.generated.h"
class UStaticMesh;
USTRUCT(BlueprintType)
struct FMyItemTableRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 MaxStack = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
TSoftObjectPtr<UStaticMesh> PreviewMesh;
/** Called after CSV/JSON import. Not editor-only. */
virtual void OnPostDataImport(const UDataTable* InDataTable, const FName InRowName, TArray<FString>& OutCollectedImportProblems) override;
/** Called for every row whenever the owning table is modified. Not editor-only. */
virtual void OnDataTableChanged(const UDataTable* InDataTable, const FName InRowName) override;
};
FTableRowBase::IsDataValid(FDataValidationContext& Context) const 是第三个钩子,并且确实由 #if WITH_EDITOR 包裹(Engine/DataTable.h:60-68);任何重写都应使用相同的条件编译保护,并包含 Misc/DataValidation.h。
查询
UPROPERTY(EditDefaultsOnly, Category = "Data")
TObjectPtr<UDataTable> ItemTable;
// FindRow<T>(FName, const TCHAR*|const FString&, bool bWarnIfRowMissing = true) -> T* or nullptr.
const FMyItemTableRow* Row = ItemTable->FindRow<FMyItemTableRow>(RowName, TEXT("LookupItem"));
TArray<FMyItemTableRow*> AllRows;
ItemTable->GetAllRows<FMyItemTableRow>(TEXT("GetAllItems"), AllRows);
TArray<FName> RowNames = ItemTable->GetRowNames();
ItemTable->ForeachRow<FMyItemTableRow>(TEXT("Foreach"),
[](const FName& Key, const FMyItemTableRow& Value)
{
// Key is the row name, Value the typed row.
});
// GetRowMap() is the raw TMap<FName, uint8*>; use it only for generic/reflection code.
const int32 NumRows = ItemTable->GetRowMap().Num();
FDataTableRowHandle 是适用于 UPROPERTY 的引用(包含表和行名称),设计人员可在编辑器中选择:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
FDataTableRowHandle StartingItemHandle; // IsNull() when nothing is picked
const FMyItemTableRow* Starting = StartingItemHandle.GetRow<FMyItemTableRow>(TEXT("StartingItem"));
运行时修改与导入
FMyItemTableRow NewRow;
NewRow.MaxStack = 5;
ItemTable->AddRow(FName(TEXT("RuntimeSword")), NewRow); // In memory only, not saved to disk.
ItemTable->RemoveRow(FName(TEXT("ObsoleteItem")));
ItemTable->HandleDataTableChanged(); // Fires the per-row OnDataTableChanged hooks.
// RowStruct must already be set. Available in all build configurations.
TArray<FString> Problems = ItemTable->CreateTableFromCSVString(CsvContent);
TArray<FString> JsonProblems = ItemTable->CreateTableFromJSONString(JsonContent);
GetTableAsCSV()、GetTableAsJSON() 和 GetTableAsString() 位于 #if WITH_EDITOR 内(Engine/DataTable.h:318-345)——它们在打包后的构建中不存在。表资产上的 bStripFromClientBuilds 会使 NeedsLoadForClient() 返回 false,因此仅供服务器使用的表不会发送到客户端(Engine/DataTable.h:117,149)。
Blueprint 访问
UDataTableFunctionLibrary(Kismet/DataTableFunctionLibrary.h)向 Blueprint 提供数据表访问功能:GetDataTableRowFromName、DoesDataTableRowExist、GetDataTableRowNames、GetDataTableColumnNames、GetDataTableColumnAsString、GetDataTableRowStruct、EvaluateCurveTableRow、GetCurveTableRowNames,以及面向编辑器脚本的 FillDataTableFromCSVString / FillDataTableFromCSVFile。
曲线表与复合表
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scaling")
FCurveTableRowHandle DamageCurve;
float ScaledDamage = DamageCurve.Eval(CharacterLevel, TEXT("ScaleDamage"));
// Or take the curve once for repeated sampling.
const FRealCurve* Curve = DamageCurve.GetCurve(TEXT("ScaleDamage"));
FCurveTableRowHandle 还提供 GetRichCurve()、GetSimpleCurve()、IsNull() 和 Eval(float XValue, float* YValue, const FString& ContextString),后者会报告查询是否成功。
UCompositeDataTable 会将其 ParentTables 数组合并为一个行映射;后面的表会按行名称覆盖前面的表。可将其用于基础游戏加 DLC,或用于按平台覆盖。AppendParentTables()、RemoveParentTables() 和 GetParentTables() 用于管理该层级;AddRow 和 RemoveRow 被重写为空操作(Engine/CompositeDataTable.h:43-47、CompositeDataTable.cpp:191-204),因此应修改父表。
引用:硬引用与软引用
| 形式 | 所属对象加载时是否加载目标 | 用途 |
|---|---|---|
TObjectPtr<UStaticMesh> | 是 | 始终需要、体积较小或已驻留内存的资产 |
TSubclassOf<AActor> | 是(类) | 频繁生成的类 |
TSoftObjectPtr<UStaticMesh> | 否 | 数据引用的美术资源、音频和 VFX |
TSoftClassPtr<AActor> | 否 | 由数据选择的 Blueprint 类 |
FSoftObjectPath | 否 | 通用系统中的非类型化路径 |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art")
TSoftObjectPtr<UStaticMesh> MeshSoft;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spawning")
TSoftClassPtr<AActor> SpawnableSoft; // LoadSynchronous() returns UClass*
const FSoftObjectPath MeshPath = MeshSoft.ToSoftObjectPath();
// State checks, no loading.
const bool bNoPathSet = MeshSoft.IsNull();
const bool bInMemory = MeshSoft.IsValid(); // path set AND target resolved in memory
const bool bLoadPending = MeshSoft.IsPending(); // path set, target not in memory yet
UStaticMesh* Resident = MeshSoft.Get(); // nullptr unless already loaded
// Blocking resolution. Avoid on the game thread outside loading screens.
UStaticMesh* Mesh = MeshSoft.LoadSynchronous();
UObject* Loaded = MeshPath.TryLoad(); // nullptr if missing, does not assert
UObject* Existing = MeshPath.ResolveObject(); // in-memory only, never loads
UPrimaryDataAsset 中的硬引用会在定义加载时将其整个依赖链载入内存——这是数据驱动项目中内存失控的最大单一原因。内容默认使用软引用,值和小型结构体则使用硬引用。
异步加载
FStreamableManager(Engine/StreamableManager.h)由 Asset Manager 持有;通过 UAssetManager::GetStreamableManager() 获取,切勿在游戏代码中自行构造。
RequestAsyncLoad 有两种形式。经典形式依次接受路径、委托、TAsyncLoadPriority Priority、bool bManageActiveHandle、bool bStartStalled 和 FString DebugName。参数形式则接受单个 FStreamableAsyncLoadParams&&。
// AMyPickupActor members: the handle owns the load, so it must not be a local.
TSharedPtr<FStreamableHandle> LoadHandle;
UPROPERTY(EditDefaultsOnly, Category = "Art")
TSoftObjectPtr<UStaticMesh> MeshSoft;
UPROPERTY(VisibleAnywhere, Category = "Art")
TObjectPtr<UStaticMeshComponent> MeshComponent;
void AMyPickupActor::BeginLoadMesh()
{
FStreamableManager& SM = UAssetManager::GetStreamableManager();
LoadHandle = SM.RequestAsyncLoad(
MeshSoft.ToSoftObjectPath(),
FStreamableDelegate::CreateUObject(this, &AMyPickupActor::OnMeshLoaded),
FStreamableManager::AsyncLoadHighPriority,
/*bManageActiveHandle=*/false,
/*bStartStalled=*/false,
TEXT("Pickup mesh"));
}
void AMyPickupActor::OnMeshLoaded()
{
if (UStaticMesh* Mesh = MeshSoft.Get())
{
MeshComponent->SetStaticMesh(Mesh); // Component now holds a hard reference.
}
LoadHandle.Reset();
}
当需要取消/更新委托或 JIT 渐进加载时,使用参数形式:
FStreamableAsyncLoadParams Params;
Params.TargetsToStream = { IconSoft.ToSoftObjectPath(), MeshSoft.ToSoftObjectPath() };
Params.Priority = FStreamableManager::AsyncLoadHighPriority;
Params.bUseJustInTimeAsyncLoader = true;
Params.OnComplete = FStreamableDelegateWithHandle::CreateWeakLambda(this,
[this](TSharedPtr<FStreamableHandle> Handle)
{
// Both targets resolved.
});
LoadHandle = SM.RequestAsyncLoad(MoveTemp(Params), TEXT("Pickup bundle"));
RequestSyncLoad(TargetsToStream, bManageActiveHandle = false, DebugName = FString()) 会阻塞直到加载完成——仅用于加载界面和一次性初始化。
FStreamableHandle 成员 | 含义 |
|---|---|
IsActive() | 请求存在,且尚未释放或取消 |
HasLoadCompleted() | 所有目标均已完成(委托可能尚未运行) |
IsLoadingInProgress() | 仍在流式加载 |
WasCanceled() / HasError() | 已由你取消/一个或多个目标加载失败 |
GetLoadProgress() | 0.0 到 1.0 |
GetLoadedAsset() / GetLoadedAsset<T>() | 第一个目标;使用 GetLoadedAssets(TArray<T*>&) 获取全部目标 |
WaitUntilComplete(float Timeout = 0.f, bool bStartStalledHandles = true) | 阻塞调用方,返回 EAsyncPackageState::Type |
BindCompleteDelegate / BindCancelDelegate / BindUpdateDelegate | 请求发出后附加回调 |
CancelHandle() | 停止加载;完成委托不会触发 |
ReleaseHandle() | 移除流式加载引用,使资源可被回收 |
句柄就是引用。 通过 RequestAsyncLoad 加载的资源,只有在指向该请求的 TSharedPtr<FStreamableHandle> 存续期间(或 bManageActiveHandle 为 true 时)才会保持存活。在完成前丢弃所有指针并不会取消请求:管理器会持有句柄,直到完成委托运行完毕,然后释放它(Engine/StreamableManager.h:316-320);因此,除非有其他强引用,否则这些资源会在下次 GC 时被回收。
委托工厂:FStreamableDelegate::CreateUObject(this, &UMyLoader::Fn)(安全,会检查对象)、CreateWeakLambda(this, Lambda)(安全,所有者销毁后会丢弃回调)、CreateLambda(Lambda)(若捕获了 this 则不安全)。
UE_ENABLE_STREAMABLE_JIT_ASYNC_LOADING(Engine/StreamableManager.h:20)默认为 0,并在编译时控制即时异步加载器。s.StreamableEnableJITAsyncLoading 会为通过 bUseJustInTimeAsyncLoader 选择启用该加载器的请求启用此功能;s.StreamableEnableJITAsyncLoadingGlobally 则会为所有请求强制启用。JIT 会将请求逐步交给异步加载器,而不是一次性全部排队,从而在高负载下保持优先级调整和取消操作的响应能力。FStreamableHandle::IsUsingJustInTimeAsyncLoader() 会报告最终生效状态。
更多模式——批处理、组合句柄、进度、取消——请参阅资源加载模式。
资产管理器
UAssetManager 是通过 [/Script/Engine.Engine] AssetManagerClassName 注册的全局单例。继承该类并重写 StartInitialLoading() 和 PostInitialAssetScan()。
UAssetManager& AM = UAssetManager::Get(); // Engine/AssetManager.h:97
TArray<FPrimaryAssetId> WeaponIds;
AM.GetPrimaryAssetIdList(FPrimaryAssetType(TEXT("MyWeaponDefinition")), WeaponIds);
const FPrimaryAssetId WeaponId(FPrimaryAssetType(TEXT("MyWeaponDefinition")), FName(TEXT("DA_Sword")));
TSharedPtr<FStreamableHandle> Handle = AM.LoadPrimaryAsset(
WeaponId,
TArray<FName>{ TEXT("Game") },
FStreamableDelegate::CreateUObject(this, &UMyItemSubsystem::OnWeaponLoaded));
UMyWeaponDefinition* Def = AM.GetPrimaryAssetObject<UMyWeaponDefinition>(WeaponId);
AM.ChangeBundleStateForPrimaryAssets(
{ WeaponId },
TArray<FName>{ TEXT("Game") }, // AddBundles
TArray<FName>{ TEXT("UI") }); // RemoveBundles
const int32 NumUnloaded = AM.UnloadPrimaryAsset(WeaponId);
函数签名(Engine/AssetManager.h:333,340):
virtual TSharedPtr<FStreamableHandle> LoadPrimaryAsset(
const FPrimaryAssetId& AssetToLoad,
const TArray<FName>& LoadBundles = TArray<FName>(),
FStreamableDelegate DelegateToCall = FStreamableDelegate(),
TAsyncLoadPriority Priority = FStreamableManager::DefaultAsyncLoadPriority,
UE::FSourceLocation Location = UE::FSourceLocation::Current());
virtual TSharedPtr<FStreamableHandle> LoadPrimaryAsset(
const FPrimaryAssetId& AssetToLoad,
const TArray<FName>& LoadBundles,
FAssetManagerLoadParams&& LoadParams,
UE::FSourceLocation Location = UE::FSourceLocation::Current());
LoadPrimaryAssets(复数形式)、LoadPrimaryAssetsWithType、ChangeBundleStateForPrimaryAssets 和 ChangeBundleStateForMatchingPrimaryAssets 都有相同的两种形式。Location 会由编译器填充,切勿传入。新代码优先使用 FAssetManagerLoadParams 重载;它包含 OnComplete、OnCancel(二者都是 FStreamableDelegateWithHandle)、OnUpdate 和 Priority。
与原始可流式传输请求不同,主资源会一直保持加载状态,直到你卸载它们:无需让返回的句柄保持有效,只需用它轮询或等待即可。
ScanPathsForPrimaryAssets(FPrimaryAssetType, const TArray<FString>& Paths, UClass* BaseClass, bool bHasBlueprintClasses, bool bIsEditorOnly = false, bool bForceSynchronousScan = true) 会在配置驱动的扫描无法满足需求时,从代码中注册类型。AddDynamicAsset(const FPrimaryAssetId&, const FSoftObjectPath&, const FAssetBundleData&) 会注册运行时生成的主资源。
烹饪规则
; DefaultGame.ini
[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="MyWeaponDefinition",AssetBaseClass="/Script/MyGame.MyWeaponDefinition",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Data/Weapons")),Rules=(Priority=1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook))
FPrimaryAssetTypeInfo 字段(Engine/AssetManagerTypes.h:131):PrimaryAssetType、AssetBaseClass、bHasBlueprintClasses、bIsEditorOnly、Directories、SpecificAssets、Rules。FPrimaryAssetRules(Engine/AssetManagerTypes.h:66):Priority、ChunkId、bApplyRecursively、CookRule。
EPrimaryAssetCookRule(Engine/AssetManagerTypes.h:28):
| 值 | 含义 |
|---|---|
Unknown | 仅当有其他资源引用时,才会在开发和生产版本中烹饪 |
NeverCook | 从不烹饪;依赖此资源会报错 |
ProductionNeverCook | 在开发版本中,若被引用则会烹饪;在生产版本中从不烹饪 |
DevelopmentAlwaysProductionNeverCook | 在开发版本中始终烹饪,在生产版本中从不烹饪 |
DevelopmentAlwaysProductionUnknownCook | 在开发版本中始终烹饪;在生产版本中仅当被引用时才烹饪 |
AlwaysCook | 在开发和生产版本中始终烹饪 |
UAssetManagerSettings(config = Game、defaultconfig)还提供 DirectoriesToExclude、PrimaryAssetRules、CustomPrimaryAssetRules 和 bOnlyCookProductionAssets。在发布分支中启用最后一项,这样 ProductionNeverCook 资源会报错,而不是泄漏到构建中。
既无法从扫描到的主资源访问、也没有硬引用的资源不会被烹饪。仅有软引用不会将资源纳入烹饪;请为其指定主资源类型或 UPrimaryAssetLabel。
资产注册表查询
IAssetRegistry 无需加载资产即可读取其元数据。
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/IAssetRegistry.h"
IAssetRegistry& AR = IAssetRegistry::GetChecked(); // AssetRegistry/IAssetRegistry.h:272
FARFilter Filter;
Filter.PackagePaths.Add(FName(TEXT("/Game/Data/Weapons")));
Filter.bRecursivePaths = true;
Filter.ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/MyGame"), TEXT("MyWeaponDefinition")));
Filter.bRecursiveClasses = true;
TArray<FAssetData> Found;
AR.GetAssets(Filter, Found);
// Direct call; the class argument is FTopLevelAssetPath, never FName.
AR.GetAssetsByClass(FTopLevelAssetPath(TEXT("/Script/MyGame"), TEXT("MyWeaponDefinition")), Found, /*bSearchSubClasses=*/true);
for (const FAssetData& Data : Found)
{
const FTopLevelAssetPath ClassPath = Data.AssetClassPath; // Data.AssetName for the short name.
FString TagValue;
Data.GetTagValue(FName(TEXT("WeaponClassTag")), TagValue); // Registry read, still no load.
UObject* Asset = Data.GetAsset(); // This one does load.
}
在模块尚未启动前,IAssetRegistry::Get() 返回的指针可能为 null;GetChecked() 则会触发断言,FAssetRegistryModule::GetRegistry() 是模块级的等效方法。对 UPROPERTY 使用 AssetRegistrySearchable,可将该属性写入注册表,这样便能通过 FARFilter::TagsAndValues 和 GetAssetsByTagValues 筛选,而无需加载资产。资产可能尚未被发现时,请使用 ScanPathsSynchronous()。
数据注册表
DataRegistry(在 5.8 中为 Beta;插件默认关闭,模块为 DataRegistry)会根据数据表、曲线表或自定义数据源组成的链解析一个 FDataRegistryId,因此游戏逻辑代码只需按 ID 查询条目,无需知道具体由哪个表或 DLC 提供。
#include "DataRegistryId.h"
#include "DataRegistrySubsystem.h"
UDataRegistrySubsystem* Registry = UDataRegistrySubsystem::Get();
const FDataRegistryId ItemId(FName(TEXT("ItemRegistry")), FName(TEXT("Sword")));
// Cached read: non-null only if the source is already resident.
const FMyItemTableRow* Row = Registry->GetCachedItem<FMyItemTableRow>(ItemId);
// Otherwise request it, then re-query the cache in the callback.
Registry->AcquireItem(ItemId, FDataRegistryItemAcquiredCallback::CreateWeakLambda(this,
[ItemId](const FDataRegistryAcquireResult& Result)
{
const FMyItemTableRow* Acquired =
UDataRegistrySubsystem::Get()->GetCachedItem<FMyItemTableRow>(ItemId);
}));
UDataRegistrySource_DataTable 通过 FDataRegistrySource_DataTableRules(bPrecacheTable、CachedTableKeepSeconds),将注册表指向一个 TSoftObjectPtr<UDataTable> SourceTable;UDataRegistrySource_CurveTable 对曲线表也执行相同操作。注册表资源通过 UDataRegistrySettings 进行配置。
项目设置
UDeveloperSettings(模块 DeveloperSettings)会自动在项目设置中注册一个类,并从以类名命名的 ini 区段读取设置。
声明类 UCLASS(config = Game, defaultconfig, meta = (DisplayName = "My Game")),使其派生自 UDeveloperSettings,将每个字段标记为 UPROPERTY(config, EditAnywhere, Category = "Economy"),并通过 GetDefault<UMyGameSettings>() 读取。config = Game 加上 defaultconfig 会将设置写入 DefaultGame.ini 中的 [/Script/MyGame.MyGameSettings];重写 GetContainerName()、GetCategoryName() 或 GetSectionName() 可设置页面的位置。对于少量全局可调参数,可使用此设置;对于设计师需要逐项迭代调整的内容,则使用数据资产——完整类定义(包含 include 和 Build.cs 依赖项)见数据驱动设计模式。
已弃用 — 请勿使用
| 不要输出 | 在 5.8 中使用 | 来源 |
|---|---|---|
TAssetPtr<T>, TAssetSubclassOf<T> | TSoftObjectPtr<T>, TSoftClassPtr<T> | 5.8 头文件中不存在;UObject/SoftObjectPtr.h:173,795 |
FStringAssetReference | FSoftObjectPath | 5.8 头文件中不存在;UObject/SoftObjectPath.h |
UAssetManager::IsValid(), GetIfValid() | IsInitialized(), GetIfInitialized() | 在 Engine/AssetManager.h:91,99 中标记为 UE_DEPRECATED(5.3) |
重写 OnAssetStateChangeCompleted(...) | NotifyOnAssetStateChangeCompleted | 在 Engine/AssetManager.h:1035 中标记为 UE_DEPRECATED(5.6) |
LoadAssetListInternal(...) | 接受 FAssetManagerLoadParams 的 LoadAssetList(...) | 在 Engine/AssetManager.h:1072 中标记为 UE_DEPRECATED(5.6) |
ShouldSetManager(Manager, Source, Target, ...) | ShouldSetManager(UE::AssetRegistry::FShouldSetManagerContext&) | 在 Engine/AssetManager.h:1082 中标记为 UE_DEPRECATED(5.6) |
UpdateManagementDatabase(bool) | UpdateManagementDatabase(EUpdateManagementDatabaseFlags) | 在 Engine/AssetManager.h:924 中标记为 UE_DEPRECATED(5.6) |
BuildChunkMap(PackagesToUpdateChunksFor) | 三参数版本的 BuildChunkMap | 在 Engine/AssetManager.h:1129 中标记为 UE_DEPRECATED(5.6) |
FAssetData::AssetClass(短名称) | FAssetData::AssetClassPath(FTopLevelAssetPath) | 在 AssetRegistry/AssetData.h:198 中标记为 UE_DEPRECATED(5.1) |
GetAssetsByClass(FName ClassName, ...) | GetAssetsByClass(FTopLevelAssetPath, ...) | AssetRegistry/IAssetRegistry.h:335 |
返回数组的 UCurveTable::GetCurves() | GetCurves(TAdderReserverRef<FRichCurveEditInfoConst>) | 在 Engine/CurveTable.h:106 中标记为 UE_DEPRECATED(5.6) |
UDataTable::RowStructName | 运行时使用 GetRowStruct();RowStructPathName 属于 WITH_EDITORONLY_DATA | Engine/DataTable.h:168 中的 RowStructName_DEPRECATED;GetRowStruct() 位于 :110 |
行结构体中的 GENERATED_USTRUCT_BODY() | GENERATED_BODY() | 两者都能编译;当前项目规范只使用后者 |
Build.cs 依赖列表中的 "AssetManager" | "Engine" | 此模块不存在;Engine/Classes/Engine/AssetManager.h |
常见错误
**让流式加载句柄失效:**已加载的资源由句柄引用,而不是由软指针引用。在硬引用(组件、TObjectPtr、数组)持有资源之前,将 TSharedPtr<FStreamableHandle> 存为成员变量,然后再重置它。
从数据资产硬引用内容:UPrimaryDataAsset 中的 TObjectPtr<UNiagaraSystem> 会随定义一起加载特效。请改用 TSoftObjectPtr 和资源包。
**在 Tick 或游戏关键路径中调用 LoadSynchronous():**如果资源尚未驻留,它会阻塞游戏线程。启动一次异步加载,并在回调中执行操作。
**将 FName 传给 GetAssetsByClass:**5.8 要求使用 FTopLevelAssetPath(TEXT("/Script/Module"), TEXT("ClassName"))。短类名无法通过编译。
**忘记设置 PrimaryAssetTypesToScan:**如果未注册类型,GetPrimaryAssetIdList 不会返回任何内容,LoadPrimaryAsset 也会静默无操作。在 DefaultGame.ini 中注册类型,或调用 ScanPathsForPrimaryAssets。
**在裸 CreateLambda 中捕获 this:**对象销毁后,回调仍可能触发。请使用 CreateUObject 或 CreateWeakLambda(this, Lambda)。
**在运行时调用 GetTableAsCSV():**它仅在 WITH_EDITOR 下可用,在已烘焙的构建中无法链接。请自行序列化行数据。同样,重命名或移除行结构体属性会导致现有行在下次导入时丢失该列——修改结构体前先将表格导出为 CSV,然后重新导入。
相关技能
ue-cpp-foundations—UPROPERTY/USTRUCT说明符、TObjectPtr、UObject 生命周期、子系统表ue-async-threading— 流式加载之外的一般异步惯用法(UE::Tasks、Async、FTSTicker)ue-serialization-savegames— 跨会话持久化软对象路径和主资产 IDue-game-features— 在插件中随包发布数据资产,以及游戏功能资产扫描ue-gameplay-abilities— 使用这些数据资产的能力和特效定义ue-world-level-streaming— 关卡流送、World Partition 和 Data Layersue-module-build-system— 在Build.cs中添加AssetRegistry、DeveloperSettings和插件模块ue-audio-system— UAudioComponent、MetaSounds、子混音、衰减和并发ue-blueprint-cpp-interop— 将 C++ 暴露给 Blueprint:UFUNCTION/UPROPERTY 元数据键、潜在操作和异步节点ue-editor-tools— 细节自定义、编辑器实用工具控件、UToolMenus 和编辑器子系统ue-gameplay-tags-messaging— 原生和 ini 游戏玩法标签、容器、查询和异步消息系统ue-materials-rendering— 材质实例、参数集合、渲染目标和后期处理ue-procedural-generation— PCG 图表、程序化和动态网格体、实例化和样条线ue-ui-umg-slate— UMG 控件、Slate、Common UI 和 MVVM