Hard versus soft references, how hard ones get created without you noticing, what they cost, and the tools for cutting coupling.
Loads its target into memory whenever the referencing object loads. No loading code, no choice in the matter.
Just a path. Costs nothing until you explicitly resolve it.
UPROPERTY.UObject* — but it must be a UPROPERTY to be visible to the GC.ConstructorHelpers::FObjectFinder / FClassFinder (Chapter 6) — the hardcoded path loads the asset when the CDO is constructed.UPROPERTY isn't "a hard reference done safely" — it's a dangling-pointer bug waiting to happen, because GC can collect the target out from under it.
Actor.TObjectPtr is the modern idiom for hard UObject references. At runtime it behaves like a raw pointer and is pointer-sized in a cooked build, so there's no runtime cost.
The difference is editor-only: access-tracking hooks and clearer intent. Purely a cooked-versus-editor distinction.
Hard references load transitively and eagerly. If a character hard-references a weapon that hard-references a mesh, a sound, a VFX system and an ability set, then loading the character drags all of it into memory — whether or not you use it that frame.
On an interconnected encounter, that reference graph balloons fast. Circular hard dependencies are especially dangerous.
| Tool | What it buys you |
|---|---|
| Interfaces | Depend on a capability rather than a concrete class (Chapter 2) |
| Base-class references | Reference Actor or a shared base instead of a concrete type |
| Soft references | TSoftObjectPtr / TSoftClassPtr plus async loading — the path costs nothing until resolved |
| Event Dispatchers | Invert the dependency direction, so the referencer doesn't hold the referenced (Chapter 13) |
| Data Tables / Data Assets | Data-driven config decoupling (Chapter 8) |
| Reference Viewer | The editor tool for auditing what your core Blueprints actually pull in |
The soft-reference machinery here is the same machinery that underlies Asset Bundles — see Chapter 9.