Creating an abstract component then selecting class in BP

I am trying to create a class in C++.
And then, I will create blueprints based on that class.

The class will have a UShapeComponent as the root (and for collision). But different BPs may need different shapes. So I don’t want to create a Box or Sphere or Capsule in my C++ class. I want to create a UShapeComponent and select the shape in my BP.

Since UShapeComponent is abstract, I cannot use obvious methods.

Is this possible?

There are a few ways to do it. One way would be in the C++ class to do:

UShapeComponent* ShapeComponent = CastChecked<UShapeComponent>(GetRootComponent());

to get a pointer to the ShapeComponent when it is needed.

Does that do what you want?

I am not sure if that is what I need.

To be more clear, I am already defining my components like this:
Dispenser.h:

	UPROPERTY(EditDefaultsOnly)
	UStaticMeshComponent* MeshComponent;

	UPROPERTY(EditDefaultsOnly)
	class UBoxComponent* InteractionBox;

Then create them in cpp like this:
Dispenser.cpp:

ADispenser::ADispenser()
{
	PrimaryActorTick.bCanEverTick = true;

	CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionBox"));
	RootComponent = CollisionBox;

	MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
	MeshComponent->SetupAttachment(RootComponent);
}

BUT this forces me to use a UBoxComponent for every child BP of this class.

I want to be able to use any of the UShapeComponent inherited shape (like capsule, sphere) for THAT component depending of the actual shape of the object.

Right, I understand. What I’m suggesting is to not create the components in the ADispenser constructor, but to do that in the blueprint subclasses (like in the blueprint editor), and then you can find them / downcast them / store pointers to them later (in for example ADispenser::PostInitializeComponents or ADispenser::BeginPlay).

That is an amazing idea but then how would I attach the, for example, mesh component to my collision component? I am guessing not in the constructor but in BeginPlay etc. right?

But that means I won’t be able to edit them in the BP editor.

So, I need to create them in BP too.

If this is the case, I would be a little bit worried since the component tree is not being inherited but the parent class code would be heavily dependent on it.

That is an amazing idea but then how would I attach the, for example, mesh component to my collision component? I am guessing not in the constructor but in BeginPlay etc. right? But that means I won’t be able to edit them in the BP editor.

No, you would create and attach the mesh component in the BP editor in the blueprint subclass. If you need a pointer to the mesh component in ADispenser, then you get it like you would get it and cast it like you would the ShapeComponent.

If this is the case, I would be a little bit worried since the component tree is not being inherited but the parent class code would be heavily dependent on it.

Right, the base class would need to check that the component tree provided by the subclass has the right structure in PostInitializeComponents or whereever it gets the pointers to its components.

I’d like to provide a potential alternative, if you like dangerous things.

You can still create an inherited component tree, and change the root component post-construction. It is not exposed to blueprints, but in C++ it’s not that uncommon to use SetRootComponent to change the root component.

For example, this method is used in Unreal Tournament when turning player character into a ragdoll. By default the root component is the capsule, but for ragdolling Epic sets the skeletal mesh as root component and enables physics on it (and disables capsule).

In a similar fashion, you could declare a default Box root component, and change the root component post-construction to a different shape component (eg. sphere), then reattach the former root to your new root.

Considering you’ll probably want to bind events to the shape component which is gonna be set by blueprints, you cannot bind events in constructor. But you can do that in BeginPlay just fine.

Here is a rough attempt, that seems to work (probably… maybe)

UCLASS()
class ATestActor : public AActor
{
    GENERATED_BODY()

    ATestActor();

public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UShapeComponent* ShapeComp;

    UFUNCTION(BlueprintCallable)
    void ReplaceRoot(USceneComponent* Comp);

    virtual void BeginPlay() override;

    UFUNCTION()
    virtual void NativeOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);    
};
ATestActor::ATestActor()
{
    ShapeComp = CreateDefaultSubobject<UBoxComponent>("Box");
    RootComponent = ShapeComp;
}

void ATestActor::ReplaceRoot(USceneComponent* Comp)
{
    if (!Comp || Comp == RootComponent)
        return;

    auto OldRoot = GetRootComponent();
    OldRoot->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);

    Comp->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);

    SetRootComponent(Comp);

    OldRoot->AttachToComponent(Comp, FAttachmentTransformRules::KeepWorldTransform);
}

void ATestActor::BeginPlay()
{
    Super::BeginPlay();

    UE_LOG(LogTemp, Display, TEXT("[BeginPlay] ShapeComp = %s"), *ShapeComp->GetName());

    ShapeComp->OnComponentBeginOverlap.AddDynamic(this, &ATestActor::NativeOverlap);
}

void ATestActor::NativeOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    UE_LOG(LogTemp, Display, TEXT("[NativeOverlap] Comp = %s , Other = %s"), *OverlappedComponent->GetName(), *OtherActor->GetName());
}

Then this is my BP extending native class.
See the inherited root component. I added a static mesh for good measure, it could be inherited or not, doesn’t change anything.
I added a Sphere component below the root component :

Then in construction script I’m calling my replace root function, and assigning the sphere to the ShapeComp variable :

The blueprint viewport doesn’t show hierarchy changes, but when placing actor in the world the sphere effectively takes over the root, and box is attached back to the sphere :

Upon playtesting the logs show RootComponent is the sphere, ShapeComp is the sphere, and the events bound in BeginPlay are triggered from the sphere.

1 Like