Get default object for blueprint class

Hey,

I am trying to get the default subobjects (in this case static mesh components) for a blueprint class which derived from a native class.

The problem is the default object only contains the components defined in the native class.
I can’t get the components which were defined later in the child blueprint class.

All of this is for an inventory system, that displays items by retrieving the mesh components from the class and constructing a new actor for this specific purpose.

this is the function that sets up the actor for the inventory:

void AInventoryMesh::SetupInventoryMesh(TSubclassOf<class AItem> BaseClass, UClass* SubClass, USceneComponent* TargetRoot)
{
	//if we have a subclass, let it represent the item and ignore the baseclass
	//that is because there are items like the "constructionkit" which contains
	//a specific building piece for example a fence or stairs.
	//and to know which part is inside this construction kit we use the subclass instead
	UClass* chosenClass = SubClass ? SubClass : BaseClass;	
	
	//get the default object
	//?? this seems to only contain the native defined components
	//and not the ones defined in the deriving blueprint
	AActor* defaultObj = chosenClass->GetDefaultObject<AActor>();
	
	if (defaultObj)
	{
		//we remember the first used component
		UStaticMeshComponent* newParentMesh = nullptr;
		UStaticMeshComponent* templateParentMesh = nullptr;
		TArray<UActorComponent*> subComps = defaultObj->GetComponentsByClass(UStaticMeshComponent::StaticClass());			

		for (auto comp : subComps)
		{
			UStaticMeshComponent* meshComp = Cast<UStaticMeshComponent>(comp);			
			//create and register a cloned mesh component
			UStaticMeshComponent* meshClone = ConstructObject<UStaticMeshComponent>(UStaticMeshComponent::StaticClass(), this);
			meshClone->RegisterComponentWithWorld(GetWorld());			

			//copy the properties
			meshClone->SetStaticMesh(meshComp->StaticMesh);
			for (int i = 0; i < meshComp->GetNumMaterials(); i++)
			{
				meshClone->SetMaterial(i, meshComp->GetMaterial(i));
			}
						
			//if we already had a first component...
			if (newParentMesh)
			{
				//...attach all following components to this first component
				meshClone->AttachTo(newParentMesh);
				//and use the offset from the first default component to the current default component
				//as the new relative position
				meshClone->SetRelativeLocationAndRotation(meshComp->GetComponentLocation() - templateParentMesh->GetComponentLocation(), meshComp->GetComponentRotation());
			}
			else
			{
				//...else we attach it to the rootcomponent
				//and remember the current components
				newParentMesh = meshClone;
				templateParentMesh = meshComp;
				meshClone->AttachTo(RootComponent, NAME_None, EAttachLocation::SnapToTarget);
			}

			meshClone->SetCollisionProfileName("NoCollision");
		}

		//then we attach this new actor, which represents an item inside of the inventory
		//to a previous defined uscenecomponent which is positioned at the slot this item
		//resides in
		AttachRootComponentTo(TargetRoot, NAME_None, EAttachLocation::SnapToTarget);		
		//then scale the actor to fit inside the visual slot of the inventory GUI
		FVector scale = FVector(UHelperFunctions::GetActorScaleToFitInRadius(this, 6.f));
		SetActorScale3D(scale);

		//move the actor so that it is centered in the slot
		FVector offset = UHelperFunctions::GetActorLocalOffsetToCenter(this);
		SetActorRelativeLocation(offset);
	}
}

Now I have a base class “AStructure” which defines the basic building parts.
It contains only 1 mesh component by default - and this is the one that I can get from the default object.
I have a structure “stairs” which consists of the basic mesh and 2 additional mesh components for the railings.
These 2 components were added afterwards in the derived blueprint from “AStructure”, but I can’t get those “extra” components to build up my inventory actor.

Maybe there is another way to get the missing components?

Thanks in advance,
Jerry

I’d also like to know if this is possible.

Same boat. Epic? Are you there?

Cool, there is still space for me on the boat! :slight_smile:

i felt quite alone looking for this feature until i found your post…

Waiting for the feature, i will suggest spawning the item from the class, then looking for values …

This just hit me, too. I need to pull data off of an object before I spawn it in order to be able to spawn it. That data is missing for blueprint classes since all of their components disappear. Not sure how I’m going to work around this one…

I’m experiencing this problem too

Hi guys, figured it out:

auto blueprint = UBlueprint::GetBlueprintFromClass(ActorClass);

		if (blueprint && blueprint->SimpleConstructionScript)
		{
			const TArray<USCS_Node*>& scsnodes = blueprint->SimpleConstructionScript->GetAllNodes();
			for (auto scsnode : scsnodes)
			{
				if (scsnode)
				{
					if (auto itemComponent = Cast<UYourComponent>(scsnode->ComponentTemplate))
					{
						//Do Stuff
						return;
					}
				}
			}
		}
2 Likes

And to get the subobjects from native classes:

if (AActor * defaultObject = GetMutableDefault(ActorClass))
	{
		TArray<UObject*> subobjects;
		defaultObject->GetDefaultSubobjects(subobjects);
		for (int32 i = 0; i < subobjects.Num(); i += 1)
		{
			if (auto itemComponent = Cast<UYourComponent>(subobjects[i]))
			{
				//do stuff
				return;
			}
		}
	}

This will not work in shipping builds.

Same issue. I’m at a loss for why this is happening… I can get the CDO for the blueprint class, and it has all of the correct values that were set in the blueprint class defaults… however any blueprint created components are mysteriously absent. Even more odd is that they will occasionally appear as valid pointers, but they will be pointing to garbage memory.

same here…

What makes it not work in shipping builds?

UBlueprints and blueprint nodes are editor only objects. Any build that does not include editor code (Shipping will not) will have no concept of these, which will cause a compile error when you attempt to build in that configuration. Blueprint generated classes can be used, however. (i.e MyBlueprint_C"), as these exist outside of the editor framework.

I see parts of UBlueprint have #if WITH_EDITOR, but not the entire object. It also doesn’t override IsEditorOnly (which by default returns false). The SimpleConstructionScript (which contains the nodes) isn’t wrapped with any #ifs, so appears to be valid in non-editor builds. The fact that you can spawn blueprint classes in shipping builds means the components/properties stored in the UBlueprint have to be available somewhere. It seems like either UBlueprint has to work in shipping builds, or the components and properties get moved out to another class in non-editor builds?

You’re welcome to try. In my experience, you must work with the generated class only.

If you do manage to successfully cook a shipping build with that code, I’d be interested to know :slight_smile:

Hello again!

Since a few people were doubtful about my previous answer working in shipped builds, the following is code we use in our game, unmodified, that definitely works in a Shipping build. The gist of it is: Blueprints have two lists for components we care about: UInheritableComponentHandler and USimpleConstructionScript.

The USimpleConstructionScript contains templates for components declared in the current blueprint, possibly overriding a native component.

Whereas the UInheritableComponentHandler contains templates overriding a component declared in a parent blueprint (always seems to point to a node in a USimpleConstructionScript). Hence the ordering of which component template to choose as the desired candidate in the following function.

There is another AnswerHub post with a similar answer here:

How to get a component from a ClassDefaultObject?

There is a pretty good possibility of making a function like this work for any provided subclass of UActorComponent, we just haven’t need one yet :slight_smile:


void UFastStaticLibrary::GetItemName(TSubclassOf<AActor> ActorClass, FText & Name)
{
	TArray<UObject*> candidates;

	//Blueprint Classes
	{
		UBlueprintGeneratedClass * currentClass = Cast<UBlueprintGeneratedClass>(ActorClass);
		
		while (currentClass)
		{
			//UInheritableComponentHandler
			if (UInheritableComponentHandler * handler = currentClass->InheritableComponentHandler) {
				TArray<UActorComponent*> templates;
				handler->GetAllTemplates(templates);
				for (auto componentTemplate : templates)
				{
					candidates.AddUnique(componentTemplate);
				}
			}

			//USimpleConstructionScript
			if (USimpleConstructionScript * scs = currentClass->SimpleConstructionScript) {
				for (auto node : scs->GetAllNodes())
				{
					candidates.AddUnique(node->ComponentTemplate);
				}
			}
			currentClass = Cast<UBlueprintGeneratedClass>(currentClass->GetSuperClass());
		}
	}

	//C++ Classes
	{
		TSubclassOf<UObject> currentClass = ActorClass;


		while (currentClass)
		{
			TArray<UObject*> currentSubobjects;
			currentClass->GetDefaultObjectSubobjects(currentSubobjects);
			for (auto object : currentSubobjects)
			{
				candidates.AddUnique(object);
			}
			currentClass = currentClass->GetSuperClass();
		}
	}
	
	//Loop through candidate objects until we find the first UInventoryItemComponent
	for (auto object : candidates)
	{
		if (auto itemComponent = Cast<UInventoryItemComponent>(object))
		{
			Name = itemComponent->Name;
			return;
		}
	}
}
4 Likes