Create a new, blank, C++ project, with starter content.
Create a custom PrimaryDataAsset class:
UTESTPrimaryDataAsset.h -
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Engine/DataTable.h"
#include "TESTPrimaryDataAsset.generated.h"
/**
*
*/
UCLASS(Blueprintable, BlueprintType)
class PRIMARYASSETTEST_API UTESTPrimaryDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Asset", AssetRegistrySearchable)
FName ID;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Assets", AssetRegistrySearchable, meta = (AssetBundles = "Table", MetaClass = "UDataTable"))
TSoftObjectPtr<UDataTable> AssetTable;
UTESTPrimaryDataAsset(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/**
* -----------------------------------
* Methods
*
*/
/** PackageDataAssets use the root UTESTPrimaryDataAsset class name and their (globally) unique package id as their primary asset id */
inline static FName PrimaryAssetType() {
return UTESTPrimaryDataAsset::StaticClass()->GetFName();
}
/** PackageDataAssets use the root UCOREPackageDataAsset class name and their (globally) unique package id as their primary asset id */
inline static FPrimaryAssetId PrimaryAssetId(FName TargetPackageID) {
return FPrimaryAssetId(UTESTPrimaryDataAsset::PrimaryAssetType(), TargetPackageID);
}
/**
* -----------------------------------
* UPrimaryDataAsset overrides
*
*/
/** PackageDataAssets use the root UCOREPackageDataAsset class name and their (globally) unique package id as their primary asset id */
FPrimaryAssetId GetPrimaryAssetId() const override;
};
UTESTPrimaryDataAsset.cpp -
#include "TESTPrimaryDataAsset.h"
#include "PrimaryAssetTest.h"
/**
* -----------------------------------
* Actions
*
*/
UTESTPrimaryDataAsset::UTESTPrimaryDataAsset(const FObjectInitializer& ObjectInitializer)
:
UPrimaryDataAsset(ObjectInitializer),
ID(GetFName()),
AssetTable(nullptr)
{}
FPrimaryAssetId UTESTPrimaryDataAsset::GetPrimaryAssetId() const {
return UTESTPrimaryDataAsset::PrimaryAssetId((ID == "" ? GetFName() : ID));
}
Create a custom GameMode class:
PrimaryAssetTestGameModeBase.h -
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "TESTPrimaryDataAsset.h"
#include "PrimaryAssetTestGameModeBase.generated.h"
/**
*
*/
UCLASS()
class PRIMARYASSETTEST_API APrimaryAssetTestGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
uint8 bScanPerformed : 1;
uint8 bIsLoadingData : 1;
uint8 bIsDataLoaded : 1;
uint8 bHasDataLoadBeenTried : 1;
/**
* -----------------------------------
* Constructor
*
*/
APrimaryAssetTestGameModeBase();
/**
* -----------------------------------
* Methods
*
*/
void PostDebugMessage(const FString& DbgMsg, int32 Type = 0, FColor ScreenColor = FColor::Silver, float DisplaySecs = 1000.0f);
void PostDebugWarning(const FString& DbgMsg, float DisplaySecs = 1000.0f) {
PostDebugMessage(DbgMsg, 1, FColor::Orange, DisplaySecs);
}
void PostDebugErr(const FString& DbgMsg, float DisplaySecs = 1000.0f) {
PostDebugMessage(DbgMsg, 2, FColor::Red, DisplaySecs);
}
UFUNCTION()
void ScanForPrimaryAssets();
UFUNCTION()
void OnPrimaryDataLoaded(FPrimaryAssetId TgtAssetID);
/**
* -----------------------------------
* AGameModeBase overrides
*
*/
/** Performs tasks during the active gameplay loop; most activity is coordinated here */
void Tick(float DeltaSeconds) override;
};
PrimaryAssetTestGameModeBase.cpp
#include "PrimaryAssetTestGameModeBase.h"
#include "PrimaryAssetTest.h"
#include "Runtime/Engine/Classes/Engine/AssetManager.h"
//Constructor
APrimaryAssetTestGameModeBase::APrimaryAssetTestGameModeBase()
:
AGameModeBase(),
bScanPerformed(false),
bIsLoadingData(false),
bIsDataLoaded(false),
bHasDataLoadBeenTried(false)
{
bPauseable = false;
bAllowTickBeforeBeginPlay = true;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
PrimaryActorTick.SetTickFunctionEnable(true);
}
/**
* -----------------------------------
* Methods
*
*/
void APrimaryAssetTestGameModeBase::PostDebugMessage(const FString& DbgMsg, int32 Type, FColor ScreenColor, float DisplaySecs) {
static int32 MessageID = 0;
switch (Type) {
case 1:
{
UE_LOG(SWarn, Warning, TEXT("%s"), *DbgMsg);
ScreenColor = FColor::Orange;
}
break;
case 2:
{
UE_LOG(SWErr, Error, TEXT("%s"), *DbgMsg);
ScreenColor = FColor::Red;
}
break;
default:
//fallthrough
case 0:
{
UE_LOG(SWDebug, Log, TEXT("%s"), *DbgMsg);
}
break;
}
if (GEngine) {
GEngine->AddOnScreenDebugMessage(MessageID++, DisplaySecs, ScreenColor, *DbgMsg);
}
}
void APrimaryAssetTestGameModeBase::ScanForPrimaryAssets() {
TArray<FString> A_Paths = { "/Game/TestData" };
PostDebugMessage("Scanning for Primary Data Assets of TYPE: '" + UTESTPrimaryDataAsset::PrimaryAssetType().ToString() + "' in Path: '" + A_Paths[0] + "'", 0, FColor::Silver);
UAssetManager& AssetMgr = UAssetManager::Get();
const UAssetManagerSettings& AMSettings = AssetMgr.GetSettings();
AssetMgr.StartBulkScanning();
int32 Found = AssetMgr.ScanPathsForPrimaryAssets(UTESTPrimaryDataAsset::PrimaryAssetType(), A_Paths, UTESTPrimaryDataAsset::StaticClass(), true, false, true);
AssetMgr.StopBulkScanning();
bScanPerformed = true;
PostDebugMessage("Scan Found #" + FString::FromInt(Found) + " Primary Assets of TYPE: '" + UTESTPrimaryDataAsset::PrimaryAssetType().ToString() + "'", 0, FColor::Silver);
}
void APrimaryAssetTestGameModeBase::OnPrimaryDataLoaded(FPrimaryAssetId TgtAssetID) {
if (!bIsLoadingData) {
return;
}
bHasDataLoadBeenTried = true;
bIsLoadingData = false;
PostDebugMessage("Primary Data Asset LOADED || Target Core Package ID: '" + TgtAssetID.ToString() + "'", 0, FColor::Emerald);
UAssetManager& AssetMgr = UAssetManager::Get();
UTESTPrimaryDataAsset* TgtAsset = AssetMgr.GetPrimaryAssetObject<UTESTPrimaryDataAsset>(TgtAssetID);
if (!TgtAsset) {
PostDebugWarning("Primary Asset: '" + TgtAssetID.ToString() + "' Does not exist in memory!");
FAssetData AssetData;
if (AssetMgr.GetPrimaryAssetData(TgtAssetID, AssetData)) {
PostDebugMessage("Retrieved Asset Data!- LOADING", 0, FColor::Emerald);
UObject* GenObj = AssetData.ToSoftObjectPath().TryLoad();
if (GenObj) {
PostDebugMessage("Generic object EXISTS, CASTING", 0, FColor::Emerald);
TgtAsset = Cast<UTESTPrimaryDataAsset>(GenObj);
if (!TgtAsset) {
PostDebugErr("FAILED TO CAST loaded asset to UTESTPrimaryDataAsset object!");
UPrimaryDataAsset* GenAsset = Cast<UPrimaryDataAsset>(GenObj);
if (!GenAsset) {
PostDebugErr("FAILED TO CAST loaded asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!");
}
}
}
}
}
TArray<FName> AssetBundleState;
TArray<FSoftObjectPath> AssetBundlePaths;
TSharedPtr<FStreamableHandle> AssetHandle = AssetMgr.GetPrimaryAssetHandle(TgtAssetID, false, &AssetBundleState);
PostDebugMessage("Asset Bundle Request includes " + FString::FromInt(AssetBundleState.Num()) + " Asset Bundles", 0, FColor::Emerald);
for (int32 Idx = 0; Idx < AssetBundleState.Num(); Idx++) {
PostDebugMessage("AssetBundleID at IDX #" + FString::FromInt(Idx) + ": '" + AssetBundleState[Idx].ToString() + "'", 0, FColor::Emerald);
}
if (AssetHandle.IsValid()) {
AssetHandle->GetRequestedAssets(AssetBundlePaths);
PostDebugMessage("Asset Handle lists " + FString::FromInt(AssetBundlePaths.Num()) + " Paths", 0, FColor::Emerald);
for (int32 Idx = 0; Idx < AssetBundlePaths.Num(); Idx++) {
PostDebugMessage("Path at IDX #" + FString::FromInt(Idx) + ": '" + AssetBundlePaths[Idx].ToString() + "'", 0, FColor::Emerald);
}
if (!TgtAsset && AssetBundlePaths.IsValidIndex(0)) {
PostDebugMessage("Forcing Primary Asset load from Listed FSoftObjectPath: '" + AssetBundlePaths[0].ToString() + "'", 0, FColor::Emerald);
UObject* GenObj = AssetBundlePaths[0].TryLoad();
if (GenObj) {
PostDebugMessage("Successfully loaded object from Listed FSoftObjectPath: '" + AssetBundlePaths[0].ToString() + "'", 0, FColor::Emerald);
TgtAsset = Cast<UTESTPrimaryDataAsset>(GenObj);
if (!TgtAsset) {
PostDebugErr("FAILED TO CAST Listed FSoftObjectPath asset to UTESTPrimaryDataAsset object!");
UPrimaryDataAsset* GenAsset = Cast<UPrimaryDataAsset>(GenObj);
if (!GenAsset) {
PostDebugErr("FAILED TO CAST Listed FSoftObjectPath asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!");
}
}
}
else {
PostDebugWarning("FAILED TO LOAD Listed FSoftObjectPath asset: '" + AssetBundlePaths[0].ToString() + "'");
}
}
}
if (TgtAsset) {
bIsDataLoaded = true;
PostDebugMessage("Primary Asset Loaded Properly", 0, FColor::Emerald);
if (!TgtAsset->AssetTable.IsValid()) {
PostDebugWarning("PrimaryAsset Loaded with Error - Table is NOT IN memory");
}
else {
PostDebugMessage("PrimaryAsset Loaded Successfully - Table is VALID and in memory", 0, FColor::Green);
}
} else {
PostDebugErr("FAILED TO RETRIEVE LOADED DATA ASSET!!!");
}
}
/** Per-frame actions for this object */
void APrimaryAssetTestGameModeBase::Tick(float DeltaSeconds) {
if (!bHasDataLoadBeenTried) {
FString Message;
if (!bScanPerformed) {
PostDebugMessage("Triggering Scan for PrimaryAssets");
ScanForPrimaryAssets();
PostDebugMessage("Scan Complete");
} else if (!bIsDataLoaded) {
if (!bIsLoadingData) {
PostDebugMessage("Triggering PrimaryAsset Load");
bIsLoadingData = true;
UAssetManager& AssetMgr = UAssetManager::Get();
FPrimaryAssetType DataAssetType = UTESTPrimaryDataAsset::PrimaryAssetType();
PostDebugMessage("Retrieving PrimaryAssetId list from AssetManager for Assets of type: '" + DataAssetType.ToString() + "'");
TArray<FPrimaryAssetId> DataAssets;
AssetMgr.GetPrimaryAssetIdList(DataAssetType, DataAssets);
FPrimaryAssetId TgtAssetID = UTESTPrimaryDataAsset::PrimaryAssetId(TEXT("TESTDataAsset"));
PostDebugMessage("ID List Holds #" + FString::FromInt(DataAssets.Num()) + " Assets of type: '" + DataAssetType.ToString() + "'");
for (int32 Idx = 0; Idx < DataAssets.Num(); Idx++) {
PostDebugMessage("Listed DataAsset: '" + DataAssets[Idx].ToString() + "'");
}
PostDebugMessage("Triggering PrimaryAsset LOAD: '" + TgtAssetID.ToString() + "'");
TSharedPtr<FStreamableHandle> LoadingHandle = AssetMgr.LoadPrimaryAsset(
TgtAssetID,
{ TEXT("Table") },
FStreamableDelegate::CreateUFunction(this, TEXT("OnPrimaryDataLoaded"), TgtAssetID),
1
);
if (LoadingHandle.IsValid()) {
PostDebugMessage("Successfully Triggered PrimaryAsset Loading: " + TgtAssetID.ToString());
} else {
PostDebugErr("!!FAILED!! to begin PrimaryAsset Loading: " + TgtAssetID.ToString());
}
} else {
PostDebugMessage("PrimaryAsset is Loading...", 0, FColor::Yellow);
}
}
}
Super::Tick(DeltaSeconds);
}
Compile, then open the editor.
Register your custom data asset class with the editor. Include the proper directory.
Create a new instance of your custom PrimaryDataAsset in the proper directory.
Include an existing asset in your PrimaryDataAsset. Here Iâve used a custom Data Table.
PIE.
Why doesnât this work?
According to the Asset Manager, I successfully loaded my custom data asset class object BUT it WILL NOT cast to my custom class, nor the UPrimaryDataAsset class.
How is that possible?
RELEVANT UE LOG PORTION::
[2020.02.08-22.50.49:299][880]LogLoad: Game class is âPrimaryAssetTestGameModeBaseâ
[2020.02.08-22.50.49:301][880]LogWorld: Bringing World /Game/TestMaps/UEDPIE_0_TestMap01.TestMap01 up for play (max tick rate 0) at 2020.02.08-17.50.49
[2020.02.08-22.50.49:301][880]LogWorld: Bringing up level for play took: 0.001032
[2020.02.08-22.50.49:301][880]LogOnline: OSS: Creating online subsystem instance for: :Context_2
[2020.02.08-22.50.49:305][880]PIE: Play in editor start time for /Game/TestMaps/UEDPIE_0_TestMap01 -0.458
[2020.02.08-22.50.49:305][880]SWDebug: Triggering Scan for PrimaryAssets
[2020.02.08-22.50.49:305][880]SWDebug: Scanning for Primary Data Assets of TYPE: âTESTPrimaryDataAssetâ in Path: â/Game/TestDataâ
[2020.02.08-22.50.49:309][880]SWDebug: Scan Found #1 Primary Assets of TYPE: âTESTPrimaryDataAssetâ
[2020.02.08-22.50.49:309][880]SWDebug: Scan Complete
[2020.02.08-22.50.49:379][881]SWDebug: Triggering PrimaryAsset Load
[2020.02.08-22.50.49:379][881]SWDebug: Retrieving PrimaryAssetId list from AssetManager for Assets of type: âTESTPrimaryDataAssetâ
[2020.02.08-22.50.49:379][881]SWDebug: ID List Holds #1 Assets of type: âTESTPrimaryDataAssetâ
[2020.02.08-22.50.49:379][881]SWDebug: Listed DataAsset: âTESTPrimaryDataAsset:TESTDataAssetâ
[2020.02.08-22.50.49:379][881]SWDebug: Triggering PrimaryAsset LOAD: âTESTPrimaryDataAsset:TESTDataAssetâ
[2020.02.08-22.50.49:379][881]SWDebug: Successfully Triggered PrimaryAsset Loading: TESTPrimaryDataAsset:TESTDataAsset
[2020.02.08-22.50.49:380][881]SWDebug: Primary Data Asset LOADED || Target Core Package ID: âTESTPrimaryDataAsset:TESTDataAssetâ
[2020.02.08-22.50.49:380][881]SWarn: Warning: Primary Asset: âTESTPrimaryDataAsset:TESTDataAssetâ Does not exist in memory!
[2020.02.08-22.50.49:380][881]SWDebug: Retrieved Asset Data!- LOADING
[2020.02.08-22.50.49:380][881]SWDebug: Generic object EXISTS, CASTING
[2020.02.08-22.50.49:380][881]SWErr: Error: FAILED TO CAST loaded asset to UTESTPrimaryDataAsset object!
[2020.02.08-22.50.49:380][881]SWErr: Error: FAILED TO CAST loaded asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!
[2020.02.08-22.50.49:380][881]SWDebug: Asset Bundle Request includes 1 Asset Bundles
[2020.02.08-22.50.49:380][881]SWDebug: AssetBundleID at IDX #0: âTableâ
[2020.02.08-22.50.49:380][881]SWDebug: Asset Handle lists 2 Paths
[2020.02.08-22.50.49:380][881]SWDebug: Path at IDX #0: â/Game/TestData/TESTDataAsset.TESTDataAsset_Câ
[2020.02.08-22.50.49:380][881]SWDebug: Path at IDX #1: â/Game/TestData/TestTableData.TestTableDataâ
[2020.02.08-22.50.49:380][881]SWDebug: Forcing Primary Asset load from Listed FSoftObjectPath: â/Game/TestData/TESTDataAsset.TESTDataAsset_Câ
[2020.02.08-22.50.49:381][881]SWDebug: Successfully loaded object from Listed FSoftObjectPath: â/Game/TestData/TESTDataAsset.TESTDataAsset_Câ
[2020.02.08-22.50.49:381][881]SWErr: Error: FAILED TO CAST Listed FSoftObjectPath asset to UTESTPrimaryDataAsset object!
[2020.02.08-22.50.49:381][881]SWErr: Error: FAILED TO CAST Listed FSoftObjectPath asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!
[2020.02.08-22.50.49:381][881]SWErr: Error: FAILED TO RETRIEVE LOADED DATA ASSET!!!
[2020.02.08-22.52.16:886][613]LogContentBrowser: Native class hierarchy populated in 0.0198 seconds. Added 3019 classes and 722 folders.
[2020.02.08-22.52.16:896][613]LogContentBrowser: Native class hierarchy updated for âWidgetCarouselâ in 0.0006 seconds. Added 0 classes and 0 folders.
[2020.02.08-22.52.16:977][613]LogContentBrowser: Native class hierarchy updated for âAddContentDialogâ in 0.0006 seconds. Added 0 classes and 0 folders.
[2020.02.08-22.52.17:008][613]LogSlate: Took 0.000337 seconds to synchronously load lazily loaded font ââŚ/âŚ/âŚ/Engine/Content/Editor/Slate/Fonts/FontAwesome.ttfâ (139K)
[2020.02.08-22.52.30:201][160]LogAssetEditorSubsystem: Opening Asset editor for Blueprint /Game/TestData/TESTDataAsset.TESTDataAsset
[2020.02.08-22.52.30:564][160]LogContentBrowser: Native class hierarchy updated for âBlueprintGraphâ in 0.0012 seconds. Added 121 classes and 0 folders.
[2020.02.08-22.52.41:572][575]LogSlate: Window âTESTDataAssetâ being destroyed
[2020.02.08-22.52.41:583][575]LogWorld: UWorld::CleanupWorld for World_0, bSessionEnded=true, bCleanupResources=true
[2020.02.08-22.52.41:583][575]LogSlate: InvalidateAllWidgets triggered. All widgets were invalidated