Loading...
Loading...
Use when working with DataAsset, DataTable, soft reference, hard reference, TSoftObjectPtr, async loading, Asset Manager, StreamableManager, or game data structures in Unreal Engine. See references/asset-loading-patterns.md for async loading and StreamableManager patterns. See references/data-driven-design.md for data-driven gameplay architecture. For serialization, see ue-serialization-savegames. For C++ foundations, see ue-cpp-foundations.
npx skill4agent add quodsoler/unreal-engine-skills ue-data-assets-tables.agents/ue-project-context.mdUAssetManager| Concern | DataAsset | DataTable |
|---|---|---|
| Structure | C++ class with typed UPROPERTY fields | Row struct, all rows same shape |
| Designer workflow | Editor-authored instances, picker UI | Spreadsheet import (CSV/JSON) |
| Hierarchy / inheritance | Yes, via Blueprint subclasses | No |
| Asset Manager integration | Yes ( | Not directly |
| Bulk lookup by row name | No | Yes ( |
| Best for | Per-item config objects | Large flat tables (loot, dialogue, XP curves) |
UDataAssetEngine/DataAsset.hUCLASS(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;
};UMyItemDataUPrimaryDataAssetGetPrimaryAssetId()// 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;
};FTableRowBaseEngine/DataTable.hUSTRUCT(BlueprintType)// 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;
};FItemTableRowUPROPERTY(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);
});// 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"));// 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// 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();UStaticMesh* Mesh = MeshSoft.LoadSynchronous(); // Blocks until loaded.
// FSoftObjectPath::TryLoad — returns nullptr if not on disk, does not assert.
UObject* Loaded = MeshPath.TryLoad();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.FStreamableManagerEngine/StreamableManager.hUAssetManager::GetStreamableManager()FStreamableManager& SM = UAssetManager::GetStreamableManager();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;// 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.
}));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.RequestAsyncLoadAsyncLoadHighPriorityStreamableManager.hFStreamableManagerRequestSyncLoad()[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="WeaponDefinition",
AssetBaseClass=/Script/MyGame.WeaponDefinition,
bHasBlueprintClasses=False,
bIsEditorOnly=False,
Directories=((Path="/Game/Data/Weapons")),
Rules=(Priority=1,bApplyRecursively=True))UAssetManagerStartInitialLoading()DefaultEngine.ini[/Script/Engine.Engine]
AssetManagerClassName=/Script/MyGame.UMyAssetManagerUAssetManager& 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);meta = (AssetBundles = "BundleName")// "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") }); // RemoveBundlesIAssetRegistryIAssetRegistry::GetChecked()FAssetRegistryModule::GetRegistry()#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);UPROPERTY(EditDefaultsOnly, AssetRegistrySearchable)
FName WeaponType;// 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;// 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));// 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 variableUPrimaryDataAssetPrimaryAssetTypesToScanDefaultGame.iniGetPrimaryAssetIdListLoadPrimaryAssetbPreserveExistingValues/Game/FSoftObjectPathPrimaryAssetRulesUDataAssetPostLoadPostEditChangePropertyUAssetManager::GetPrimaryAssetObjectListChangeBundleStateForPrimaryAssetsbStripFromClientBuildsUDataTableue-cpp-foundationsue-serialization-savegamesue-module-build-systemAssetRegistryEngine