Asset management
Every .uasset is a UObject in a package addressed by a virtual path. The single most
important decision is hard vs soft reference: it determines what loads into memory (and gets
cooked) with your class. Wrong choices here cause load hitches and bloated memory/package sizes.
When to use this skill
- Choosing how a class references a mesh, material, sound, Blueprint, or data asset.
- Load hitches, or memory/cook size ballooning due to too many hard references.
- Loading content on demand (level-streaming-friendly, plugin/DLC content).
- Enumerating or filtering assets at runtime without loading them.
- Setting up a
UPrimaryDataAssetpipeline withUAssetManagerfor large-content games.
Content paths
/Game/...→ projectContent/;/Engine/...→ engine content;/PluginName/...→ a plugin.- Full object reference string:
/Game/Weapons/SM_Rifle.SM_Rifle(Package.Object). - Never use OS filesystem paths in C++ asset references; always virtual paths.
Hard vs soft references (the key distinction)
| Kind | Type | When the target loads | Use for |
|---|---|---|---|
| Hard (object) | TObjectPtr<UTexture2D> (UPROPERTY) |
when the owner loads | small, always-needed assets |
| Soft (object) | TSoftObjectPtr<UTexture2D> |
only when you load it | heavy/optional/occasional assets |
| Soft (class) | TSoftClassPtr<AActor> |
only when you load it | classes you spawn conditionally |
UPROPERTY(EditAnywhere, Category=Mesh)
TObjectPtr<UStaticMesh> AlwaysMesh; // hard: loads with owner
UPROPERTY(EditAnywhere, Category=Audio)
TSoftObjectPtr<USoundBase> RareSfx; // soft: path-only until loaded
UPROPERTY(EditAnywhere, Category=Spawning)
TSoftClassPtr<AActor> BossClass; // soft class ref
A hard reference pulls the target and all its dependencies into memory and into the cook
whenever the referencing class loads. A class loaded everywhere (e.g. GameInstance, a base
ACharacter) holding hard refs to many heavy assets is a classic memory/cook bloat bug — switch
to soft refs there.
TSoftObjectPtr stores an FSoftObjectPath internally. IsNull() checks whether the path is
empty; IsValid() checks whether the object is currently resident in memory. These are
different: a non-null soft ptr can return false for IsValid() if the asset hasn't been loaded
yet.
// Do NOT confuse IsNull (empty path) with IsValid (loaded and in memory):
if (!RareSfx.IsNull() && !RareSfx.IsValid())
{
// Path is set but asset is not loaded — load it before use.
}
if (USoundBase* S = RareSfx.Get()) // returns nullptr if not in memory
{
// asset is resident
}
Loading soft references
Sync — use sparingly, blocks the game thread:
if (UStaticMesh* M = MeshSoftPtr.LoadSynchronous())
{
// M is now resident; do not call in hot paths
}
Async (preferred) via the streamable manager:
#include "Engine/AssetManager.h"
TSharedPtr<FStreamableHandle> Handle =
UAssetManager::GetStreamableManager().RequestAsyncLoad(
RareSfx.ToSoftObjectPath(),
FStreamableDelegate::CreateUObject(this, &AMyActor::OnSfxLoaded));
// Store Handle as a UPROPERTY-adjacent member; releasing it may allow GC.
Keep the returned TSharedPtr<FStreamableHandle> as long as you need the asset resident.
Releasing the handle removes the streamable manager's hard GC reference to the loaded asset.
Call Handle->ReleaseHandle() explicitly to unload, or let the TSharedPtr go out of scope.
See references/streamable-manager.md for the full async loading workflow, batch loading, combined handles, and progress tracking.
Referencing engine/known content in C++ constructors
ConstructorHelpers::FObjectFinder and FClassFinder resolve assets in the constructor only:
#include "UObject/ConstructorHelpers.h"
AMyActor::AMyActor()
{
static ConstructorHelpers::FObjectFinder<UStaticMesh>
MeshObj(TEXT("/Engine/BasicShapes/Cube.Cube"));
if (MeshObj.Succeeded())
Mesh->SetStaticMesh(MeshObj.Object);
}
Use this only for stable engine content or prototypes. For game content, prefer a soft/hard
UPROPERTY assigned in a Blueprint subclass — it's data-driven and avoids hard-coding paths.
FObjectFinder called outside a constructor fails/asserts at runtime.
Asset Registry — query without loading
The Asset Registry stores metadata gathered from .uasset file headers. Use it to discover
and filter assets without loading them; then load only the ones you need.
#include "AssetRegistry/AssetRegistryModule.h"
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
// Query all UStaticMesh assets (no load triggered):
TArray<FAssetData> Assets;
AR.GetAssetsByClass(UStaticMesh::StaticClass()->GetClassPathName(), Assets);
// Multi-criterion filter:
FARFilter Filter;
Filter.ClassPaths.Add(UStaticMesh::StaticClass()->GetClassPathName());
Filter.PackagePaths.Add(TEXT("/Game/Meshes"));
Filter.bRecursivePaths = true;
AR.GetAssets(Filter, Assets);
// FAssetData carries: PackageName, PackagePath, AssetName, TagsAndValues, AssetClassPath
// Convert to a loaded object only when needed:
UObject* Loaded = Assets[0].GetAsset(); // loads on call
Properties marked UPROPERTY(AssetRegistrySearchable) appear in TagsAndValues for filtering.
See references/asset-registry.md for filter construction,
searchable tags, async discovery callbacks, and commandlet use.
UAssetManager & primary assets (for scale)
UAssetManager wraps an FStreamableManager and adds discovery, typed loading, and asset
bundles. This is the right tool when you have a large content library, DLC, or chunked delivery.
Primary vs secondary assets:
- A primary asset is one the Asset Manager directly tracks — identified by
FPrimaryAssetId(FPrimaryAssetType+FName). Only levels (UWorld) are primary by default. - A secondary asset is everything else; the engine loads secondaries automatically when they are hard-referenced by a primary.
The minimal primary asset setup — inherit UPrimaryDataAsset:
// WeaponData.h
UCLASS(BlueprintType)
class MYGAME_API UWeaponData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category=Display,
meta=(AssetBundles="UI"))
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(EditDefaultsOnly, Category=Display,
meta=(AssetBundles="Game"))
TSoftObjectPtr<UStaticMesh> WeaponMesh;
};
Register the type in Project Settings → Asset Manager, then load by ID:
FPrimaryAssetId WeaponId(TEXT("WeaponData"), TEXT("Rifle_01"));
UAssetManager::Get().LoadPrimaryAsset(WeaponId, {TEXT("UI")},
FStreamableDelegate::CreateUObject(this, &AMyHUD::OnWeaponLoaded));
Asset bundles let you load only the assets each context needs (UI vs in-game vs offline).
See references/asset-manager-and-bundles.md for full
UPrimaryDataAsset, bundle registration, LoadPrimaryAssetsWithType, UnloadPrimaryAssets, and
ChangeBundleStateForPrimaryAssets.
Module dependencies
Add to your Build.cs as needed:
| Module | Needed for |
|---|---|
"Engine" |
UAssetManager, FStreamableManager, ConstructorHelpers |
"AssetRegistry" |
IAssetRegistry, FAssetData, FARFilter |
"CoreUObject" |
TSoftObjectPtr, TSoftClassPtr, FSoftObjectPath |
Gotchas
- Hard refs from widely-loaded classes → memory/cook bloat. Put heavy assets behind soft refs.
LoadSynchronousduring gameplay → frame hitch. Load async ahead of when you need it.- Hard-coded path strings in C++ → brittle. Expose
UPROPERTYrefs that designers can change. IsNullvsIsValidconfusion —IsNull()tests whether the path is empty;IsValid()tests whether the object is resident in memory. A non-null soft ptr may not be valid yet.- Dropping the streamable handle → loaded assets can be GC'd; keep the handle alive.
FObjectFinderoutside a constructor → fails/asserts; constructor-only.- Forgetting
AssetRegistrymodule dep →IAssetRegistrylink errors at compile time. - Asset Registry in cooked builds → the in-memory database reflects the cook; queries work but the registry is read-only and populated from the cook's serialized registry data.
GetAssetsByClasswith UE5 class path API — passGetClassPathName()(returnsFTopLevelAssetPath), notGetFName(), to match the 5.1+ API.
Version notes
TObjectPtr<T>is the UE5+ idiom for hard-reference UPROPERTYs; older code uses rawT*(still valid). Seeue-memory-and-gc.FSoftObjectPathreplacedFStringAssetReference(UE4 name, deprecated).GetClassPathName()returningFTopLevelAssetPathwas introduced in UE 5.1; legacy code may passGetFName()toGetAssetsByClass— this still compiles but uses the old overload.IsValid()was deprecated as a static (UAssetManager::IsValid()) in UE 5.3; callUAssetManager::IsInitialized()instead.
References & source material
Engine source (UE 5.8, under Engine/Source/):
Runtime/Engine/Classes/Engine/StreamableManager.h—FStreamableManager:731,FStreamableHandle:196,RequestAsyncLoad:756,LoadSynchronous:800,FStreamableDelegatealias:38.Runtime/Engine/Classes/Engine/AssetManager.h—UAssetManager:83,GetStreamableManager():105,LoadPrimaryAsset:333,UnloadPrimaryAssets:367,ChangeBundleStateForPrimaryAssets:390.Runtime/CoreUObject/Public/UObject/SoftObjectPtr.h—FSoftObjectPtr:44,TSoftObjectPtr:173,TSoftClassPtr:795,LoadSynchronous:82/547,IsValid:571/933,IsNull:592/954,ToSoftObjectPath:96/604.Runtime/CoreUObject/Public/UObject/PrimaryAssetId.h—FPrimaryAssetType:27,FPrimaryAssetId:133.Runtime/Engine/Classes/Engine/DataAsset.h—UDataAsset:17,UPrimaryDataAsset:47,GetPrimaryAssetId:53.Runtime/AssetRegistry/Public/AssetRegistry/IAssetRegistry.h—GetAssetsByClass:335,GetAssets:363,GetDependencies:517,GetReferencers:580.Runtime/AssetRegistry/Public/AssetRegistry/AssetRegistryModule.h—FAssetRegistryModule:26,Get():34.Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h—FObjectFinder:77,FClassFinder:157.
Official docs (UE 5.8):
- Asset Management — https://dev.epicgames.com/documentation/unreal-engine/asset-management-in-unreal-engine
- Async Asset Loading — https://dev.epicgames.com/documentation/unreal-engine/asynchronous-asset-loading-in-unreal-engine
- Referencing Assets — https://dev.epicgames.com/documentation/unreal-engine/referencing-assets-in-unreal-engine
- Asset Registry — https://dev.epicgames.com/documentation/unreal-engine/asset-registry-in-unreal-engine
- Data Assets — https://dev.epicgames.com/documentation/unreal-engine/data-assets-in-unreal-engine
Deep-dive references in this skill:
- references/streamable-manager.md — async loading workflow, batch loads, combined handles, handle lifecycle, priority.
- references/asset-registry.md — filter construction, searchable tags, async discovery, dependency/referencer queries.
- references/asset-manager-and-bundles.md — primary asset setup, bundle metadata, load/unload, bundle-state switching.