Soft References and Async Loading in UE5: Stop Loading Meshes You Never Use

Created: 2026-07-20

Why merely referencing an asset pulls its data into memory. Covers the difference between hard and soft references, creating Soft Object and Soft Class References, loading on demand with Async Load Asset, why to avoid Load Asset Blocking, and a hands-on that shows a placeholder mesh before swapping in the real one.

You have a map with only one enemy type, yet opening the level puts every enemy mesh in memory. You're standing in the starting grasslands and the final boss's giant mesh is already loaded. Opening a single Blueprint drags in a chain of related assets and takes tens of seconds.

All of this comes from the fact that referencing an asset loads its actual data along with it. UE gives you soft references and async loading to control that. You change the reference so it only remembers a location, and load the data when you actually need it. This article covers the difference between hard and soft references, how to create soft references, how to load with Async Load Asset, which synchronous loads to avoid, and a hands-on where a placeholder mesh gets swapped for the real one.

Soft blue figure comparing a memory box holding only the meshes in use against a bulging box holding meshes that are never used

What You'll Learn

  • Hard references load together; soft references only remember the location
  • Creating Soft Object References and Soft Class References
  • Loading on demand with Async Load Asset
  • Why to avoid Load Asset Blocking
  • Where it pays off ( large meshes, cutscenes, bosses, DLC )
  • Hands-on: loading an enemy's mesh only when it's needed

Sponsored

Hard References vs. Soft References

First, what does "referencing" mean in UE? You set a variable's type to Static Mesh (object reference) and assign an asset to it. That's a hard reference. When something loads the referencing side, the referenced target is always loaded with it.

Comparison diagram showing hard references chaining to load everything, while a soft reference only remembers a location and loads nothing

The problem is that this chains. An enemy Blueprint hard-references a mesh, that mesh references a material, and the material references textures. Load the enemy Blueprint and the materials and textures come along for the ride. List 20 enemy types in a Data Table and you drag in all 20 even when you only intend to spawn one.

Soft references break that chain. What a soft reference holds isn't the asset itself but a string containing the asset's location (path). It just knows "the dragon mesh lives at /Game/Enemies/SM_Dragon," and the dragon's data never enters memory.

Hard referenceSoft reference
What it holdsThe asset's dataThe asset's location (path)
When loadedThe target loads with itThe target doesn't load
Using itReady to use directlyLoad it yourself first
Best forLight things you always useLarge things you may not use

Here's the key point. A soft reference isn't a mechanism for "not loading." It's a mechanism for deciding when to load. When you need the data, you load it yourself, as covered in the following sections.

Sponsored

Creating a Soft Reference

Creating one just means choosing a different reference kind when you pick the variable's type.

Diagram showing the variable type dropdown being switched from Object Reference to Soft Object Reference

Create a variable, pick the target asset type in the type dropdown (Skeletal Mesh, for example), and a second set of options appears on the right. That's where you pick the reference kind.

Reference kindMeaningExample
Object ReferenceHard reference (the data)Your usual mesh variable
Soft Object ReferenceSoft reference (the asset's location)Assets like meshes, textures, sounds
Class ReferenceHard reference (a class)A class variable you spawn from
Soft Class ReferenceSoft reference (a class's location)Loading a Blueprint class later

For assets themselves (meshes, textures, sounds, Niagara systems) loaded later, use Soft Object Reference. For Blueprint classes (the enemy or UI type you spawn) loaded later, use Soft Class Reference. They look nearly identical in the details panel, showing a slot where you pick an asset. The only difference is that assigning one doesn't load the data.

Note: Once you've assigned an asset to a soft reference variable, you can treat it much like a hard reference. The difference only shows up at the moment of use. You can't wire a soft reference straight into Set Skeletal Mesh Asset or similar nodes. You always have to load the data first.


Loading with Async Load Asset

The standard way to get data out of a soft reference is the Async Load Asset node. As "Async" implies, it loads in the background without stopping the game.

Flow diagram wiring a soft reference into Async Load Asset's Asset input, then casting the Object output from Completed and passing it to the node that uses it

The node's pins are as follows.

PinKindContents
AssetInputThe Soft Object Reference you want to load
CompletedOutput (exec)Fires the moment loading finishes
ObjectOutput (data)The loaded asset's data (Object type)

Here's the flow. Wire a soft reference into Async Load Asset's Asset pin and execution moves on immediately. When the background load finishes, Completed fires and Object holds the data. Since Object is a generic Object type, cast it to the type you want before passing it to Set Skeletal Mesh Asset or similar.

To load a class instead, use the twin node Async Load Class Asset. It takes a Soft Class Reference as input and gives you the loaded class from Completed. Use it when you want to load a class you'll spawn later.

Completed can take several frames, or hundreds of milliseconds for large assets. The game keeps running during that time, so decide what to show while loading (a placeholder mesh, a loading indicator, a translucent stand-in). In the hands-on section we'll build the placeholder-then-swap pattern.

Note: It's safe to call Async Load Asset on an asset that's already loaded. It won't reload, and returns the cached data via Completed immediately. You don't need to check "have I loaded this yet" yourself; just call it. To confirm whether the result is valid, run the returned Object through Is Valid.

Sponsored

Why Not Load Asset Blocking

There's another way to turn a soft reference into data: Load Asset Blocking. As the name says, it loads immediately, right there, and looks simpler at first glance. But in practice you avoid it almost everywhere.

Timeline comparison showing Load Asset Blocking stretching one frame into a hitch while Async spreads across multiple frames smoothly

Load Asset Blocking halts the game thread while loading. Nothing advances to the next frame until it's done. For a small asset that's instant, but for a large mesh or texture that single frame's processing time spikes and you get a visible hitch. The official documentation also recommends Async Load Asset where possible.

Async Load AssetLoad Asset Blocking
While loadingThe game keeps runningThe game stops
Large assetsSpread across multiple framesOne long frame that hitches
Getting the resultLater, via CompletedImmediately, in place
Where it fitsAlmost everywhereGenuinely instant small items, or behind a loading screen

Also watch out for synchronous loads you didn't intend. Even without Load Asset Blocking, converting a soft reference to a hard reference triggers a synchronous load at that instant. When you're hitting a load stall despite "using soft references," check whether such a conversion snuck in somewhere. Routing all loading through Async Load Asset is the safe approach.


Where It Pays Off

Soft references are useful, but making everything a soft reference backfires. The loading itself doesn't disappear, so an asset you always use ends up with a load wait every time you need it.

They pay off on assets where these conditions overlap.

ConditionExample
LargeHigh-resolution skeletal meshes, 4K textures, long audio
Not always usedBosses, enemies specific to one map, achievement unlocks
Predictable moment of useEntering an area, starting a conversation, transitioning into a boss fight
Two bars comparing hard references, where the boss mesh, cutscene and codex art all stay in memory from startup, against soft references, which load only what is needed once the boss fight starts

Concretely, these are the situations where it makes a difference.

  • Bosses and large enemies: don't load until the player enters the arena. The boss's mesh disappears from the early map's memory footprint
  • Cutscene-only assets: load right before the event starts and release when it's over
  • Collection and encyclopedia screens: instead of holding every item icon at once, load only what's on screen
  • DLC and additional content: keep it out of the base game's memory and load it only when the player enters that feature

Conversely, things always on screen, like the player character's default mesh, the common enemies, or core UI parts, are more comfortable as plain hard references with no load wait. The deciding question is "is it large, and is it not always used?"

You can also control loading volume wholesale from the level side. Combining this with Level Streaming and World Partition, which loads and unloads by area, works at a much larger granularity.

Sponsored

A Doorway into Asset Manager

Asset Manager is the system for managing soft references one tier more rigorously. It makes the engine aware of what assets exist, where, and how many, giving you a foundation for controlling loading and unloading centrally.

You reach for it once your asset types multiply and you want load state managed in one place. As an entry point, start by creating a Primary Data Asset and registering its type under Project Settings > Asset Manager. The concrete steps, and how to use soft references inside Data Assets, are covered in the article on data-driven design with Data Assets. This article stays focused on how soft references work and how to load them with Async Load Asset, and leaves the Asset Manager configuration to that one.

Get comfortable with the minimal form first: one variable as a soft reference, loaded with Async Load Asset. Asset Manager will still be there when that stops being enough.


Hands-On: Loading an Enemy Mesh On Demand

A field boss in an RPG, an area boss in a metroidvania, a floor boss in a roguelike. Large enemies that don't appear until you fight them are where soft references pay off most. Here we'll set up a boss whose high-resolution mesh doesn't load until the player enters the arena.

The Finished Result

Before entering the arena, there's just a simple placeholder mesh where the boss stands. When the player touches the trigger, the boss's real mesh loads in the background and swaps in a few frames later. Until you enter, the boss's huge mesh isn't in memory at all.

The placeholder mesh before entering the trigger, the swap to the real mesh once Async Load completes, and a before-and-after Size Map comparison

Setup

Create a new project from the Third Person template (Blueprint) and prepare the following.

1. The boss (BP_Boss)

Create it with Character (or Actor) as the parent class, and assign a lightweight placeholder mesh to the Skeletal Mesh Component up front (the Starter Content Mannequin or anything low-poly). That's what shows while loading.

VariableTypeDefaultSettings
BossMeshSkeletal Mesh ( Soft Object Reference )The boss's high-resolution meshInstance Editable on
HasLoadedBooleanfalsePrevents double loading

The reference kind on BossMesh is the whole point. After picking Skeletal Mesh in the type dropdown, set the reference kind to Soft Object Reference. Leave it as a hard reference and the real mesh loads the moment BP_Boss loads, which defeats the entire purpose.

2. The trigger (BP_BossTrigger)

Create it with Actor as the parent class and add one Box Collision. Its job is to tell the boss to load when the player overlaps it. You can communicate that via Event Dispatchers, or just have the trigger hold a reference to the boss and call it directly. For clarity, we'll have the trigger reference the BP_Boss in the level and call the function directly.

The Boss's Graph

Build the load-and-swap function in BP_Boss.

Node graph for LoadBossMesh, called by the trigger's On Component Begin Overlap, casting the Object from Async Load Asset's Completed pin and passing it to Set Skeletal Mesh Asset
BP_Boss

Function LoadBossMesh
  → Branch (Condition: HasLoaded)
      True  → (do nothing; prevents double loading)
      False →
        Set (HasLoaded = true)
        → Async Load Asset (Asset: BossMesh)
            Completed →
              Cast to Skeletal Mesh (Object: the Object output of Async Load Asset)
                → Is Valid (As Skeletal Mesh)
                    Is Valid → Set Skeletal Mesh Asset
                                  Target : Mesh
                                  New Mesh : As Skeletal Mesh
                    Is Not Valid → Print String ("Failed to load BossMesh")

The trigger just calls LoadBossMesh when the player overlaps it.

BP_BossTrigger
On Component Begin Overlap (Box)
  → Cast to (the player character)  // respond only to the player
      → the target BP_Boss (reference) → LoadBossMesh

Because Async Load Asset is in the middle, the frame that calls LoadBossMesh doesn't stall the game. Completed fires on the frame the load finishes, and only then does the mesh swap. The Is Valid check is there because Object can come back empty if the asset was deleted or moved, and passing that to Set Skeletal Mesh Asset gives you an Accessed None (see the debugging article).

Verifying

First, confirm it isn't in memory. Right-click BP_Boss and open its Size Map.

  • With BossMesh as a hard reference (Object Reference), the Size Map includes the boss's high-resolution mesh plus all its materials and textures, and the total size is large (80MB, for example)
  • Switch it to a Soft Object Reference and that mesh disappears from the Size Map, with BP_Boss itself dropping to a few hundred KB

That's your visible proof that referencing no longer loads the data.

Next, confirm it loads on demand. Launch with Standalone Game and approach the boss to enter the trigger.

Success means a simple placeholder mesh before you touch the trigger, and the real boss mesh a few frames after. The brief gap before the swap is itself evidence that the real mesh wasn't loaded until that moment.

If it isn't working, here's how to narrow it down.

  • The real mesh is there from the startBossMesh's reference kind isn't Soft Object Reference. As a hard reference it loads when the actor is placed
  • Entering the trigger doesn't swap anything → the trigger's On Component Begin Overlap isn't firing. Check Generate Overlap Events on the Box Collision and the player's collision settings
  • It swaps, but the game freezes briefly → you're using Load Asset Blocking, or a synchronous load is happening somewhere. Consolidate on Async Load Asset
  • You get Accessed None → you're wiring Object into Set Skeletal Mesh Asset without going through Is Valid, or you forgot to assign an asset to BossMesh
  • The second trigger entry is slow → the HasLoaded branch isn't working. Skip Async Load Asset once it's already loaded

Two things to take away.

  • The reference kind decides when loading happens: hard references load at placement or spawn time; soft references load the moment you call Async Load Asset. Taking "when do I load this" back into your own hands is the whole point of soft references
  • Always prepare what shows during loading: Async Load Asset takes several frames. Fill that gap with a placeholder mesh or a loading indicator or you'll have a moment where nothing is there. If you're going async, decide what the "not here yet" state looks like first

The same thinking applies directly to cutscene-only assets, collection icons, and DLC content. When you spot something large that you don't always use, take a look at its reference kind.

Sponsored

Bonus: Good to Know for Later

  • See the chain with Reference Viewer: right-click an asset and open Reference Viewer to see, as a graph, what it references and what references it. Paired with Size Map, it's how you track down "why is this mesh being loaded"
  • Release what you no longer need: an asset loaded with Async Load Asset stays in memory as long as something hard-references it. When the boss fight ends and the mesh is no longer needed, drop the reference so GC can reclaim it. Leaving everything loaded defeats the purpose of soft references
  • Loading an array? Use Async Load Assets: when you want to load several soft references at once, there's a plural Async Load Assets. It suits cases like an encyclopedia where you load "just what's on screen" as a batch. Passing them together is easier to manage than looping Async Load Asset one at a time
  • Measure memory with Memreport: run Memreport -full in the console to dump what's currently in memory and how much. Compare before and after switching to soft references and you can confirm numerically that the asset you targeted actually went away (for how to think about measuring, see the measurement article)
  • Hard references are also why startup is slow: most cases of "opening a Blueprint takes tens of seconds" come from that Blueprint hard-referencing heavy assets. Trace it with Reference Viewer and move anything that can load later to a soft reference, and the editor itself gets snappier

Summary

  • A hard reference loads the data the moment you reference it, and it chains outward from there
  • A soft reference only remembers the asset's location, with no data loaded. Soft Object Reference for assets, Soft Class Reference for classes
  • When you need the data, load it with Async Load Asset and cast the Object from Completed
  • Avoid Load Asset Blocking and synchronous loads. They stop the game and cause hitches
  • It pays off on assets that are large and not always used. Making everything soft backfires
  • Since you're going async, decide what shows during loading (a placeholder mesh) up front

What's the largest asset in the project you're building right now? Is it always on screen? If you find something large that you only use occasionally, start by changing its reference kind to Soft Object Reference. If your problem is instead a lot of light actors being created and destroyed, object pooling is the better fix.