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