Search code examples
c++unreal-engine4unreal-engine5

Dynamically create a SphereComponent in UnrealEngine


I'm trying to create a SphereComponent dynamically 2 seconds after the level is started. For that I have this following code:

void ACollidingPawnSpawnPawns::DelayedFunction() {
         // The first parameter 'SphereComponent' is the main component in the level and it has been created using the CreateDefaultSubobject method in the constructor of this class.  But that method can't be used outside the constructor.
         USphereComponent *dynamicallyCreatedSphere = NewObject<USphereComponent>(SphereComponent, USphereComponent::StaticClass());
             dynamicallyCreatedSphere ->InitSphereRadius(30.0f);
             dynamicallyCreatedSphere ->SetCollisionProfileName(TEXT("Pawn"));
             dynamicallyCreatedSphere ->SetRelativeLocation(FVector(155.0f, 165.0f, 45.0f));
             dynamicallyCreatedSphere ->SetVisibility(true);
 }

After I run the level I don't see this dynamic sphere pop up.

For the main 'SphereComponent' that does show up this code is in the constructor:

// Our root component will be a sphere that reacts to physics
 SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
 RootComponent = SphereComponent;
 SphereComponent->InitSphereRadius(40.0f);
 SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
 
    // Create and position a mesh component so we can see where our sphere is
     UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
     SphereVisual->SetupAttachment(RootComponent);
     static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
     if (SphereVisualAsset.Succeeded())
     {
         SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
         SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
         SphereVisual->SetWorldScale3D(FVector(0.8f));
     }

But again I can't use the CreateDefaultSubobject to create the UStaticMeshComponent outside the constructor.

So I imagine for me to see my dynamicallyCreatedSphere in my level I would have to create a UStaticMeshComponentand I would need some variation of the FObjectFinder method to invoke (this FObjectFinder also doesn't work outside the constructor).

I've been stuck on this for a while now. Anyone know how to do this?

Update 2021-12-2 Thanks to advice from https://stackoverflow.com/a/70180682/4722577 below, here's the final code I have to create the sphere dynamically:

In the cpp file:

void ACollidingPawnSpawnPawns::spawnPawns()
{
    if (dynamicallyCreatedSphere == nullptr) {
        //dynamicallyCreatedSphere = NewObject<USphereComponent>(USphereComponent::StaticClass()); // compiles
        dynamicallyCreatedSphere = NewObject<USphereComponent>(SphereComponent, USphereComponent::StaticClass());
        //dynamicallyCreatedSphere->SetupAttachment(SphereComponent);
        dynamicallyCreatedSphere->InitSphereRadius(30.0f);
        dynamicallyCreatedSphere->SetCollisionProfileName(TEXT("Pawn"));
        dynamicallyCreatedSphere->SetRelativeLocation(FVector(155.0f, 165.0f, 45.0f));
        dynamicallyCreatedSphere->SetVisibility(true);


        dynamicMesh = NewObject<UStaticMeshComponent>(this);
        dynamicMesh->AttachToComponent(dynamicallyCreatedSphere, FAttachmentTransformRules::KeepWorldTransform);
        dynamicMesh->RegisterComponent();
        dynamicMesh->SetStaticMesh(StaticMesh);
    }
}

in the .h file I have these 3 lines:

UPROPERTY(EditAnywhere)
UStaticMesh* StaticMesh;

USphereComponent* dynamicallyCreatedSphere = nullptr;
UStaticMeshComponent* dynamicMesh = nullptr;

Declaring the StaticMesh as a UProperty allows me to select the mesh manually in the UE interface. Then when I run the level that StaticMesh is inserted into the dynamicMesh variable.


Solution

  • First, only NewObject can be used to create components outside of constructor:

    void ACollidingPawnSpawnPawns::DelayedFunction() 
    {
        // you should specify outer as current actor (because this component is part of it)
        USphereComponent* DynamicallyCreatedSphere = NewObject<USphereComponent>(this, USphereComponent::StaticClass());
        DynamicallyCreatedSphere->RegisterComponent();
        // when component is dynamically created, you can use AttachToComponent, not SetupAttachment
        DynamicallyCreatedSphere->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
        DynamicallyCreatedSphere->InitSphereRadius(30.0f);
        DynamicallyCreatedSphere->SetCollisionProfileName(TEXT("Pawn"));
        DynamicallyCreatedSphere->SetRelativeLocation(FVector(155.0f, 165.0f, 45.0f));
        DynamicallyCreatedSphere->SetVisibility(true);
    }
    

    Instead of FObjectFinder you can use LoadObject to load something using specified path. But I think this is incovinent way, because ref is hardcoded. Use following code in UCLASS instead:

    UPROPERTY(EditAnywhere) 
    UStaticMesh* StaticMesh;
    

    And specify static mesh directly: SphereVisual->SetStaticMesh(StaticMesh);

    After that you can inherit C++ class to blueprint and pick StaticMesh from Content Browser.