Static lighting on procedurally added static mesh components disappears when running game

I’m experimenting with procedurally adding static meshes to an actor based on variables. Below is a class that fills the height of a box with repeated meshes.

/**
 * a simple test class that takes a mesh and repeats it over the z axis
 */
UCLASS()
class MYPROJECT_API AMeshTower : public AActor
{
GENERATED_UCLASS_BODY()

/** the box this volume takes up */
UPROPERTY(VisibleInstanceOnly, Category = Size)
TSubobjectPtr<UBoxComponent> baseBox;

/** the mesh that this actor duplicates */
UPROPERTY(EditAnywhere, Category = Parameters)
UStaticMesh *dupMesh;

/** the construct script, creates the actual geometry */
UFUNCTION()
virtual void OnConstruction(const FTransform& aTransform) override;

};

AMeshTower::AMeshTower(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{


baseBox = PCIP.CreateDefaultSubobject<UBoxComponent>(this, TEXT("baseBox"));

baseBox->SetMobility(EComponentMobility::Static);

RootComponent = baseBox;


}


void AMeshTower::OnConstruction(const FTransform& aTransform) {

int spacing = 100;

int32 num_z = baseBox->GetUnscaledBoxExtent().Z/spacing;

for (int32 i = -num_z; i < num_z; i++) {
    
    
    UStaticMeshComponent *newComp = NewObject<UStaticMeshComponent>(this);
    
    newComp->AttachTo(RootComponent);
    newComp->RegisterComponent();
    
    // required for proper GC - this causes the mesh to get automatically destroyed when OnConstruction is re-run
    newComp->bCreatedByConstructionScript = true;
    
    UStaticMesh *useMesh = dupMesh;
    
    newComp->SetStaticMesh(useMesh);
    
    FVector loc(0, 0, i * spacing);
    
    newComp->SetRelativeLocation(loc);
    
    newComp->SetMobility(EComponentMobility::Static);
    
    
    
}


}

When clicking build, all static lighting is built correctly. However, when I press play or simulate, the meshes revert to dynamic lighting, and the message “LIGHTING NEEDS TO BE REBUILT (x unbuilt objects(s))”.

Hi fx21,

If you are using Static Meshes with Mobility set to Static, then you will see the message “LIGHTING NEEDS TO BE REBUILT.” In order for a mesh to accept light dynamically, it would have to either be a Static Mesh with Mobility set to Movable, or a Skeletal Mesh.

Hello, thanks for your answer.

Let me see if I can explain myself better. I do want all light to be static, which is why I set the mobility to static. I’ve previously done the same thing with blueprints and that worked fine.

The problem is that the above code is that it convinces the editor that the objects are static enough to have to have lighting built. If I place one of my towers on a brush, for example, the brush gets a static shadow added to it that won’t disappear until I rebuild the lighting. HOWEVER, when I click play or simulate, the static lighting calculations on the generated meshes is thrown away and the error that light needs to be rebuilt pops up. The meshes then cast both static AND dynamic shadows.

I wasn’t sure if I should file this under bugs, as I’m not experienced enough to know if I’ve made a mistake somewhere. All I want is for the light to be built and then stick around when I click play.

Since you are spawning Static Meshes in the construction script of your class, it is normal for the pre-built lighting not to be used. All of those Static Meshes are being recreated as soon as the game starts, and the Engine no longer has any lighting information for the new meshes.

What setting are you using for your light’s mobility? That also gets factored in. I did some testing with spawned Static Meshes, and everything worked as expected. Here is what I tested:

Static Mesh set to Static and Light set to Static: No shadow cast at all and lighting rebuild message displayed.

Static Mesh set to Static and Light set to Stationary: Dynamic shadow cast and lighting rebuild message displayed.

Static Mesh set to Static and Light set to Movable: Dynamic shadow cast and no lighting rebuild message.

Static Mesh set to Movable and Light set to Static: No shadow cast at all and no lighting rebuild message.

Static Mesh set to Movable and Light set to either Stationary or Movable: Dynamic shadow cast and no lighting rebuild message.

I also tried destroying a Static Mesh set to Static with a Static light and the shadow it cast remained after it was destroyed, while with Stationary or Movable lights, the shadow disappeared when it was destroyed.

All of those scenarios I mentioned above are expected behavior. However, you mention that you are seeing both static and dynamic shadows at the same time? Would you be able to provide a screenshot of the shadows that you are seeing?

Here is my level. To the left is the blueprint I’m using for reference, and to the right is the C++ class that’s supposed to do the same thing. The blueprint uses the construction script to AddStaticMeshComponent with mobility set to static. Each adds 2 static meshes, and the editor decides those 4 meshes need to have lighting built. So far so good.

Lighting is built and the warning message goes away.

Hiding both actors shows that the shadows have been added to the underlying floor mesh, that is, both are casting static shadows.

Pressing simulate. The blueprint version remains the same as it always was, the C++ version loses its static light and starts casting a new shadow (dynamic, or preview, not sure how to describe it) on top of the one already baked onto the floor. The warning message shows that the lighting needs to be rebuilt for the C++ meshes, whereas the blueprint ones are fine.

The light is set to stationary.

Is there any way to do in C++ what the blueprint does? The first part of your comment suggest that it’s not, at least not using OnConstruction. In that case I’m confused, as I was under the impression that OnConstruction and the blueprint Construction Script were roughly equivalent.

There are a few things I would like you to try, using the final screenshot that you provided as a baseline, and see how each one affects the shadows created by the C++ version. I have had some difficulty reproducing the spawning behavior given the code you provided. There is probably something minor that I am doing differently.

  • Move the camera away from the objects until they appear about half the size.
  • Set the light’s mobility to Static.
  • Change the C++ class to set the Static Mesh to Movable.


The camera moved away from the objects. I stopped when the sharp shadow started to slightly fade away. As the camera moves further back, the sharp shadow fades away completely and only the built shadow remains.


Light source set to static.


Light source set back to stationary and the spawned meshes set to movable.


Light source static and meshes moveable.

Just to be clear, are you saying it’s possible to do what the blueprint version does with C++?

Sorry for not getting back to you sooner. I set up some different code for spawning using C++, and was able to see the same result that we were getting from the Blueprint. I suspect that the Engine is treating the lighting as dynamic in these instances, but I have not been able to confirm that yet. The information I have received so far is that it is not intended for it to be possible to spawn StaticMesh components with Mobility set to Static during runtime, which is essentially what we are doing here. I believe we can do what you are trying to do for now, but I would not be surprised if this becomes impossible in a future version of the Engine.

Here is the code that I have that seems to be working the same as the Blueprint:

.h
#pragma once

#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

/**
 * 
 */
UCLASS()
class TESTLIGHTING_API AMyActor : public AActor
{
	GENERATED_UCLASS_BODY()
	
	UPROPERTY(VisibleAnywhere, Category = Parameters)
	TSubobjectPtr<UStaticMeshComponent> dupMesh;
};

.cpp

#include "TestLighting.h"
#include "MyActor.h"


AMyActor::AMyActor(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	dupMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("dupMesh"));
	RootComponent = dupMesh;

	static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMesh(TEXT("StaticMesh'/Game/Shapes/Shape_Cube.Shape_Cube'"));

	dupMesh->SetStaticMesh(StaticMesh.Object);
	dupMesh->SetRelativeLocation(FVector(0.f, 0.f, 0.f));
	dupMesh->SetMobility(EComponentMobility::Static);
}

You will have to modify this code to fit your needs, but from what I have seen in my testing this works the same as the Blueprint spawning does.

Well, that’s disappointing. The blueprint version can spawn any number of meshes based on the box size, of any type, based on the dupMesh variable. Just adding a single fixed cube isn’t going to cut it for my needs.

Maybe I can create some kind of blueprint-c+±abomination to work around it. Thanks for your help.

It should still be possible to do what you want. I deliberately kept the code I was trying as simple as possible in order to eliminate as many potential variations as I could. You should be able to modify the code I provided above to spawn multiple meshes within a box, and change the mesh that is spawned as well.

For example, you could try using a TArray of UStaticMeshComponents, which you add meshes to, placing each new one on top of the previous one, until the box is filled. I have not tested that, but it should work.

If it is possible then I sure don’t see how. If I were to use your example, I would have to do any kind of dynamic adding the the constructor. However, this is too early, as the user variables have not gotten their values in the editor at that point. For example, baseBox dimensions are still 32/32/32 at this point. Even your example you had to hard code ShapeCube in as you (I assume) could not read it from a variable.

Then, on the other hand, I have OnConstruction, which apparently is the equivalent of a blueprint construction script, except it throws away any added components and is run again when the game starts.

Blueprint Essentials - 10 - Using Loops : Procedural Level Design

This is what I’m trying to achieve. The ability to add actors and change their variables for each instance, and have those changes appear in the editor in the form of spawned meshes. It’s doable for blueprints, but I don’t see how it’s done for C++, as OnConstruction is not the place for it. Is there another function that is guaranteed to have its components kept (like a blueprint construction script)?

Let’s say you wanted to change your example so instead of a cube spawning every time, the user would be able set any mesh they wanted from the asset library, an unique one on every MyActor added to the level. How would you change your code to use that variable and still have the lighting kept?

EDIT: Or spawn n of them, where n is an int variable exposed in the details panel?

I’m having some success with just generating TArrays of stuff that needs to be added (TArray<UStaticMesh *> and TArray<FTransform>) in a C++ function and passing those off to a subclass blueprint for actual adding (AddStaticMeshComponent). Lighting is built and kept when clicking play. The added meshes seem to stick around though, resulting in an increasing number of meshes-with-NULL-mesh error messages. I will investigate further.

Hi fx21,

I spent some time today recreating the Blueprint tutorial that you linked using C++. It still needs some work, but it replicates the basics of what you saw in the video (the math needs some polishing, and I ran into the same issue you described where meshes were not being removed). This is what I have so far:

.h

#pragma once

#include "GameFramework/Actor.h"
#include "ProceduralWall.generated.h"

/**
 * 
 */
UCLASS()
class TESTLIGHTING_API AProceduralWall : public AActor
{
	GENERATED_UCLASS_BODY()

	/*UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ProceduralWall)
	TSubobjectPtr<USceneComponent> Root;*/
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ProceduralWall)
	TSubobjectPtr<UStaticMeshComponent> BaseWall;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (MakeEditWidget = true), Category = ProceduralWall)
	FVector EndPoint;

	UPROPERTY()
	float MeshSize;

	UFUNCTION()
	virtual void OnConstruction(const FTransform& aTransform) override;
};

.cpp

#include "TestLighting.h"
#include "ProceduralWall.h"
#include "Engine.h"


AProceduralWall::AProceduralWall(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	BaseWall = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("BaseWall"));
	RootComponent = BaseWall;

	MeshSize = 50.f;
}

void AProceduralWall::OnConstruction(const FTransform& aTransform)
{
	FVector CleanEndPoint = EndPoint * FVector(1.f, 1.f, 0.f);
	if (CleanEndPoint != (FVector(0.f, 0.f, 0.f)))
	{
		if (GEngine)
		{
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Red, TEXT("Building!"));
		}
		float Length = EndPoint.Size();
		float CalcWalls = Length / MeshSize;
		int32 NumWalls = FMath::FloorToInt(CalcWalls);
		for (int32 i = 1; i < NumWalls; i++)
		{
			UStaticMeshComponent* NewComp = NewObject<UStaticMeshComponent>(this);
			NewComp->AttachTo(BaseWall, NAME_None, EAttachLocation::KeepWorldPosition);
			NewComp->SetStaticMesh(BaseWall->StaticMesh);
			NewComp->RegisterComponent();
			int32 Dist = i * MeshSize;
			FVector NewPos = FVector(Dist, 0, 0);
			NewComp->SetRelativeLocation(NewPos);
		}
		
		FVector ActorLocation = GetActorLocation();
		FRotator LookAtRotation = FRotationMatrix::MakeFromX(EndPoint).Rotator();
		FRotator FinalLook = FRotator(0, LookAtRotation.Yaw, 0);
		BaseWall->SetWorldRotation(FinalLook);
	}
}

This also seems to have cleared up the question about the lighting needing to be rebuilt, since I could put a wall in place, build the lighting, then click Simulate or Play and I did not receive the message that lighting needs to be rebuilt.

I will see if I can find some more time next week to try to clean up the math and the problem with meshes not being deleted.

Thank you, I appreciate the effort. A small question though, I don’t see you setting the mobility to static anywhere. Does this wall even need lighting built to begin with?

Ah, yes. Sorry. I forgot to mention that. I was setting mobility to Static when I specified the mesh to be used by BaseWall in the Blueprint I created for this class. I tried it with the Mobility set to both Static and Movable, as well as the different Mobility settings for the light source, and as long as I built the lighting after I placed the wall in the Editor, I did not see the message about lighting needing to be rebuilt. If you move or alter the wall after building lighting, then I believe the message appeared again (I’m not at the office currently, so I don’t have the project in front of me).

Also, the “Building!” debug message I added was simply to give me an indication of when the Editor was running through that part of the code, and it appeared to be running exactly when it was supposed to.

It turns out I managed to leave a NewObject<UStaticMeshComponent> when I copied code around so that’s where the extra mesh component error messages came from. Making a function that stores some arrays in a variable works, as does making a function that returns an array of data and using it.

Hi . I hope you had a good week. I’m going to go with the workaround of creating the data in C++, then passing it to a blueprint construct script for actual construction, to have it retained when the game starts. Thanks for your help.