How to swap out the visual mesh component of a pawn at run-time

I am attempting to replace the visible component of a pawn with a different component at runtime (in my case a UCustomMeshComponent with randomly generated vertices) and running into problems.

I set up my pawn like this in its constructor (where OurMesh is a UCustomMeshComponent * member variable):

ATestGeomPawn::ATestGeomPawn() : OurMesh(NULL)
{

	PrimaryActorTick.bCanEverTick = false;
	AutoPossessPlayer = EAutoReceiveInput::Player0;

	// root is a scene component so this is placeable in the editor
	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));

    // camera attached to root
	UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("OurCamera"));
	Camera->SetupAttachment(RootComponent);
	Camera->SetRelativeLocation(FVector(-250.0f, 0.0f, 250.0f));
	Camera->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));

    // create an initial visible component
	RegenGeometry();

}

And RegenGeometry()'s intent is to set up the new component, destroying the old one if it exists (first time through it won’t). RegenGeometry() may be called at any time (in my tests I have it bound to an action event):

void ATestGeomPawn::RegenGeometry() {

    // destroy old mesh
	if (OurMesh) {
		OurMesh->DestroyComponent();
		OurMesh = NULL;
	}

	TArray<FCustomMeshTriangle> tris;
    // code for setting up tris omitted for brevity

    // create and attach new mesh
	OurMesh = NewObject<UCustomMeshComponent>(this);
	OurMesh->SetCustomMeshTriangles(tris);
	OurMesh->SetupAttachment(RootComponent);

}

However, this leads to all sorts of bad results. I’ve tried various hand-wavey iterations of this:

  • The code as shown above just crashes the editor immediately, even if I don’t actually have an ATestGeomPawn placed in the map.
  • If I use the now-deprecated NewNamedObject instead, with the same name every time, it works initially but subsequent calls to RegenGeometry() cause the mesh to disappear, with weird results shown in this video I made.
  • If I use NewNamedObject but generate a new name every time, it works initially but does not remove the initial component from the hierarchy, and has a similar visible outcome to the above, shown in this other video I made.
  • If I call RegenGeometry() in BeginPlay() instead of the constructor, it is never visible.

How do I do this properly?

I’m super new to the Unreal SDK, as in yesterday morning I was doing a crash course through tutorials and docs to try and get this done. I eventually arrived at the above. I have a feeling that my mistake here is some misunderstanding of life-cycles, replication, or maybe e.g. reference counting due to say player input on the pawn or something:

Update: The player input theory is eliminated. I tried the exact same thing as above but with a regular actor instead of a pawn (camera eliminated, no player input, just RegenGeometry() in constructor and nothing else), and the editor crashed in the same way. So it isn’t related to this being a pawn.

I’m not sure which direction to head in next.

(Full source, if relevant: TestGeomPawn.h and TestGeomPawn.cpp; the “grow” stuff I just threw in from the tutorial to make sure player input was working)

So after a lot of hunting I stumbled on somebody else’s code, and it turns out the problem is in the component creation. SetupAttachment isn’t the right thing to do for runtime-created components. Instead of:

OurMesh = NewObject<UCustomMeshComponent>(this);
OurMesh->SetCustomMeshTriangles(tris);
OurMesh->SetupAttachment(RootComponent);

I changed it to:

OurMesh = NewObject<UCustomMeshComponent>(this);
OurMesh->RegisterComponent();
OurMesh->SetCustomMeshTriangles(tris);
OurMesh->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform, NAME_None);

And it seems to be working, aside from the component initially created in the constructor sticking around in the object tree the whole time (which seems to be related to “default” values, but it doesn’t seem to be having any bad effects, so…).

I’m not sure if I just missed this in the documentation, or if Epic felt like only developers who hunted the vast plains of the internet for it were worthy of the knowledge. The tutorials could be improved by mentioning clearly that the frequently used SetupAttachment isn’t useful for runtime components.