Hey, guys. I’m using Unreal Engine 5.3.2 and need some help writing C++ code that runs in the editor.
Specifically, I’ve been trying to write some C++ code to help me populate my level with tree foliage. The current workflow I have is this: I use the Foliage Mode tools to paint ground foliage, rocks, boulders and tree trunks; the tree foliage (which is connected to tree trunks) is added using C++ code I wrote. The gross functionality is working, I can add a new UFoliageInstancedStaticMeshComponent to the AInstancedFoliageActor and I manage to create the instances with no issues.
The problem is, however, that the Foliage ISM Component is not picked up by the Foliage Mode.
When I switch back to Foliage Mode after running my code and select all foliage, I get something like this:
Notice the Foliage branches are not highlighted, even though I just hit ALL from the Foliage Mode panel. The Static Mesh Foliage (UFoliageType_InstancedStaticMesh) appears in the level list when Foliage Mode is active, but no instances are counted.
For the purposes of gameplay, this doesn’t really matter. But when I use the world partition commandlet to change foliage cell size, for example, the fact the tree foliage is not recognized by the foliage mode matters a lot: it disappears. This is a problem because I’m using a Foliage Cell size of 256m for working in the editor, for performance, but when actually running the level I want to use a Foliage Cell size of 32m, also for performance.
Working with a foliage cell size of 32m in the editor is not feasible: for a 16 square km landscape, so many AInstancedFoliageActors are created that the editor can take ages to load and unload them all. Conversely (when actually running the level) having the Foliage Cell size of 256m nets me ~40FPS, but at 32m I get 60+ FPS.
The code I’m running is this:
UFoliageInstancedStaticMeshComponent* NewFoliageComponent = NewObject<UFoliageInstancedStaticMeshComponent>(Actor, UFoliageInstancedStaticMeshComponent::StaticClass());
NewFoliageComponent->OnComponentCreated();
NewFoliageComponent->AttachToComponent(Actor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
NewFoliageComponent->RegisterComponent();
Actor->AddInstanceComponent(NewFoliageComponent);
NewFoliageComponent->SetStaticMesh(Mesh);
this->ProcessNewTreeFoliageComponent(NewFoliageComponent);
this->RegisterFoliageTypeToActor(Cast<AInstancedFoliageActor>(Actor), /*FoliageType*/ Entry.Value.Get<1>());
TreeFoliageComponentMap.Add(Mesh->GetName(), NewFoliageComponent);
ProcessNewTreeFoliageComponent is a function for setting a bunch of parameters on the Foliage Component; so that nothing is left unsaid, it does this:
void AFoliageHandler::ProcessNewTreeFoliageComponent(UFoliageInstancedStaticMeshComponent* NewFoliageComponent) {
NewFoliageComponent->SetMobility(EComponentMobility::Type::Movable);
NewFoliageComponent->SetGenerateOverlapEvents(true);
NewFoliageComponent->SetCollisionProfileName(FName("Custom..."), true);
NewFoliageComponent->SetCollisionEnabled(ECollisionEnabled::Type::QueryOnly);
NewFoliageComponent->SetCollisionObjectType(this->FoliageCollisionChannel);
NewFoliageComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
NewFoliageComponent->bMultiBodyOverlap = true;
NewFoliageComponent->SetNumCustomDataFloats(1);
NewFoliageComponent->bEnableAutoLODGeneration = true;
NewFoliageComponent->HLODBatchingPolicy = EHLODBatchingPolicy::MeshSection;
NewFoliageComponent->CastShadow = true;
NewFoliageComponent->bAffectDynamicIndirectLighting = true;
NewFoliageComponent->bAffectDistanceFieldLighting = true;
NewFoliageComponent->bCastDynamicShadow = true;
NewFoliageComponent->bCastStaticShadow = true;
NewFoliageComponent->bCastContactShadow = true;
NewFoliageComponent->bHasCustomNavigableGeometry = EHasCustomNavigableGeometry::No;
NewFoliageComponent->bUseAsOccluder = true;
NewFoliageComponent->bVisibleInRayTracing = true;
NewFoliageComponent->SetEvaluateWorldPositionOffset(true);
NewFoliageComponent->SetEvaluateWorldPositionOffsetInRayTracing(true);
NewFoliageComponent->WorldPositionOffsetDisableDistance = 2500;
if (!NewFoliageComponent->ComponentHasTag(FName("TreeFoliage"))) NewFoliageComponent->ComponentTags.Add(FName("TreeFoliage"));
}
The function that actually tries to do what I’m struggling with is RegisterFoliageTypeToActor, it is this:
void AFoliageHandler::RegisterFoliageTypeToActor(AInstancedFoliageActor* FoliageActor, UFoliageType_InstancedStaticMesh* FoliageType)
{
if (!FoliageActor || !FoliageType)
{
if (this->debug) UE_LOG(LogTemp, Warning, TEXT("FoliageActor or FoliageType is null!"));
return;
}
// Register the FoliageType with the actor
bool shouldRegister = FoliageActor->ForEachFoliageInfo(
[&](UFoliageType* ExistingType, FFoliageInfo& Info)
{
UFoliageType_InstancedStaticMesh* ExistingFoliageType = Cast<UFoliageType_InstancedStaticMesh>(ExistingType);
if (ExistingFoliageType == FoliageType)
{
if (this->debug) UE_LOG(LogTemp, Display, TEXT("FoliageType already registered."));
return false; // Stop if already registered
}
return true;
}
);
if (shouldRegister) {
// Create a FoliageInfo for the new FoliageType
FFoliageInfo* NewFoliageInfoPtr = new FFoliageInfo();
NewFoliageInfoPtr->CreateImplementation(FoliageType);
NewFoliageInfoPtr->IFA = FoliageActor;
NewFoliageInfoPtr->Initialize(FoliageType);
// Add the new FoliageType if not present
FoliageActor->AddFoliageType(FoliageType, &NewFoliageInfoPtr);
if (this->debug) UE_LOG(LogTemp, Display, TEXT("Registered new FoliageType: %s"), *FoliageType->GetName());
}
}
The code that adds instances is called later, after all Foliage ISM Components are created/processed. The snippet responsible for that is this:
UFoliageInstancedStaticMeshComponent* FoliageComponent = TreeFoliageComponentMap[Entry.Key];
FTransform TrunkTransform;
Trunk->GetInstanceTransform(i, TrunkTransform, false);
FTransform FinalTransform = Entry.Value * TrunkTransform;
const int32 instanceIndex = FoliageComponent->AddInstance(FinalTransform);
FoliageComponent->SetCustomDataValue(instanceIndex, 0, FMath::FRand());
Has anyone tried this before? Am I missing something or is it not possible to do what I’m trying to do in version 5.3.2?
Any help is appreciated, thanks in advance!