Add component to actor in C++ (the final word!)

I was wondering too if it would be safe to use AddComponentByClass to easily create components at runtime, and was confused by the DO NOT USE DIRECTLY comment and BlueprintInternalUseOnly flag…

I think the comment is only relevant for UK2Node_AddComponentByClass or otherwise it would have been placed in the function comment body directly, not in the @see note.

It seems this function is safe to use in C++, since it is called in some places in some built-in plugins of the engine (see examples below).

Moreover, it looks like using this function automatically adds replication and per-instance serialization (game save) support for the created component, and is using the bAutoRegister flag of the component to register it or not.


Some examples:

  • In PCG (Procedural Content Generation) unit tests:
UPCGUnitTestDummyComponent* Component = Cast< UPCGUnitTestDummyComponent>(TestData.TestActor->AddComponentByClass(Settings->ComponentClass, false, FTransform::Identity, false));
  • In LiDAR Point Cloud plugin:
ULidarPointCloudComponent* PCC = (ULidarPointCloudComponent*)TargetActor->AddComponentByClass(ULidarPointCloudComponent::StaticClass(), true, Component->GetComponentTransform(), false);
PCC->SetPointCloud(Component->GetPointCloud());
PCC->SetWorldTransform(Component->GetComponentTransform());
PCC->AttachToComponent(TargetActor->GetRootComponent(), FAttachmentTransformRules(EAttachmentRule::KeepWorld, false));
  • In Smart Objects plugin for preview meshes:
UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(Actor->AddComponentByClass(UStaticMeshComponent::StaticClass(), true, FTransform::Identity, false));
if (MeshComponent)
{
	MeshComponent->SetStaticMesh(InStaticMesh);
}

EDIT

After some research in the source code, it does not seems to be the best way to create C++ components at runtime.

In fact, the component is added to the BlueprintCreateComponents array for serialization and marked as created by user construction script (in blueprint).
It seems the serialization will be valid only if the archetype of the component is a component CDO or an actor’s DefaultSubObject component.
Also, this array gets occasionally wiped and refilled using the actor’s construction script.

From looking into how the UGameFrameworkComponentManager of the Modular Gameplay plugin handles the runtime component creation, I think the only required setup are those 2 lines:

UActorComponent* NewComp = NewObject<UActorComponent>(ActorInstance, ComponentClass);
NewComp->RegisterComponent();

You should also manage the attachment for USceneComponents, and also prevent creation of replicated components on client actors (no authority).
Replication is handled inside RegisterComponent().
I don’t know if we need to call something to support game save (e.g. AddInstancedComponent?).

A generic function to create runtime actor components would looks like that:

UActorComponent* CreateComponentOnInstance(AActor* ActorInstance, TSubclassOf<UActorComponent> ComponentClass, USceneComponent* OptionalParentForSceneComponent = nullptr)
{
	if (!ActorInstance || !ComponentClass)
		return nullptr;

	// Don't create component on a template actor (CDO or Archetype)
	if (ActorInstance->IsTemplate())
		return nullptr;

	// For multiplayer games, create component only on server if component is replicating
	const UActorComponent* ComponentCDO = ComponentClass->GetDefaultObject<UActorComponent>();
	if (ComponentCDO->GetIsReplicated() && !ActorInstance->HasAuthority())
		return nullptr;

	UActorComponent* NewComp = NewObject<UActorComponent>(ActorInstance, ComponentClass);

	// Handles USceneComponent attachment
	if (USceneComponent* NewSceneComp = Cast<USceneComponent>(NewComp))
	{
		USceneComponent* ParentComponent = OptionalParentForSceneComponent;
		if (!ParentComponent)
			ParentComponent = ActorInstance->GetRootComponent();

		if (ParentComponent)
		{
			// Parent component should always be owned by the passed in actor instance!
			check(ParentComponent->GetOwner() != ActorInstance);
			NewSceneComp->SetupAttachment(ParentComponent);
		}
		else
		{
			// Set component directly as root if no root component on the actor
			ActorInstance->SetRootComponent(NewSceneComp);
		}
	}

	NewComp->RegisterComponent();
	return NewComp;
}
6 Likes