Download

Can't get SpawnActor to work with UClass*, only TSubclassof?

Hello! So I have until now been spawning my actors with a TSubclassof that I choose in blueprint. This has been working fine. However, I am now trying to add the posibility of changing which actor on the fly, the function I use for this FAssetData::GetClass() returns me with a UClass.

When I feed this into the SpawnActor function, nothing spawns. Any ideas? This is the current code:


void AEnemySpawner::SpawnEnemy()
{

	UClass* SpawnClass = EnemyToSpawn();
	if(SpawnClass != NULL)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("ShouldBeAbleToSpawn?"));

	Bot = GetWorld()->SpawnActor<AWSNPC>(SpawnClass, GetActorLocation(), GetActorRotation());
	if (Bot != nullptr)
	{
		GameInst->AddSpawnedCounter();
		Bot->PlayerSpotted = StartAggressive;
	}
	else
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("UnableAbleToSpawn"));
}
	UClass* SpawnClass;
	SpawnClass = SpawnableEnemies[ListIndex].GetClass();

	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, FString::FromInt(ListIndex));
	if (!SpawnClass)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("GetClassFailed"));

	return SpawnClass;

.h:

	UClass* EnemyToSpawn();

	class AWSNPC* Bot;

	TArray<FAssetData> SpawnableEnemies;

My debugmessages confirms that it’s at the point of actually spawning the “class AWSNPC* Bot;” where this breaks. Suggestions?

Couple things stand out to me:

  1. Does SpawnClass inherit from AWSNPC? The UClass passed to SpawnActor needs to be derived from the templated class, which in your case would be AWSNPC.
  2. Probably not (I’m guessing, though), because SpawnableEnemies is an array of FAssetData, not AActor, and I’m guessing FAssetData::GetClass() does not return an actor’s class. This is mostly speculation since I’ve never worked with FAssetData objects myself, so I might be wrong about this.

The variable SpawnClass is just a UClass*, but the class I am filling it with is a blueprint that’s a child of AWSNPC, yes.

This is my first time working with asynchronous loading and FAssetData myself, but as far as I can tell from the few examples i’ve found it should return the actor class, unless I’m misunderstanding:

UClass *
GetClass()
Returns the class UClass if it is loaded.

That’s from the API documentation, right? I found that part a little ambiguous. What about the rest of the info:

…if it is loaded. It is not possible to load the class if it is unloaded since we only have the short name.

Could it be that the actor class is not loaded yet and therefore the class returned by FAssetData is null? What made you decide to not just make SpawnableEnemies a TArray<TSubclassOf<AWSNPC>>?

I’ve used this to confirm that the asset is available and another debug message that triggers if the class returns NULL. Seems fine:

	if(!SpawnableEnemies[ListIndex].IsAssetLoaded())
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("AssetNotLoaded"));
	else
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("AssetIsLoaded"));

I couldn’t use TArray<TSubclassOf<AWSNPC>> as UObjectLibrary::LoadAssetsFromPath() which I use returns an FAssedData Array.

I’ve tried using GetObjects instead which seems to let me get whatever type I wan’t but I’m just having a whole other can of worms with that.

Okay, what happens if you use SpawnableEnemies[ListIndex].GetAsset()->GetClass()? Still no luck? There’s also FAssetData::AssetClass and FAssetData::AssetName. Are they what you would expect them to be?

Interesting, the AssetName is what I would expect, however, AssetClass seems to be returning just “blueprint” instead of the blueprint in question.

	UClass* SpawnClass;
	FString SpawnFName;
	FString SpawnClassName;
	int ListIndex;


	EnemyLibrary->GetAssetDataList(SpawnableEnemies);
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(SpawnableEnemies.Num()));

	ListIndex = FMath::RandRange(1, SpawnableEnemies.Num()) - 1;
	if(!SpawnableEnemies[ListIndex].IsAssetLoaded())
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("AssetNotLoaded"));
	else
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("AssetIsLoaded"));
	SpawnClass = SpawnableEnemies[ListIndex].GetAsset()->GetClass();

	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, FString::FromInt(ListIndex));

	SpawnFName = SpawnableEnemies[ListIndex].GetAsset()->GetFName().ToString();
	SpawnClassName = SpawnClass->GetFName().ToString();

	UE_LOG(LogTemp, Warning, TEXT("SpawnName: %s"), *FString(SpawnFName));
	UE_LOG(LogTemp, Warning, TEXT("ClassName: %s"), *FString(SpawnClassName));

	return SpawnClass;

2021-10-04 20_00_25-Output Log

Can I perhaps make use of the getasset being correct, somehow?

So if you rewrite EnemyToSpawn to return SpawnableEnemies[ListIndex].GetAsset()->GetClass() and use that class in SpawnActor, it should work now, right?

I wish, however, both SpawnableEnemies[ListIndex].GetAsset()->GetClass() and SpawnableEnemies[ListIndex].GetClass() return just “Blueprint” instead of the desired class.

However, I ran the PrintAssetData function which logged a lot more for me:

LogAssetData:     FAssetData for /Game/TwinStick/Gameplay/Enemies/BaseEnemyBP_Molten.BaseEnemyBP_Molten
LogAssetData:     =============================
LogAssetData:         PackageName: /Game/TwinStick/Gameplay/Enemies/BaseEnemyBP_Molten
LogAssetData:         PackagePath: /Game/TwinStick/Gameplay/Enemies
LogAssetData:         AssetName: BaseEnemyBP_Molten
LogAssetData:         AssetClass: Blueprint
LogAssetData:         TagsAndValues: 12
LogAssetData:             GeneratedClass : BlueprintGeneratedClass'/Game/TwinStick/Gameplay/Enemies/BaseEnemyBP_Molten.BaseEnemyBP_Molten_C'
LogAssetData:             ParentClass : Class'/Script/Winstick.WSNPC'
LogAssetData:             NativeParentClass : Class'/Script/Winstick.WSNPC'
LogAssetData:             ClassFlags : 12847124
LogAssetData:             BlueprintType : BPTYPE_Normal
LogAssetData:             IsDataOnly : False
LogAssetData:             FiBData : *Lots of unreadable symbols*
LogAssetData:             NumReplicatedProperties : 0
LogAssetData:             NativeComponents : 2
LogAssetData:             BlueprintComponents : 0
LogAssetData:             BlueprintPath : BaseEnemyBP_Molten
LogAssetData:             ImportedNamespaces : ()
LogAssetData:         ChunkIDs: 0
LogAssetData:         PackageFlags: 262144

I’m pretty sure I should be able to use PackageName.

I don’t have time for building test cases myself, but I did a quick Google search and found this.

Try this and see if it works:

const UBlueprint* BlueprintAsset = Cast<UBlueprint>(SpawnableEnemies[ListIndex].GetAsset());
if (BlueprintAsset)
{
     return BlueprintAsset->GeneratedClass;
}
return nullptr;

This should return the class that you can plug into SpawnActor.

I think I found the same example and already got it working, thank you for helping debug and figure it out! I’m so glad to finally get things moving along!

Just one thing, I noticed that the FDataAsset setup already gives me a generated BP:

I’m thinking I should able to use this directly instead generating one again?
But the tag system is confusing to me, I tried

FString GBlueprint;
	GBlueprint = SpawnableEnemies[ListIndex].GetTagValueRef<FString>("GeneratedClass");

With no luck.

I don’t have any insight to offer on the tag system, but at this point it might be worth asking yourself what you would gain from that with regards to your original goal.

If the code that both of us found works, then that’s like 3 short, convenient lines of code that do what you want. I’m assuming you’re not spawning enemies left and right on every tick, so any performance impact that comes from the cast (which really is the only impactful thing going on up there) is negligible.

On the other hand, if you get the tag above as a string or a name, you’re basically back to where you started with the filepath that got you the FAssetData in the first place. You’d have to use one of the StaticLoadClass and StaticLoadObject functions to load a UObject when you have a string like “UnrealClassName’/Game/Path/To/Asset.Asset_C’”.

I would prefer the solution with the cast, cross it off the list, and move on to the next item.

1 Like