mcpField
U
ue-data-assets-tables

ue-data-assets-tables

github.com/quodsoler开发4191.2k 次安装4 周前更新
代码仓库

关于 ue-data-assets-tables

面向 UE 5.8 开发者的数据与资源加载 Skill,涵盖设计师创作的数据容器及其运行时加载路径,并说明不同方案的适用场景。

主要功能

  1. 01选择数据容器
  2. 02数据资源与数据表
  3. 03软引用与硬引用
  4. 04异步资源加载
  5. 05Asset Manager 与烹饪规则
  6. 06资源注册表查询与设置

适用场景

  1. 01为游戏数据选择容器
  2. 02异步加载资源
  3. 03不加载资源而查找资源
  4. 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烹饪规则
查找资产但不加载资产注册表查询
在运行时跨来源共享行数据注册表
项目设置中的全项目可调参数项目设置

选择数据容器

需求使用头文件
每个对象的配置,由编辑器编写,每个条目对应一个资产UDataAssetEngine/DataAsset.h
同上,并支持按 ID 加载/卸载和烹饪规则UPrimaryDataAssetEngine/DataAsset.h
形状相同的扁平行,由电子表格编写UDataTable + FTableRowBaseEngine/DataTable.h
合并自多个表格的行(DLC、覆盖项)UCompositeDataTableEngine/CompositeDataTable.h
以行名称为键的浮点曲线UCurveTable、FCurveTableRowHandleEngine/CurveTable.h
在运行时跨多个来源解析同一个行 IDUDataRegistrySubsystemDataRegistrySubsystem.h
项目设置中的全项目可调参数UDeveloperSettingsEngine/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 期间填充 AssetBundleData UPROPERTY。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
FStringAssetReferenceFSoftObjectPath5.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_DATAEngine/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 — 跨会话持久化软对象路径和主资产 ID
  • ue-game-features — 在插件中随包发布数据资产,以及游戏功能资产扫描
  • ue-gameplay-abilities — 使用这些数据资产的能力和特效定义
  • ue-world-level-streaming — 关卡流送、World Partition 和 Data Layers
  • ue-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

评论

支持 Markdown