ue-data-assets-tables

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE Data Assets and Tables

UE Data Assets 与 DataTables

You are an expert in Unreal Engine's data management and asset loading systems.

您是Unreal Engine数据管理与资产加载系统方面的专家。

Context

背景信息

Read
.agents/ue-project-context.md
for project-specific data patterns, module layout, plugin dependencies, and any custom AssetManager subclass or DataAsset conventions the project has established.

请阅读
.agents/ue-project-context.md
了解项目特定的数据模式、模块布局、插件依赖,以及项目已确立的自定义AssetManager子类或DataAsset约定。

Information Gathering

信息收集

Before generating code or advice, ask:
  1. What kind of data is being stored? (item stats, level config, ability definitions, NPC data, etc.)
  2. Is this data authored by designers in spreadsheets, or configured directly in the editor?
  3. What are the loading requirements — always in memory, loaded per-level, streamed on demand?
  4. Is memory budget a concern? How many instances are expected?
  5. Does the project already use a custom
    UAssetManager
    subclass?

在生成代码或提供建议前,请询问以下问题:
  1. 要存储的数据类型是什么?(物品属性、关卡配置、技能定义、NPC数据等)
  2. 这些数据是由设计师在电子表格中制作,还是直接在编辑器中配置?
  3. 加载需求是什么——始终驻留内存、按关卡加载、按需流加载?
  4. 内存预算是否需要关注?预计会有多少实例?
  5. 项目是否已使用自定义
    UAssetManager
    子类?

Core Framework

核心框架

DataAssets vs DataTables — Choosing the Right Tool

DataAssets vs DataTables — 选择合适的工具

ConcernDataAssetDataTable
StructureC++ class with typed UPROPERTY fieldsRow struct, all rows same shape
Designer workflowEditor-authored instances, picker UISpreadsheet import (CSV/JSON)
Hierarchy / inheritanceYes, via Blueprint subclassesNo
Asset Manager integrationYes (
UPrimaryDataAsset
)
Not directly
Bulk lookup by row nameNoYes (
FindRow
)
Best forPer-item config objectsLarge flat tables (loot, dialogue, XP curves)

考量因素DataAssetDataTable
结构带有类型化UPROPERTY字段的C++类行结构体,所有行结构一致
设计师工作流编辑器创建实例,带有选择器UI电子表格导入(CSV/JSON)
层级/继承支持,通过Blueprint子类实现不支持
Asset Manager集成支持(
UPrimaryDataAsset
不直接支持
按行名称批量查找不支持支持(
FindRow
最佳适用场景单个物品的配置对象大型扁平表(战利品、对话、经验曲线)

DataAssets

DataAssets

UDataAsset — Simple Configuration Objects

UDataAsset — 简单配置对象

UDataAsset
(declared in
Engine/DataAsset.h
) is the base class. Assets are only loaded when directly referenced or explicitly loaded. Subclass it with typed UPROPERTY fields:
cpp
UCLASS(BlueprintType)
class MYGAME_API UMyItemData : public UDataAsset
{
    GENERATED_BODY()
public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") FText DisplayName;
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") float BaseDamage = 10.f;
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") TSoftObjectPtr<UStaticMesh> Mesh;
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") TSoftClassPtr<AActor> SpawnClass;
};
In the editor: right-click in Content Browser > Miscellaneous > Data Asset, select
UMyItemData
.
UDataAsset
(在
Engine/DataAsset.h
中声明)是基类。资产仅在被直接引用或显式加载时才会加载。通过带有类型化UPROPERTY字段的子类实现:
cpp
UCLASS(BlueprintType)
class MYGAME_API UMyItemData : public UDataAsset
{
    GENERATED_BODY()
public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") FText DisplayName;
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") float BaseDamage = 10.f;
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") TSoftObjectPtr<UStaticMesh> Mesh;
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item") TSoftClassPtr<AActor> SpawnClass;
};
在编辑器中操作:在内容浏览器中右键 > 杂项 > 数据资产,选择
UMyItemData

UPrimaryDataAsset — Asset Manager Integration

UPrimaryDataAsset — Asset Manager集成

UPrimaryDataAsset
overrides
GetPrimaryAssetId()
so the Asset Manager can track, scan, and load it. The Primary Asset Type is derived from the first native class in the hierarchy.
cpp
// PrimaryAssetType == native class name; PrimaryAssetName == asset name.
UCLASS(BlueprintType)
class MYGAME_API UWeaponDefinition : public UPrimaryDataAsset
{
    GENERATED_BODY()
public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    FText WeaponName;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    float FireRate = 1.f;

    // meta = (AssetBundles = "X") groups soft refs for selective AM loading.
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon",
              meta = (AssetBundles = "UI"))
    TSoftObjectPtr<UTexture2D> Icon;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon",
              meta = (AssetBundles = "Game"))
    TSoftObjectPtr<USkeletalMesh> WorldMesh;
};

UPrimaryDataAsset
重写了
GetPrimaryAssetId()
,以便Asset Manager可以跟踪、扫描和加载它。主资产类型由层级结构中的第一个原生类派生而来。
cpp
// PrimaryAssetType == 原生类名称; PrimaryAssetName == 资产名称.
UCLASS(BlueprintType)
class MYGAME_API UWeaponDefinition : public UPrimaryDataAsset
{
    GENERATED_BODY()
public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    FText WeaponName;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    float FireRate = 1.f;

    // meta = (AssetBundles = "X") 将软引用分组,以便AM选择性加载.
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon",
              meta = (AssetBundles = "UI"))
    TSoftObjectPtr<UTexture2D> Icon;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon",
              meta = (AssetBundles = "Game"))
    TSoftObjectPtr<USkeletalMesh> WorldMesh;
};

DataTables

DataTables

Defining a Row Struct

定义行结构体

FTableRowBase
is declared in
Engine/DataTable.h
. Every row struct must inherit it and use
USTRUCT(BlueprintType)
.
cpp
// ItemTableRow.h
#pragma once
#include "Engine/DataTable.h"
#include "ItemTableRow.generated.h"

USTRUCT(BlueprintType)
struct FItemTableRow : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 MaxStack = 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Weight = 0.5f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSoftObjectPtr<UStaticMesh> PreviewMesh;

    // Called after CSV/JSON import. Override for custom fixups.
    virtual void OnPostDataImport(const UDataTable* InDataTable,
                                  const FName InRowName,
                                  TArray<FString>& OutCollectedImportProblems) override;
};
In the editor: right-click > Miscellaneous > Data Table, assign
FItemTableRow
as the row struct.
FTableRowBase
Engine/DataTable.h
中声明。每个行结构体都必须继承它并使用
USTRUCT(BlueprintType)
cpp
// ItemTableRow.h
#pragma once
#include "Engine/DataTable.h"
#include "ItemTableRow.generated.h"

USTRUCT(BlueprintType)
struct FItemTableRow : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 MaxStack = 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Weight = 0.5f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSoftObjectPtr<UStaticMesh> PreviewMesh;

    // CSV/JSON导入后调用。重写以实现自定义修正。
    virtual void OnPostDataImport(const UDataTable* InDataTable,
                                  const FName InRowName,
                                  TArray<FString>& OutCollectedImportProblems) override;
};
在编辑器中操作:右键 > 杂项 > 数据表,将
FItemTableRow
指定为行结构体。

Querying DataTables at Runtime

运行时查询DataTables

cpp
UPROPERTY(EditDefaultsOnly, Category = "Data")
TObjectPtr<UDataTable> ItemTable;

// FindRow<T>: returns nullptr if row not found or type mismatch.
const FItemTableRow* Row = ItemTable->FindRow<FItemTableRow>(
    RowName, TEXT("LookupItem"));

// GetAllRows<T>: fills array with pointers to all rows.
TArray<FItemTableRow*> AllRows;
ItemTable->GetAllRows<FItemTableRow>(TEXT("GetAllItems"), AllRows);

// ForeachRow: iterate with row name keys.
ItemTable->ForeachRow<FItemTableRow>(
    TEXT("ForeachRow"),
    [](const FName& Key, const FItemTableRow& Value)
    {
        UE_LOG(LogTemp, Log, TEXT("Row %s: weight=%.2f"), *Key.ToString(), Value.Weight);
    });
cpp
UPROPERTY(EditDefaultsOnly, Category = "Data")
TObjectPtr<UDataTable> ItemTable;

// FindRow<T>: 如果未找到行或类型不匹配,返回nullptr.
const FItemTableRow* Row = ItemTable->FindRow<FItemTableRow>(
    RowName, TEXT("LookupItem"));

// GetAllRows<T>: 将所有行的指针填充到数组中.
TArray<FItemTableRow*> AllRows;
ItemTable->GetAllRows<FItemTableRow>(TEXT("GetAllItems"), AllRows);

// ForeachRow: 按行名称键迭代.
ItemTable->ForeachRow<FItemTableRow>(
    TEXT("ForeachRow"),
    [](const FName& Key, const FItemTableRow& Value)
    {
        UE_LOG(LogTemp, Log, TEXT("Row %s: weight=%.2f"), *Key.ToString(), Value.Weight);
    });

Runtime Modification and Row Handles

运行时修改与行句柄

cpp
// AddRow/RemoveRow do not persist to disk.
FItemTableRow NewRow;
NewRow.DisplayName = FText::FromString(TEXT("Runtime Sword"));
ItemTable->AddRow(FName(TEXT("RuntimeSword")), NewRow);
ItemTable->RemoveRow(FName(TEXT("ObsoleteItem")));

// Import from CSV at runtime (RowStruct must be set beforehand).
TArray<FString> Problems = ItemTable->CreateTableFromCSVString(CsvContent);

// Import from JSON at runtime. JSON format uses "RowName" as key with struct fields as properties.
TArray<FString> JsonProblems = ItemTable->CreateTableFromJSONString(JsonContent);

// Export to CSV/JSON strings (WITH_EDITOR only — unavailable in cooked/shipping builds):
FString CsvOut  = ItemTable->GetTableAsCSV();
FString JsonOut = ItemTable->GetTableAsJSON();

// FDataTableRowHandle: a UPROPERTY-friendly typed row reference.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
FDataTableRowHandle StartingWeaponHandle;

const FItemTableRow* Row = StartingWeaponHandle.GetRow<FItemTableRow>(
    TEXT("StartingWeapon lookup"));

cpp
// AddRow/RemoveRow不会持久化到磁盘.
FItemTableRow NewRow;
NewRow.DisplayName = FText::FromString(TEXT("Runtime Sword"));
ItemTable->AddRow(FName(TEXT("RuntimeSword")), NewRow);
ItemTable->RemoveRow(FName(TEXT("ObsoleteItem")));

// 运行时从CSV导入(必须预先设置RowStruct).
TArray<FString> Problems = ItemTable->CreateTableFromCSVString(CsvContent);

// 运行时从JSON导入。JSON格式使用"RowName"作为键,结构体字段作为属性.
TArray<FString> JsonProblems = ItemTable->CreateTableFromJSONString(JsonContent);

// 导出为CSV/JSON字符串(仅在WITH_EDITOR模式下可用——在打包/发布版本中不可用):
FString CsvOut  = ItemTable->GetTableAsCSV();
FString JsonOut = ItemTable->GetTableAsJSON();

// FDataTableRowHandle: 支持UPROPERTY的类型化行引用.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
FDataTableRowHandle StartingWeaponHandle;

const FItemTableRow* Row = StartingWeaponHandle.GetRow<FItemTableRow>(
    TEXT("StartingWeapon lookup"));

Asset References

资产引用

Hard References

硬引用

cpp
// Hard ref: loaded when the referencing asset loads. Causes the mesh/material
// to be in memory as long as this object is alive.
UPROPERTY(EditDefaultsOnly, Category = "Art")
TObjectPtr<UStaticMesh> Mesh;          // UE5 TObjectPtr preferred over raw ptr
Use hard references only for assets that are always needed while this object exists (e.g., a character's skeleton).
cpp
// 硬引用:当引用资产加载时,该资产也会被加载。只要此对象存在,网格/材质就会驻留在内存中.
UPROPERTY(EditDefaultsOnly, Category = "Art")
TObjectPtr<UStaticMesh> Mesh;          // UE5中优先使用TObjectPtr而非原始指针
仅当对象存在期间始终需要该资产时才使用硬引用(例如角色的骨骼)。

Soft References

软引用

Soft references store a path string. The asset is NOT loaded until explicitly resolved.
cpp
// TSoftObjectPtr<T>: soft ref to an asset instance.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art")
TSoftObjectPtr<UStaticMesh> MeshSoft;

// TSoftClassPtr<T>: soft ref to a class (blueprint subclasses especially).
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spawning")
TSoftClassPtr<AActor> SpawnableSoft;

// FSoftObjectPath: untyped path, useful for generic systems.
FSoftObjectPath MeshPath = MeshSoft.ToSoftObjectPath();
软引用存储路径字符串。资产不会被加载,直到显式解析。
cpp
// TSoftObjectPtr<T>: 指向资产实例的软引用.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art")
TSoftObjectPtr<UStaticMesh> MeshSoft;

// TSoftClassPtr<T>: 指向类的软引用(尤其适用于Blueprint子类).
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spawning")
TSoftClassPtr<AActor> SpawnableSoft;

// FSoftObjectPath: 无类型路径,适用于通用系统.
FSoftObjectPath MeshPath = MeshSoft.ToSoftObjectPath();

Synchronous Resolution (avoid on game thread for large assets)

同步解析(大型资产避免在游戏线程使用)

cpp
UStaticMesh* Mesh = MeshSoft.LoadSynchronous();   // Blocks until loaded.

// FSoftObjectPath::TryLoad — returns nullptr if not on disk, does not assert.
UObject* Loaded = MeshPath.TryLoad();
cpp
UStaticMesh* Mesh = MeshSoft.LoadSynchronous();   // 阻塞直到加载完成.

// FSoftObjectPath::TryLoad — 如果资产不在磁盘上返回nullptr,不会触发断言.
UObject* Loaded = MeshPath.TryLoad();

Checking State Without Loading

不加载资产的状态检查

cpp
if (MeshSoft.IsNull())    { /* no path set */ }
if (MeshSoft.IsValid())   { /* path set AND asset is loaded in memory */ }
if (MeshSoft.IsPending()) { /* path set, async load started, not complete */ }

UStaticMesh* MeshPtr = MeshSoft.Get(); // Returns nullptr if not loaded.

cpp
if (MeshSoft.IsNull())    { /* 未设置路径 */ }
if (MeshSoft.IsValid())   { /* 已设置路径且资产已加载到内存 */ }
if (MeshSoft.IsPending()) { /* 已设置路径,异步加载已启动但未完成 */ }

UStaticMesh* MeshPtr = MeshSoft.Get(); // 如果未加载返回nullptr.

Async Loading

异步加载

FStreamableManager

FStreamableManager

FStreamableManager
is declared in
Engine/StreamableManager.h
. Access it via
UAssetManager::GetStreamableManager()
.
cpp
FStreamableManager& SM = UAssetManager::GetStreamableManager();
FStreamableManager
Engine/StreamableManager.h
中声明。通过
UAssetManager::GetStreamableManager()
访问。
cpp
FStreamableManager& SM = UAssetManager::GetStreamableManager();

RequestAsyncLoad — Single Asset

RequestAsyncLoad — 单个资产

cpp
void AMyActor::LoadWeaponMeshAsync()
{
    FStreamableManager& SM = UAssetManager::GetStreamableManager();

    StreamableHandle = SM.RequestAsyncLoad(
        WeaponMeshSoft.ToSoftObjectPath(),
        FStreamableDelegate::CreateUObject(this, &AMyActor::OnWeaponMeshLoaded));
}

void AMyActor::OnWeaponMeshLoaded()
{
    if (StreamableHandle.IsValid() && StreamableHandle->HasLoadCompleted())
    {
        UStaticMesh* Mesh = WeaponMeshSoft.Get();
        if (Mesh)
        {
            MeshComponent->SetStaticMesh(Mesh);
        }
    }
}

// Member:
TSharedPtr<FStreamableHandle> StreamableHandle;
cpp
void AMyActor::LoadWeaponMeshAsync()
{
    FStreamableManager& SM = UAssetManager::GetStreamableManager();

    StreamableHandle = SM.RequestAsyncLoad(
        WeaponMeshSoft.ToSoftObjectPath(),
        FStreamableDelegate::CreateUObject(this, &AMyActor::OnWeaponMeshLoaded));
}

void AMyActor::OnWeaponMeshLoaded()
{
    if (StreamableHandle.IsValid() && StreamableHandle->HasLoadCompleted())
    {
        UStaticMesh* Mesh = WeaponMeshSoft.Get();
        if (Mesh)
        {
            MeshComponent->SetStaticMesh(Mesh);
        }
    }
}

// 成员变量:
TSharedPtr<FStreamableHandle> StreamableHandle;

RequestAsyncLoad — Multiple Assets

RequestAsyncLoad — 多个资产

cpp
// All paths fire one callback when every asset is loaded.
TArray<FSoftObjectPath> PathsToLoad = {
    IconSoft.ToSoftObjectPath(), MeshSoft.ToSoftObjectPath() };
StreamableHandle = SM.RequestAsyncLoad(
    PathsToLoad,
    FStreamableDelegate::CreateLambda([this]()
    {
        // Both guaranteed loaded; IconSoft.Get() and MeshSoft.Get() are valid.
    }));
cpp
// 所有路径加载完成后触发一次回调.
TArray<FSoftObjectPath> PathsToLoad = {
    IconSoft.ToSoftObjectPath(), MeshSoft.ToSoftObjectPath() };
StreamableHandle = SM.RequestAsyncLoad(
    PathsToLoad,
    FStreamableDelegate::CreateLambda([this]()
    {
        // 两个资产均已加载完成; IconSoft.Get()和MeshSoft.Get()有效.
    }));

FStreamableHandle State and Control

FStreamableHandle状态与控制

cpp
Handle->HasLoadCompleted();   // true when all assets finished.
Handle->IsLoadingInProgress();// true while still loading.
Handle->WasCanceled();        // true if CancelHandle() was called.
Handle->GetLoadProgress();    // 0.0 to 1.0.
Handle->WaitUntilComplete();  // blocks game thread — use only on loading screens.
Handle->ReleaseHandle();      // allow GC of loaded assets.
Priority: pass higher values to
RequestAsyncLoad
for urgent loads (default is 0). Use
AsyncLoadHighPriority
(100) from
StreamableManager.h
for gameplay-critical assets.
FStreamableManager
also provides
RequestSyncLoad()
for synchronous loading when the asset is needed immediately (e.g., during initialization). Prefer async for gameplay to avoid stalling the game thread.

cpp
Handle->HasLoadCompleted();   // 所有资产加载完成时返回true.
Handle->IsLoadingInProgress();// 加载进行中时返回true.
Handle->WasCanceled();        // 如果调用了CancelHandle()返回true.
Handle->GetLoadProgress();    // 返回0.0到1.0的加载进度.
Handle->WaitUntilComplete();  // 阻塞游戏线程——仅在加载界面使用.
Handle->ReleaseHandle();      // 允许已加载资产被GC回收.
优先级:为紧急加载向
RequestAsyncLoad
传递更高的值(默认值为0)。对于游戏玩法关键资产,使用
StreamableManager.h
中的
AsyncLoadHighPriority
(值为100)。
FStreamableManager
还提供
RequestSyncLoad()
用于同步加载,适用于需要立即获取资产的场景(例如初始化期间)。游戏玩法中优先使用异步加载以避免阻塞游戏线程。

Asset Manager

Asset Manager

Setup — DefaultGame.ini

设置 — DefaultGame.ini

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))
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))

Custom AssetManager Subclass

自定义AssetManager子类

Subclass
UAssetManager
, override
StartInitialLoading()
for startup logic, register in
DefaultEngine.ini
:
ini
[/Script/Engine.Engine]
AssetManagerClassName=/Script/MyGame.UMyAssetManager
继承
UAssetManager
,重写
StartInitialLoading()
以实现启动逻辑,并在
DefaultEngine.ini
中注册:
ini
[/Script/Engine.Engine]
AssetManagerClassName=/Script/MyGame.UMyAssetManager

Loading Primary Assets

加载主资产

cpp
UAssetManager& AM = UAssetManager::Get();

// List all registered IDs of a type.
TArray<FPrimaryAssetId> WeaponIds;
AM.GetPrimaryAssetIdList(FPrimaryAssetType(TEXT("WeaponDefinition")), WeaponIds);

// Load a single primary asset asynchronously.
FPrimaryAssetId WeaponId(TEXT("WeaponDefinition"), TEXT("DA_Sword"));
TSharedPtr<FStreamableHandle> Handle = AM.LoadPrimaryAsset(
    WeaponId,
    TArray<FName>{ TEXT("Game") },  // load "Game" bundle (world mesh, etc.)
    FStreamableDelegate::CreateLambda([WeaponId]()
    {
        UWeaponDefinition* Def = UAssetManager::Get()
            .GetPrimaryAssetObject<UWeaponDefinition>(WeaponId);
        // Use Def...
    }));

// Load all assets of a type.
TSharedPtr<FStreamableHandle> AllHandle = AM.LoadPrimaryAssetsWithType(
    FPrimaryAssetType(TEXT("WeaponDefinition")),
    TArray<FName>{ TEXT("UI") });   // e.g., load only icons.

// Unload when no longer needed.
AM.UnloadPrimaryAsset(WeaponId);
cpp
UAssetManager& AM = UAssetManager::Get();

// 获取某一类型的所有已注册ID.
TArray<FPrimaryAssetId> WeaponIds;
AM.GetPrimaryAssetIdList(FPrimaryAssetType(TEXT("WeaponDefinition")), WeaponIds);

// 异步加载单个主资产.
FPrimaryAssetId WeaponId(TEXT("WeaponDefinition"), TEXT("DA_Sword"));
TSharedPtr<FStreamableHandle> Handle = AM.LoadPrimaryAsset(
    WeaponId,
    TArray<FName>{ TEXT("Game") },  // 加载"Game"包(世界网格等)
    FStreamableDelegate::CreateLambda([WeaponId]()
    {
        UWeaponDefinition* Def = UAssetManager::Get()
            .GetPrimaryAssetObject<UWeaponDefinition>(WeaponId);
        // 使用Def...
    }));

// 加载某一类型的所有资产.
TSharedPtr<FStreamableHandle> AllHandle = AM.LoadPrimaryAssetsWithType(
    FPrimaryAssetType(TEXT("WeaponDefinition")),
    TArray<FName>{ TEXT("UI") });   // 例如,仅加载图标.

// 不再需要时卸载.
AM.UnloadPrimaryAsset(WeaponId);

Asset Bundles

资产包

Bundles group soft references for selective loading. Decorate UPROPERTY fields with
meta = (AssetBundles = "BundleName")
. The Asset Manager will load only the requested bundle's assets.
cpp
// "UI" bundle: loaded in menus for icon display.
UPROPERTY(EditDefaultsOnly, meta = (AssetBundles = "UI"))
TSoftObjectPtr<UTexture2D> Icon;

// "Game" bundle: loaded when entering gameplay.
UPROPERTY(EditDefaultsOnly, meta = (AssetBundles = "Game"))
TSoftObjectPtr<USkeletalMesh> WorldMesh;

// Transition from UI to Game bundle:
AM.ChangeBundleStateForPrimaryAssets(
    { WeaponId },
    { TEXT("Game") },   // AddBundles
    { TEXT("UI") });    // RemoveBundles

资产包将软引用分组以实现选择性加载。使用
meta = (AssetBundles = "BundleName")
修饰UPROPERTY字段。Asset Manager将仅加载请求包中的资产。
cpp
// "UI"包:在菜单中加载以显示图标.
UPROPERTY(EditDefaultsOnly, meta = (AssetBundles = "UI"))
TSoftObjectPtr<UTexture2D> Icon;

// "Game"包:进入游戏玩法时加载.
UPROPERTY(EditDefaultsOnly, meta = (AssetBundles = "Game"))
TSoftObjectPtr<USkeletalMesh> WorldMesh;

// 从UI包切换到Game包:
AM.ChangeBundleStateForPrimaryAssets(
    { WeaponId },
    { TEXT("Game") },   // 添加包
    { TEXT("UI") });    // 移除包

Asset Registry

资产注册表

IAssetRegistry
allows querying asset metadata without loading assets. Access it via
IAssetRegistry::GetChecked()
or
FAssetRegistryModule::GetRegistry()
.
cpp
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"

IAssetRegistry& AR = IAssetRegistry::GetChecked();

// Get all assets of a class in a path.
TArray<FAssetData> AssetDataList;
AR.GetAssetsByPath(FName(TEXT("/Game/Data/Weapons")), AssetDataList, /*bRecursive=*/true);

// Get all assets of a specific class.
AR.GetAssetsByClass(
    FTopLevelAssetPath(TEXT("/Script/MyGame"), TEXT("WeaponDefinition")),
    AssetDataList,
    /*bSearchSubClasses=*/true);

// FAssetData is lightweight — no asset load occurs.
for (const FAssetData& Data : AssetDataList)
{
    FString AssetName = Data.AssetName.ToString();
    FSoftObjectPath Path = Data.GetSoftObjectPath();

    // Read asset registry tags without loading.
    FString TagValue;
    Data.GetTagValue(FName(TEXT("WeaponType")), TagValue);
}

// Query by tag values.
TMultiMap<FName, FString> TagFilter;
TagFilter.Add(TEXT("WeaponType"), TEXT("Melee"));
AR.GetAssetsByTagValues(TagFilter, AssetDataList);
IAssetRegistry
允许在不加载资产的情况下查询资产元数据。通过
IAssetRegistry::GetChecked()
FAssetRegistryModule::GetRegistry()
访问。
cpp
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"

IAssetRegistry& AR = IAssetRegistry::GetChecked();

// 获取某一路径下某一类别的所有资产.
TArray<FAssetData> AssetDataList;
AR.GetAssetsByPath(FName(TEXT("/Game/Data/Weapons")), AssetDataList, /*bRecursive=*/true);

// 获取某一特定类别的所有资产.
AR.GetAssetsByClass(
    FTopLevelAssetPath(TEXT("/Script/MyGame"), TEXT("WeaponDefinition")),
    AssetDataList,
    /*bSearchSubClasses=*/true);

// FAssetData是轻量级的——不会加载资产.
for (const FAssetData& Data : AssetDataList)
{
    FString AssetName = Data.AssetName.ToString();
    FSoftObjectPath Path = Data.GetSoftObjectPath();

    // 无需加载即可读取资产注册表标签.
    FString TagValue;
    Data.GetTagValue(FName(TEXT("WeaponType")), TagValue);
}

// 按标签值查询.
TMultiMap<FName, FString> TagFilter;
TagFilter.Add(TEXT("WeaponType"), TEXT("Melee"));
AR.GetAssetsByTagValues(TagFilter, AssetDataList);

Making Properties Searchable

使属性可搜索

cpp
UPROPERTY(EditDefaultsOnly, AssetRegistrySearchable)
FName WeaponType;

cpp
UPROPERTY(EditDefaultsOnly, AssetRegistrySearchable)
FName WeaponType;

Common Mistakes and Anti-Patterns

常见错误与反模式

Hard Referencing Everything

所有内容都使用硬引用

cpp
// BAD: This UPROPERTY loads ALL 50 particle effects when this data asset loads.
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UParticleSystem> HitEffect;

// GOOD: Soft reference — only load when the gameplay effect actually triggers.
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UParticleSystem> HitEffect;
cpp
// 错误:此UPROPERTY会在加载该数据资产时加载所有50个粒子特效.
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UParticleSystem> HitEffect;

// 正确:软引用——仅在游戏玩法特效实际触发时加载.
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UParticleSystem> HitEffect;

Loading on the Game Thread

在游戏线程加载资产

cpp
// BAD: LoadSynchronous on a large skeletal mesh stalls the render thread.
USkeletalMesh* Mesh = MeshSoft.LoadSynchronous();

// GOOD: Async load, apply result in callback.
UAssetManager::GetStreamableManager().RequestAsyncLoad(
    MeshSoft.ToSoftObjectPath(),
    FStreamableDelegate::CreateUObject(this, &AMyActor::OnMeshLoaded));
cpp
// 错误:在游戏线程使用LoadSynchronous加载大型骨骼网格会阻塞渲染线程.
USkeletalMesh* Mesh = MeshSoft.LoadSynchronous();

// 正确:异步加载,在回调中应用结果.
UAssetManager::GetStreamableManager().RequestAsyncLoad(
    MeshSoft.ToSoftObjectPath(),
    FStreamableDelegate::CreateUObject(this, &AMyActor::OnMeshLoaded));

Forgetting to Keep the Handle Alive

忘记保持句柄存活

cpp
// BAD: Handle is a local — destroyed when function returns, assets may be unloaded.
void LoadStuff()
{
    TSharedPtr<FStreamableHandle> Handle = SM.RequestAsyncLoad(...);
} // Handle destroyed here!

// GOOD: Store handle as a member until assets are no longer needed.
TSharedPtr<FStreamableHandle> LoadHandle; // member variable
cpp
// 错误:句柄是局部变量——函数返回时被销毁,资产可能被卸载.
void LoadStuff()
{
    TSharedPtr<FStreamableHandle> Handle = SM.RequestAsyncLoad(...);
} // 此处句柄被销毁!

// 正确:将句柄存储为成员变量,直到资产不再需要.
TSharedPtr<FStreamableHandle> LoadHandle; // 成员变量

Not Registering Primary Asset Types

未注册主资产类型

If a
UPrimaryDataAsset
subclass is not listed under
PrimaryAssetTypesToScan
in
DefaultGame.ini
,
GetPrimaryAssetIdList
returns nothing and
LoadPrimaryAsset
silently fails.
如果
UPrimaryDataAsset
子类未在
DefaultGame.ini
PrimaryAssetTypesToScan
中列出,
GetPrimaryAssetIdList
将返回空,
LoadPrimaryAsset
会静默失败。

Circular Soft Reference Resolution

循环软引用解析

Resolving a soft reference that itself holds soft references that point back creates load cycles. Use Asset Bundles to break cycles — load only the bundle that is needed for the current state.
解析一个自身包含指向回环的软引用的资产会导致加载循环。使用资产包打破循环——仅加载当前状态所需的包。

DataTable Column Changes with Existing Data

修改DataTable列时保留现有数据

Adding a column to an existing DataTable struct invalidates serialized row data unless
bPreserveExistingValues
is set on the table or you re-import. Removing a column causes all existing rows to lose that data. Always back up DataTable assets before struct changes in production.

向现有DataTable结构体添加列会使序列化的行数据失效,除非在表上设置
bPreserveExistingValues
或重新导入。删除列会导致所有现有行丢失该列的数据。生产环境中修改结构体前请务必备份DataTable资产。

Edge Cases

边缘情况

  • Cooked vs uncooked paths: In uncooked builds, paths use
    /Game/
    . Cooked paths differ — do not hard-code paths; use
    FSoftObjectPath
    from UPROPERTY references.
  • Asset Manager and cook: Assets not reachable through Primary Asset scanning rules or hard references will be excluded from the cook. Use
    PrimaryAssetRules
    or explicit asset labels to ensure inclusion.
  • Hot reload:
    UDataAsset
    changes during PIE (Play In Editor) may not reflect until the asset is fully reloaded. Use
    PostLoad
    or
    PostEditChangeProperty
    for editor-time updates.
  • Memory budgets: Track loaded assets by type via
    UAssetManager::GetPrimaryAssetObjectList
    . Use
    ChangeBundleStateForPrimaryAssets
    to swap between UI and Game bundles as scenes change.
  • bStripFromClientBuilds
    : Set this on
    UDataTable
    instances that contain server-only data (e.g., loot tables with drop rates) to prevent distribution to clients.

  • 打包与未打包路径:在未打包构建中,路径使用
    /Game/
    。打包后的路径不同——不要硬编码路径;使用来自UPROPERTY引用的
    FSoftObjectPath
  • Asset Manager与打包:无法通过主资产扫描规则或硬引用访问的资产将被排除在打包之外。使用
    PrimaryAssetRules
    或显式资产标签确保资产被包含。
  • 热重载:PIE(编辑器内运行)期间修改
    UDataAsset
    可能不会立即生效,直到资产完全重新加载。使用
    PostLoad
    PostEditChangeProperty
    实现编辑器时更新。
  • 内存预算:通过
    UAssetManager::GetPrimaryAssetObjectList
    按类型跟踪已加载资产。使用
    ChangeBundleStateForPrimaryAssets
    在场景切换时在UI和Game包之间切换。
  • bStripFromClientBuilds
    :在包含服务器专属数据(例如带有掉落率的战利品表)的
    UDataTable
    实例上设置此属性,以避免分发给客户端。

Related Skills

相关技能

  • ue-cpp-foundations
    — UPROPERTY specifiers, USTRUCT, UObject lifecycle
  • ue-serialization-savegames
    — saving and loading soft object references across sessions
  • ue-module-build-system
    — adding
    AssetRegistry
    ,
    Engine
    module dependencies to Build.cs
  • ue-cpp-foundations
    — UPROPERTY说明符、USTRUCT、UObject生命周期
  • ue-serialization-savegames
    — 跨会话保存和加载软对象引用
  • ue-module-build-system
    — 在Build.cs中添加
    AssetRegistry
    Engine
    模块依赖