[C++] How do I retrieve a Blueprint asset's script class so I can call member functions on it?

I am writing an editor utility tool. I scour the asset registry for blueprints with a certain ParentClass tag, and am receiving assets which when loaded are of type UBlueprint.

What I’d like to do with these blueprints is to then call a function on them (which returns a boolean determining whether or not to do an action involving one of their properties), but I’m having the hardest time trying to get a reference to the class instance itself.

The native class of my blueprints extends from UObject. The function and property I want to access is defined in the native class, and the blueprint just overrides the function (this is why I am extending from UObject and not UDataAsset). I wonder if I need to spawn the object before I can call functions / access properties on it? They are not static functions/properties.

	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<
		FAssetRegistryModule>("AssetRegistry");
	TArray<FAssetData> AssetData;

	// Value taken from Blueprint.cpp GetAssetRegistryTags()
	const FString NativeParentClass = FString::Printf(
		TEXT("%s'%s'"), *UET_Descriptor::StaticClass()->GetClass()->GetName(),
		*UET_ToolDescriptor::StaticClass()->GetPathName());

	TMultiMap<FName, FString> TagsValues;
	TagsValues.Add(FName("NativeParentClass"), NativeParentClass);
	AssetRegistryModule.Get().GetAssetsByTagValues(TagsValues, AssetData);

	for (auto Asset : AssetData)
	{
        // This is valid
		UBlueprint* BlueprintAsset = Cast<UBlueprint>(Asset.GetAsset());

// All attempts to cast below classes to UET_Descriptor* with Cast<UET_Descriptor>() fail.
			UClass* ParentClass = GetParentNativeClass(BlueprintAsset->GeneratedClass);
			UClass* BlueprintClass = BlueprintAsset->GetBlueprintClass();

			FString ObjPath = Asset.ObjectPath.ToString().Append("_C");
			UClass* Class = StaticLoadClass(UObject::StaticClass(), NULL, *ObjPath, NULL, LOAD_None, NULL);

I’m absolutely certain I’m just not understanding some aspect of what I’m doing here, doing something wrong, or am missing a step. Is anyone able to help?

One thing that gives me pause for thought is that for these assets to appear in a BP asset selector, they must be of type TSubclassOf<UET_Descriptor> instead of UET_Descriptor*, so I wonder if my cast method is wrong.

You can call a function without the need to spawn the actor. Just access the default object version of it.
AMyClass* tempItem = MyClass->GetDefaultObject();
tempItem->MyFunctionToCall();

(ofc AMyclass would be the actor that comes from the class and myclass is your actor class)

I use this to call functions on my inventory items when I just keep a reference to their class.

1 Like

3dRavens answer is the most correct as it’s using the proper class type.

You can also call functions without knowing the class type though which can sometimes be handy.

UObject contains a method called CallFunctionByNameWithArguments:

UBlueprintGeneratedClass* bpClass=Cast<UBlueprintGeneratedClass>(BlueprintAsset->GeneratedClass);
1 Like

Thank you, that is good to know. I cannot cast from the blueprint class’s DefaultObject to my class type, though, as the DefaultObject is a BlueprintGeneratedClass type. I’m sitll unclear on how to get from UBlueprint to a UET_Descriptor (which is a UObject, not AActor, if that makes a difference).

You can do something like this:

for (auto Asset : AssetData) {
    Asset.GetAsset()->CallFunctionByNameWithArguments(...);
}

Just make sure the function name is unique for your blueprints.
You may also need to mark those functions as “Editor Callable”.

1 Like

Thanks, I decided not to use CallFunctionByNameWithArguments() as it felt like bypassing my issue instead of solving it, but I eventually figured it out.

I’ve ended up spawning the objects and storing them on the calling object, which is preferential for me as I need to access their methods/properties frequently. The missing piece of the puzzle was having to get the class name by appending "_C" to the blueprint name, and then spawning the object. Previously I was trying to work with the class like it was an object itself.

FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<
	FAssetRegistryModule>("AssetRegistry");
TArray<FAssetData> NewAssetData;

// Value taken from Blueprint.cpp GetAssetRegistryTags()
const FString NativeParentClass = FString::Printf(
	TEXT("%s'%s'"), *UET_Descriptor::StaticClass()->GetClass()->GetName(),
	*UET_Descriptor::StaticClass()->GetPathName());

TMultiMap<FName, FString> TagsValues;
TagsValues.Add(FName("NativeParentClass"), NativeParentClass);
AssetRegistryModule.Get().GetAssetsByTagValues(TagsValues, NewAssetData);


for (auto Asset : NewAssetData)
{
	if (DescriptorAssets.Contains(Asset))
	{
		continue;
	}

	FString ObjPath = Asset.ObjectPath.ToString().Append("_C");
	UClass* AssetClass = StaticLoadClass(UObject::StaticClass(), NULL, *ObjPath, NULL, LOAD_EditorOnly, NULL);

	if (UET_Descriptor* NewDescriptor = NewObject<UET_Descriptor>(this, AssetClass, NAME_None, RF_Transient))
	{
		Descriptors.Add(NewDescriptor);
		DescriptorAssets.Add(Asset);
	}
}
1 Like