How to get a component from a ClassDefaultObject?

I have the UClass of an AActor.

What I need is some default data off of a UActorComponent which is on the AActor.

In a sane world, I’d be able to just get the components by calling UClass->GetDefaultObject()->GetComponents(ArrayToPutThemIn) since the ClassDefaultObject is supposed to just be an instance of the UClass. However, CDOs do not have any components, so all of the standard ways of accessing them fail.

Now, I know that data is on there somewhere. In theory, I could get at it by casting the UClass to a UBlueprintGeneratedClass and digging through the USimpleConstructionScript and UInheritableComponentHandler, but that’s a giant headache and it’s going to take ages digging through some pretty low-level engine to figure out how to do it. The actor construction code isn’t a big help, since it seems to rely on constructing the actor as it goes in order to make the component data available, which obviously isn’t something I can do in this case where I’m just trying to get the data without building an actual instance of the AActor.

Does anyone know of an easier way to do this?

4 Likes

As usual, within 15 minutes of finally getting frustrated enough to post here, I found the answer.

For others with the same problem:

  • Cast the actor class to a UBlueprintGeneratedClass

  • Use UBrintGeneratedClass->SimpleConstructionScript->GetAllNodes() to get an array of USCS_Nodes

  • Iterate through the array looking for the USCS_Node whose ComponentClass matches the component you’re looking for

  • Return cast USCS node’s Template into your component class and return it, data’s all there

8 Likes

EDIT: Nice to hear you got it working :slight_smile:

Here’s what we’re using, annotated with comments matching the above steps:

template
T* FindDefaultComponentByClass(const TSubclassOf InActorClass) const
{
	return (T*)FindDefaultComponentByClass(InActorClass, T::StaticClass());
}

UActorComponent* FindDefaultComponentByClass(const TSubclassOf InActorClass, const TSubclassOf InComponentClass) const
{
	// Cast the actor class to a UBlueprintGeneratedClass
	UBlueprintGeneratedClass* ActorBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(InActorClass);

	// Use UBrintGeneratedClass->SimpleConstructionScript->GetAllNodes() to get an array of USCS_Nodes
	const TArray<USCS_Node*>& ActorBlueprintNodes = ActorBlueprintGeneratedClass->SimpleConstructionScript->GetAllNodes();

	// Iterate through the array looking for the USCS_Node whose ComponentClass matches the component you're looking for
	for (USCS_Node* Node : ActorBlueprintNodes)
	{
		if (Node->ComponentClass == InComponentClass)
		{
			// Return cast USCS node's Template into your component class and return it, data's all there
			return Node->ComponentTemplate;
		}
	}

	return nullptr;
}
4 Likes

I’ve reworked that function you posted for my use case, @Nick.Pruehs.

This one works with subclasses and at runtime. It’s a standalone function made for use outside a class and returns all of the components instead of just the first. :slight_smile:

 template <class T>
    void FindDefaultComponentsByClass(const UClass* InActorClass, UClass* InComponentClass, TArray<T*>& outArray)
    {
    	// Cast the actor class to a UBlueprintGeneratedClass
    	const UBlueprintGeneratedClass* ActorBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(InActorClass);
    
    	// Use UBrintGeneratedClass->SimpleConstructionScript->GetAllNodes() to get an array of USCS_Nodes
    	const TArray<USCS_Node*>& ActorBlueprintNodes = ActorBlueprintGeneratedClass->SimpleConstructionScript->GetAllNodes();
    
    	// Iterate through the array looking for the USCS_Node whose ComponentClass matches the component you're looking for
    	for (USCS_Node* Node : ActorBlueprintNodes)
    	{
    		if (UClass::FindCommonBase(Node->ComponentClass, InComponentClass) == InComponentClass)
    		{
    			// Return cast USCS node's Template into your component class and return it, data's all there
    			outArray.Emplace(Cast<T>(Node->ComponentTemplate));
    		}
    	}
    }
3 Likes

The previously presented solution above has a flaw. Imagine you have an Actor base blueprint that has an ActorComponent attached to it and you have a child Actor blueprint derived from the first. If you then call ‘FindDefaultComponentByClass’ with the derived Actor class as parameter it will not find the desired component that was added to the parent class.

The solution presented below will find the desired component in any case:

UActorComponent* FindDefaultComponentByClass(const TSubclassOf<AActor> InActorClass,
                                             const TSubclassOf<UActorComponent> InComponentClass)
{
    if (!IsValid(InActorClass))
    {
        return nullptr;
    }

    // Check CDO.
    AActor* ActorCDO = InActorClass->GetDefaultObject<AActor>();
    UActorComponent* FoundComponent = ActorCDO->FindComponentByClass(InComponentClass);

    if (FoundComponent != nullptr)
    {
        return FoundComponent;
    }

    // Check blueprint nodes. Components added in blueprint editor only (and not in code) are not available from
    // CDO.
    UBlueprintGeneratedClass* RootBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(InActorClass);
    UClass* ActorClass = InActorClass;

    // Go down the inheritance tree to find nodes that were added to parent blueprints of our blueprint graph.
    do
    {
        UBlueprintGeneratedClass* ActorBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(ActorClass);
        if (!ActorBlueprintGeneratedClass)
        {
            return nullptr;
        }

        const TArray<USCS_Node*>& ActorBlueprintNodes =
            ActorBlueprintGeneratedClass->SimpleConstructionScript->GetAllNodes();

        for (USCS_Node* Node : ActorBlueprintNodes)
        {
            if (Node->ComponentClass->IsChildOf(InComponentClass))
            {
                return Node->GetActualComponentTemplate(RootBlueprintGeneratedClass);
            }
        }

        ActorClass = Cast<UClass>(ActorClass->GetSuperStruct());

    } while (ActorClass != AActor::StaticClass());

    return nullptr;
}
6 Likes

Thank you so much, it works!

Hi PhilippOkoampah, the SimpleConstructionScript doesn’t contain the root component of a class, this is a significant problem,

Calling GetRootNodes() also won’t help because SCS considers the first layer of AllNodes as roots (ain’t got a clue why)

It seems to me that the only member that stores at least some info about the complete list of nodes is the UBlueprintGeneratedClass::CustomPropertyListForPostConstruction, but it has only FProperties and UClasses, so if you have any ideas on another workaround, it will be very useful

Did you found solution?

Found solution on how to get all ActorComponents(both created in C++ and Blueprint) from another ActorComponent(non SceneComponent).


If you need to get ActorComponents from Actor itself replace GetOuter()->GetClass() on GetOwner()->GetClass()

code example

static TArray<FString> GetHitRegistratorsNames(UObject* OwnerObject)
{
        TArray<FString> Result = {};

        UBlueprintGeneratedClass* BlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(OwnerObject);
        if (!BlueprintGeneratedClass) return Result;
        
        TArray<UObject*> DefaultObjectSubobjects;
        BlueprintGeneratedClass->GetDefaultObjectSubobjects(DefaultObjectSubobjects);

        // Search for ActorComponents created from C++
        for (UObject* DefaultSubObject : DefaultObjectSubobjects)
        {
            if (DefaultSubObject->IsA(UCapsuleHitRegistrator::StaticClass()))
            {
                Result.Add(DefaultSubObject->GetName());
            }
        }
        
        // Search for ActorComponents created in Blueprint
        for (USCS_Node* Node : BlueprintGeneratedClass->SimpleConstructionScript->GetAllNodes())
        {
            if (Node->ComponentClass->IsChildOf(UCapsuleHitRegistrator::StaticClass()))
            {
                Result.Add(Node->GetVariableName().ToString());
            }
        }
        
        return Result;
}
2 Likes

Thanks a lot for this solution. Due to circumstances we had to work in 4.27 and this code worked out nicely for me.