Quick answer: Add the asset (or its directory) to Asset Manager → Primary Asset Types with Cook Rule = Always Cook. Without that, dynamically-referenced assets aren’t included in the cooked .pak and async loads return null.
Your inventory system loads item icons by soft path string at runtime — FSoftObjectPath(“/Game/Items/Icons/IconSword.IconSword”). It works in PIE. The shipping build returns null for every load. Player inventories render as missing-icon placeholders.
Why Cooking Excludes Dynamic References
The cooker walks the dependency graph from a set of roots (maps in the build, asset manager primary assets, anything in Always-Cook directories) and includes only what it discovers. A soft path constructed at runtime from a string has no compile-time presence in any referencing asset; the cooker doesn’t see it and doesn’t include the target.
In PIE, the editor loads from the uncooked content tree, so missing “cook” coverage is invisible. Only the packaged build exposes the problem.
Fix 1: Asset Manager Primary Asset Type
Define a category for the dynamically-loaded assets:
- Open Project Settings → Game → Asset Manager.
- Add a Primary Asset Type:
- Primary Asset Type:
Icon - Asset Base Class:
Texture2D(or your custom DataAsset) - Directories:
/Game/Items/Icons - Rules → Cook Rule:
Always Cook
- Primary Asset Type:
- Save and re-cook.
At runtime, request the asset through the Asset Manager:
auto& AM = UAssetManager::Get();
FPrimaryAssetId Id("Icon", "IconSword");
AM.LoadPrimaryAsset(Id, {}, FStreamableDelegate::CreateUObject(this, &ThisClass::OnLoaded));
The Asset Manager handles streaming, refcounting, and unload — better than raw FStreamableManager for content libraries.
Fix 2: Always Cook Directory
Quicker if you don’t want the full Asset Manager treatment:
// DefaultGame.ini
[/Script/UnrealEd.ProjectPackagingSettings]
+DirectoriesToAlwaysCook=(Path="/Game/Items/Icons")
The directory is included unconditionally. Loads still happen through your existing soft-path code path; nothing else changes. The downside is no streaming intelligence — assets are in the .pak whether anyone references them or not.
Fix 3: Soft Object Reference UPROPERTY
If you have a finite set of dynamic assets, declare them as soft references on a config or data asset:
UCLASS(Blueprintable)
class UIconLibrary : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
TMap<FName, TSoftObjectPtr<UTexture2D>> Icons;
};
Populate in the editor by dragging icons into the map. The soft references are visible to the cooker (via the DataAsset reference chain) and get included. Load at runtime by name:
auto IconRef = Library->Icons.FindRef("Sword");
StreamableManager.RequestAsyncLoad(IconRef.ToSoftObjectPath(), Callback);
Diagnosing in a Build
Run with -log to capture the Output Log. Look for “LogStreaming: Async loading from a string path” followed by “Asset doesn’t exist”. The path is your culprit. Search the cooked content folder (Saved/Cooked/PLATFORM/Project/Content/...) for a .uasset matching the name; if it’s missing, the cooker skipped it.
For a definitive list of what got cooked:
# Linux/macOS terminal:
find Saved/Cooked -name "*.uasset" | grep -i Icon
Verifying
After applying one of the fixes, rebuild the package. Confirm via find or the .pak inspection that your assets are present. Run on target and observe the icons load. Output Log should show successful streaming with no “couldn’t find” warnings.
“PIE finds anything. Shipping finds what you cooked. Tell the cooker what to cook.”
Asset Manager looks heavy at first but pays off the moment you have more than a few dynamic asset types.