Instance Blueprint like Archetype from C++

Hi,

I am trying to use a data only blueprint to instanciate a component. First I added new code via the wizard, extending USkeletalMeshCompnent. Then I created a blueprint extending MyComponent class. Now I want to add this blueprint component to my character from code (I know this can be done inside the blueprint editor, but this a learning test). Taking a look at how the DefaultPawnClass is set I did this in the constructor of my character:

static ConstructorHelpers::FObjectFinder ComponentClass(TEXT("Class'/Game/Tests/BP_MyComponent.BP_MyComponent_C'"));

TSubobjectPtr testComponent = PCIP.CreateDefaultSubobject(this, TEXT("MyComponent"));
testComponent->AttachTo(RootComponent);
Components.Add(testComponent);

However for CreateDefaultSubobject T is expected to be a class, not what FObjectFinder returns. I also tried FClassFinder but had no luck with that either. Same for replacing FObjectFinder with FObjectFinder.

I have not yet gotten used to all the templated types, so I don’t even know what types are compatible so I cannot even get the code to compile.

greetings

Hi -

The templated type parameter that you pass in to CreateDefaultSubobject needs to be the type you’re expecting it to return. In this case:

TSubobjectPtr testComponent = PCIP.CreateDefaultSubobject(this, TEXT("MyComponent"));

Should give you what you want. Hope that helps!

With this I can instance a component, yes, but I already know how to do that. However I explicitly want to instance a blueprint that is extended from the custom MyComponent class in the way it was possible to create objects with archetypes in UDK.

Currently that is hardcoded for testing, I plan to make a list for the editor. (but I digress, that is not the point here)

Oh sorry, I misunderstood.

In order to use that class, you’ll have to basically mimic what CreateDefaultSubobject does internally for your component type. There’s a lot in that function which handles potential overrides that you shouldn’t have to worry about. The salient bits are:

  1. Get the default object for your component type
  2. Construct a new instance of the object on the instance of your actor
  3. Add the result to the default subobjects array IF the current instance is a CDO
  4. Add the component to the Components array

That should let you use any class that you have a UClass reference to as a component type.

I am trying to mimic the function, however whenever I try to get the default object like this:

ConstructorHelpers::FObjectFinder MyDefaultObject(TEXT("Class'/Game/Tests/BP_MyComponent.BP_MyComponent_C'"));

I get an error

[0008.80][ 5]Error: CDO Constructor:
Failed to find
Class’/Game/Tests/BP_MyComponent.BP_MyComponent_C’

It also seems to be impossible to mimic what CreateDefaultSubobject does, because it uses a ComponentOverrides Array which is private.

For the CreateDefaultSubobject portion, the ComponentOverrides is the part you can leave out. That’s the reason that the function call is templated. I haven’t compiled this, so there may be a compile error, but this is the stripped down version of the function you need:
(Assume MyCompClass is the UClass* you got from the finder, and Outer is your object)

UObject* Template = MyCompClass->GetDefaultObject(); // force the CDO to be created if it hasn't already
const EObjectFlags SubobjectFlags = Outer->GetMaskedFlags(RF_PropagateToSubObjects);
Result = StaticConstructObject(MyCompClass, Outer, SubobjectFName, SubobjectFlags, Template);
if ( !Outer->GetArchetype()->GetClass()->HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic) )
{
	// The archetype of the outer is not native, so we need to copy properties to the subobjects after the C++ constructor chain for the outer has run (because those sets properties on the subobjects)
	UObject* MaybeTemplate = Outer->GetArchetype()->GetClass()->GetDefaultSubobjectByName(SubobjectFName);
	if (MaybeTemplate && MaybeTemplate->IsA(MyCompClass) && Template != MaybeTemplate)
	{
		ComponentInits.Add(Result, MaybeTemplate);
	}
}

if (Outer->HasAnyFlags(RF_ClassDefaultObject) && Outer->GetClass()->GetSuperClass())
{
	Outer->GetClass()->AddDefaultSubobject(Result, MyCompClass);
}
Result->SetFlags(RF_DefaultSubObject);

That will get you the spawned component type you need based off a runtime-specified class.

For the finder part, the problem is that you’re telling it to find a component, but specifying a class. So, it fails to find it because the BP_MyComponent_C is a UBlueprintGeneratedClass, not a UMyComponent. What you need to do there is:

ConstructorHelpers::FObjectFinder MyDefaultObject(TEXT("Class'/Game/Tests/BP_MyComponent.BP_MyComponent_C'"));

And that will give you the class to use in the code to spawn the component in the first snippet. Hope that clears things up!

Alright I almost got it, but ComponentInits is marked private in FPostConstructInitializeProperties. I have also seen that created objects are returned as TSubobjectPtrConstructor which is also private so I cannot use that as well.

Everything seems to be programmed in a way to prevent me from doing this which makes me wonder if I am supposed to do it at all?

My ideal solution would be where I could have a component attribute or an array of componets that can be filled from editor. I would have a blueprint character for example and add components to it in the graph editor. Then in the defaults I would assign the added components to the array or, in case there is only one attribute, to the single property.

So far I have only been able to make attributes editiable that are “class MyClass* testSetAttribute” but I can only assign objects from the content browser and I think using smart pointers would be better?

greetings

We prevent people from doing it because there’s a lot of potential for non-obvious things to happen, unfortunately!

The constructor, where you’re attempting to add the component, actually runs before serialization of the class. So, if you were using a property to dictate which class to spawn, it wouldn’t actually be valid at that time! You’d have to move it to later, after the instance properties have been serialized in.

You also lose the ability to change and override the class in children using the PCIP, which is also non-obvious.

The best way to deal with dynamic components like this is to use the construction script or AddComponent nodes in the blueprint to add them. Alternatively, you could spawn the components in ::PostLoad() or BeginPlay() or some other time, which would allow the serialization to be valid.