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.
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;
}
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.
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));
}
}
}
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;
}
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
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;
}