Soft Object/Class References and the async-loading boundary¶
A soft reference answers "which asset could I load?" It does not answer "which object can I use right now?" Loading is the boundary between those two questions.
The one-minute version¶
- A hard object/class reference points directly at a loaded Unreal object or class. When an asset containing that property loads, its hard-referenced asset dependencies load too.
- A Soft Object Reference stores indirect asset identity, fundamentally an object path. It can name a mesh, sound, material, Data Asset, or other asset without forcing that target to load with its owner.
- A Soft Class Reference does the same for a class. For a Blueprint class,
it resolves to the generated
UClassyou can pass to Spawn Actor, Create Widget, or Get Class Defaults. - A soft reference has three useful states: unset, set-but-unloaded (pending), and resolved to a live object. "Set" does not mean "loaded."
- Async Load Asset / Async Load Class Asset start loading and later run
Completed. Put work that needs the result onCompleted, then validate the returned Object/Class because missing or uncooked content can still yieldNone. - Load Asset Blocking / Load Class Asset Blocking wait on the game thread. They are simple but can hitch badly; use async loading during interactive play whenever the asset is not already guaranteed resident.
- A soft reference does not keep the loaded object alive. Store the loaded result as a hard Blueprint variable, retain a streamable handle, or let Asset Manager own the load for as long as the feature needs it.
Hard references create load chains¶
Suppose a player Blueprint has hard defaults for every possible cosmetic:
BP_Player
-> hard reference to DA_AllSkins
-> hard references to 40 skeletal meshes
-> hard references to materials and textures
Loading BP_Player makes that dependency chain eligible to load even if the
current player uses one skin. The references are convenient and correct, but
the owning asset's memory footprint can grow far beyond what its graph shows.
A soft reference breaks the automatic load edge:
BP_Player
-> Soft Object Reference to SK_Skin_37
BP_Player loads
-> path is available
-> SK_Skin_37 is not required in memory yet
Soft does not make the mesh smaller or loading free. It moves the cost to a time your code chooses and requires the code to handle "not ready yet."
Use the Reference Viewer and Size Map to inspect real dependency chains before converting every reference on instinct. Hard references are appropriate for small assets that are always needed with their owner.
What a soft reference stores¶
FSoftObjectPath stores a top-level asset path and, when needed, an optional
subobject path. A typed soft pointer wraps that identity so the editor picker
can restrict selections:
Soft Object Reference (Texture2D)
-> /Game/UI/Icons/T_Potion.T_Potion
Soft Class Reference (BP_ProjectileBase)
-> generated class for /Game/Weapons/BP_RocketProjectile
In C++ these are commonly:
TSoftObjectPtr<UTexture2D>
TSoftClassPtr<AProjectileBase>
The path is serialized; the live pointer is only a weak cached resolution.
Epic's pointer documentation explicitly notes that TSoftObjectPtr does not
affect garbage collection and can move between pending and valid as an asset
loads and unloads.
Unset, pending, and valid¶
| State | Path/identity | Loaded object | Meaning |
|---|---|---|---|
| Null/unset | none | None |
No asset was selected. |
| Pending | present | None |
It may resolve after a load; it is not usable yet. |
| Valid/resolved | present | live object/class | It can be used while something keeps it resident. |
Blueprint's Is Valid Soft Object Reference means the soft reference is not null. It does not prove that the asset exists, was cooked, or is loaded. Regular Is Valid on an object produced by dereferencing tests the live object, so an unloaded but correctly assigned soft reference can have:
Is Valid Soft Object Reference = true
Converted live Object = None
That is not contradictory. The first check validates identity presence; the second checks current residency/usability.
Soft Object versus Soft Class¶
Choose by what the consumer needs after loading:
| Reference type | Loaded result | Typical use |
|---|---|---|
| Soft Object Reference | Asset object such as Texture, Static Mesh, Sound, Material, Data Asset | Assign to a component, UI brush, audio call, or read asset data. |
| Soft Class Reference | UClass constrained to a parent type |
Spawn an Actor, create a Widget, choose a Damage Type, or read class defaults. |
A Blueprint package contains both a Blueprint asset object and the generated class used by gameplay instances. Loading a Soft Object Reference to the Blueprint asset is not the same as loading a Soft Class Reference to its generated class. If the next node is Spawn Actor from Class, store a Soft Class Reference with an Actor parent.
The Blueprint async pattern¶
For a cosmetic mesh:
RequestedSkin (Soft Object Reference to Skeletal Mesh)
-> Is Valid Soft Object Reference
-> Branch
false: use explicit fallback / report missing selection
true: Async Load Asset(RequestedSkin)
Completed:
-> Is Valid(loaded Object)
-> Branch
true:
store LoadedSkin as a hard Skeletal Mesh variable
confirm request still equals the player's current selection
Set Skeletal Mesh(LoadedSkin)
false:
report full soft path
use explicit fallback
For a spawnable class:
ProjectileClassSoft (Soft Class Reference to BP_ProjectileBase)
-> Async Load Class Asset
Completed:
-> Is Valid Class(loaded Class)
-> Branch
true: Spawn Actor from Class(loaded Class)
-> validate returned Actor too
false: reject fire/configuration without spending ammo
The async node may expose ordinary execution flow in addition to Completed.
Do not put dependent work on the path that merely starts the request. The
Object/Class output is only meaningful in the completion path.
Revalidate the consumer and request¶
Time passes while loading. By completion:
- the widget or actor that requested the asset may be gone;
- the player may have selected a different item;
- a newer request may have completed first;
- the world may be traveling or shutting down.
Store a request ID or compare the completed soft reference with the current selection before applying the result. Keep ownership of UI state in a controller/HUD/view model whose lifetime matches the request, rather than in a temporary row widget.
Request generation = 12 for T_Potion
Async load completes
Current generation = 14 for T_Rifle
-> discard stale completion; do not replace rifle icon with potion icon
When loading fails (and what failure does)¶
The soft reference is unset¶
An unset path cannot load. Treat it as configuration/optional-content flow before starting the request. Async completion is not a substitute for checking whether the designer selected anything.
The path names missing or deleted content¶
A non-null soft reference can still be unresolvable. The load completes without a usable Object/Class, so validate the output. Log the full path and feature context; a generic "load failed" message is not enough to repair content.
Do not silently choose the first asset of a type. Use a documented fallback or stop the operation before mutating gameplay state.
The asset moved or was renamed¶
Moving assets through Unreal Editor creates redirectors so unloaded packages can still find the new location. Resave referencers and fix up redirectors before deleting old paths. Raw path strings held outside Unreal's serialized reference system need their own migration.
Redirectors are a transition aid, not a reason to expose package paths as permanent player-facing IDs.
The asset was not cooked¶
Serialized soft-reference properties participate in Unreal's dependency and cook systems, but the cooker cannot discover every path invented at runtime or downloaded from external data. An asset working in the editor is not proof it exists in the package.
For indirect catalogs, use an explicit cooking strategy:
- register Primary Asset types/directories with Asset Manager;
- apply Primary Asset rules or labels for chunks;
- keep references in scanned serialized assets/bundles;
- add a deliberate cook rule for exceptional dynamically addressed content;
- test the packaged build, not only Play In Editor.
The type is wrong¶
Typed soft properties restrict the picker, but paths imported from external data or converted broadly can still resolve unexpectedly or fail type checks. Validate the returned object/class and keep soft properties constrained to the narrowest parent type the feature accepts.
Blocking loads: simple, but not free¶
Load Asset Blocking and Load Class Asset Blocking resolve or load now
and return the object/class, or None when the target cannot be found. The
game thread waits while packages and dependencies load. Epic warns that a
synchronous load can stall for seconds.
Reasonable uses are narrow:
- startup/bootstrap before interactive play;
- a loading screen designed to cover the work;
- an editor utility;
- a tiny asset already expected to be resident, with measurement proving the path does not hitch.
Do not call blocking load from Tick, an animation update, a UMG binding, a frequent interaction trace, or the instant a player expects responsive fire. The node looking simple does not make disk/package work synchronous-safe.
Lifetime after async completion¶
The loading system keeps requested objects safe through completion. After the completion callback, a soft pointer alone does not prevent garbage collection. If the feature needs the asset afterward, retain it deliberately:
Async Load Asset completes
-> LoadedIcon variable (hard Texture2D Object Reference on owning widget/view model)
-> UI brush uses LoadedIcon
Feature closes
-> clear hard references / release managed load handle when appropriate
In C++, retain the loaded object with a reflected strong property, or retain a streamable handle for the desired lifetime. Asset Manager load functions can manage Primary Assets until you explicitly unload them.
An original C++ shape is:
void UIconPresenter::LoadIcon(TSoftObjectPtr<UTexture2D> RequestedIcon)
{
PendingIcon = RequestedIcon;
const FSoftObjectPath Path = RequestedIcon.ToSoftObjectPath();
LoadHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(
Path,
FStreamableDelegate::CreateWeakLambda(this, [this, RequestedIcon]()
{
if (PendingIcon == RequestedIcon)
{
LoadedIcon = RequestedIcon.Get(); // UPROPERTY strong reference
}
LoadHandle.Reset();
}));
}
The weak callback protects the destroyed presenter; the request comparison
rejects stale results; LoadedIcon keeps a successful result resident.
SaveGame paths and stable gameplay identity¶
A SaveGame property can serialize a soft asset reference/path, but that couples the save format to content location and availability. It can be appropriate when "this exact user-selected asset" is the durable meaning.
For item/ability progress, prefer a project-owned stable ID or Primary Asset ID and resolve current content after load:
SaveGame: ItemId = Potion_Small, Quantity = 3
Load: migrate ItemId -> find definition -> async-load optional icon/mesh
Do not save a loaded object pointer and expect it to remain meaningful after the process exits or a world is recreated. Definition identity and live object lifetime are different layers.
When Asset Manager is the next step¶
Manual soft references plus Async Load Asset are enough when a few authored properties choose assets. Asset Manager becomes useful when the project needs:
- discovery of a catalog without loading every asset;
- stable Primary Asset IDs;
- load/unload ownership across systems;
- named bundles such as
UI,Game, orActordependencies; - cooking and chunk rules for downloadable content;
- auditing disk and memory dependencies.
Asset Manager does not remove async thinking. It gives the project a central identity, discovery, cooking, and lifetime policy instead of many unrelated load calls.
Lookalikes - which reference do I want?¶
| Reference/tool | Loads target with owner? | Keeps target alive? | Use when |
|---|---|---|---|
| Hard Object Reference | yes | yes while owning strong reference lives | The asset is small and always needed with the owner. |
| Hard Class Reference | yes | yes | The class is always needed for spawn/default access. |
| Soft Object Reference | no; load explicitly | no | A specific asset should load on demand. |
| Soft Class Reference | no; load explicitly | no | A spawnable/creatable class should load on demand. |
| Weak Object Reference | no | no | Cache a live object that may be destroyed; it does not provide disk identity or on-demand loading. |
| Async Load Asset/Class | starts non-blocking load | through completion only unless retained | Interactive runtime loading with a completion boundary. |
| Load Asset/Class Blocking | immediately | returned result must be retained | Controlled startup/loading-screen/editor work where a measured stall is acceptable. |
| Primary Asset ID / Asset Manager | according to explicit request/rules | managed until unload/release | Discoverable catalogs, bundles, cooking, chunks, and centralized lifetime. |
Going deeper¶
- Blueprint variables and references - distinguish object, class, and asset-reference slots.
- Class references: Get Class / Class Is Child Of / Is Valid Class - inspect the loaded class after a Soft Class Reference resolves.
- Data Table row names, row handles, and stable IDs - keep SaveGame/domain identity separate from package paths.
- Data Tables vs Data Assets - choose whether many rows or individual authored assets own definitions.
- Official references: Referencing Assets, Asynchronous Asset Loading, Object Pointers, FSoftObjectPath, and Asset Management.
- Engine source (requires engine access; we do not reproduce it):
FSoftObjectPathinEngine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h;TSoftObjectPtr/TSoftClassPtrinEngine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPtr.h;UKismetSystemLibrary::LoadAsset,LoadAssetClass, and their blocking variants inEngine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp; andFStreamableManager::RequestAsyncLoadinEngine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h.