Best way to access assets in C++

I have an Actor called PickupActor

Inside PickupActor, I add some components using CreateDefaultSubobject

e.g. m_meshComponent = CreateDefaultSubobject<UStaticMeshComponent>(FName("NonSkelMesh"));

This works fine.

I want to set the StaticMesh on this. Initially I was using

static ConstructorHelpers::FObjectFinder<UStaticMesh> DefaultSphereVisualAsset(TEXT("/Game/Dev/Test/BasicSphere.BasicSphere"));
m_meshComponent->SetStaticMesh(DefaultSphereVisualAsset.Object);

And this works. However several limitations with this.

  • I don’t think the cooker/packager would see it.
  • If that asset moves somewhere, the path will be outdated and it will fail.

I know I can manually overcome the cooker issue. But no good rename issue.
Second issue is that if the user defines a value in some external data.

Box: { 
  Mesh: "SomePath/WoodenBoxMesh",
}
LargeBox : { 
  Mesh: "SomePath/MetalBoxMesh",
}

I need to be able to select from 1 of multiple things, and that might need to happen after the CDO.

pickupItem = SpawnActor(loc, rot);
pickupItem.Initialize(InfoAboutWhatItemToMakeThis);

Which I can’t use ConstructorHelpers::FObjectFinder outside of constructor, and thus I can’t use anymore in ConstructorHelpers in this Initialize function.

I played around a little with LoadClass<UStaticMesh>(nullptr, TEXT("/Game/Test/BasicCube.BasicCube"));

But still similar issues.

Someone suggested I use UMasterDataAssetCpp, and implement a BP_UMasterDataAssetCpp and hook it into MyGameInstanceCpp and BP_MyGameInstancpp.

That way I can have TSoftObjectPtr defined in the UMasterDataAssetCpp but then set in the BP_ version, but still access it in the underlying cpp easily. This fixes the cooker AND the raw string issue! Yay! getting close!!

However, GameInstance doesn’t exist in the editor. It only exists after you hit Play/Simulate…
So if I want to drag/drop things into game, all the meshes are ‘empty’ until I hit play/simulate.

I’d like to be able to see the mesh inside the editor as well.

I started exploring GameSingletonClassName but having some issues with this as well.

Here are the things I want.

  • I want to be able to set the mesh in C++. From an array of meshes.
  • I want to be able to see the mesh in Editor before hitting Play.
  • I want to do the ‘right’ thing by using TSoftObjectPtr and other things, for later on when I want to preload in AssetManager or AsyncLoad when the time is right.
  • I don’t want to have a BP_ version for every class. This only shifts the problem.

I dunno what you wanna do exactly . but I guess DataAsset is the right choice for you.
you can make one master DataAsset or one DataAsset per item.
then in PickupActor keep a pointer to it. this way you only hard code the path of your DataAsset instead of individual assets. or you just make one BP_ version and set the DataAsset.

we did something like that before. didn’t want to make BP_ for every item since we had lots of item and their config and appearance were supposed to be adjusted dynamically by receiving some JSON from master server.

this may help :

enum class EItemName : uint8 
{
	ItemBox,
	ItemLargeBox,
	ItemBoombBox,
	//...
};

/*
*/
struct FItemInfo
{
	UPROPERTY(EditAnywhere)
	UStaticMesh* Mesh;
	UPROPERTY(EditAnywhere)
	USoundCue*  PickupSound;
	//...
};

/*
main data asset that keeps the properties of all items.
*/
class UMasterDataAsset : public UDataAsset
{
	UPROPERTY(EditAnywhere)
	TMap<EItemName, FItemInfo> ItemsInfo;
}

/*
*/
class APickupActor : AActor 
{
	APickupActor()
	{
		static ConstructorHelpers::FObjectFinder<UMasterDataAsset> DefaultAsset(TEXT("/Game/Dev/MyAsset"));
		DataAsset = DefaultAsset.Object;
		//...
	}

	UPROPERTY(EditAnywhere)
	UMasterDataAsset* DataAsset;
	
	UPROPERTY(EditAnywhere)
	EItemName ItemName;
	
	void Initialize(const FItemInfo& info)
	{
		MeshComponent->SetMesh(info.Mesh);
		//...
	}
#if WITH_EDITOR
	//this is called when any property is changed by editor
	void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override
	{
		check(DataAsset);
		Initialize(*DataAsset->ItemsInfo.Find(ItemName));
	}
#endif
}

Hot dang! That might be exactly what I was hoping for! I will give it a shot real quick and post back.

Thanks so much!

Also your situation where you described for yourself is the exact same situation I am in.

I got it working! A small tweak was needed though from what UPO33 posted.

static ConstructorHelpers::FObjectFinder DefaultMasterAsset( TEXT("/Game/MyGame/Test/BP_MasterDataAsset.BP_MasterDataAsset_C"));

I had to use UCLASS and not my custom type here, it would always say it couldn’t find it. Using UClass, UBlueprint, or UPrimaryDataAsset all could find it. So long as it was a default UE4 type.

The next thing missing was since its returning a class and not an actual object, we have to get a default object from the class. And this works in constructor and anywhere else.

DataAsset = Cast <UMasterDataAsset> ( DefaultMasterAsset.Object.GetDefaultObject() );

Since we can do this in the constructor, I can use it to set a default Item with meshes and everything.
Since my MasterDataAsset is just a TMap of assets, I can do whatever without worrying about all the different hardcoded paths.

The rest of the stuff worked perfectly!

I captured the full merged example of what was discovered/used here

blueprint derived classes are asset as well though they are a little different.
you can use DataAsset or CDO (class default object) depending on your needs.

both have pros and cons.

class pros:
you can use inheritance instead of duplicating assets. this way default properties of children are synced with the parent when you edit the parent in Editor.

asset pros:
properties can be edited in runtime, editor and PIE.

if you want to use derived class as asset I guess ConstructorHelpers::FClassFinder is the right choice.

sample code :

UCLASS(BlueprintType, Blueprintable)
class UMasterAsset : public UDataAsset
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere)
	float Speed;
};

//solution 0 using pointer to asset :
auto path = TEXT("/Game/Test/MasterAsset"); //path to the asset we craeted in edtior. (Right click -> Miscellaneous -> Data Asset)
static ConstructorHelpers::FObjectFinder<UMasterAsset> DefaultMasterAsset(path);
MasterAsset = DefaultMasterAsset.Object;


//solution 1 using pointer to CDO :

static auto path = TEXT("/Game/Test/MasterClass"); //path to the blueprint class we derived from UMasterAsset
static ConstructorHelpers::FClassFinder<UMasterAsset> DefaultMasterClass(path);
MasterAsset = DefaultMasterClass.Class.GetDefaultObject(); //we need our properties so we get the default object.
1 Like