In my game, I have a main menu level and a gameplay level. I move between them using this function in my game instance class:
void UMyGameInstance::LoadPendingMap()
{
APlayerController* PlayerController = GetFirstLocalPlayerController();
CHECK_NULLPTR_RET(PlayerController, LogPlayerController, "MyGameInstance:: PlayerController is NULL!");
PendingMap.LoadSynchronous();
CHECK_SOFTPTR_RET(PendingMap, LogLoad, "MyGameInstance:: PendingMap is NULL!");
UGameplayStatics::OpenLevelBySoftObjectPtr(PlayerController, PendingMap);
PendingMap.Reset();
}
In my gameplay map, I have an actor that spawns other actors in BeginPlay. In order to make sure the spawned actors are placed on the correct terrain type, I run a linesweep, get the physical material from the hit, and check the surface type:
bool AMyActorSpawner::SpawnActor(FVector Location, FSpawnInfo SpawnInfo)
{
bool Result = false;
FVector TraceStart = Location;
FVector TraceEnd = FVector(TraceStart.X, TraceStart.Y, TraceStart.Z - Size.Z);
FHitResult OutHit;
FCollisionShape Sphere = FCollisionShape::MakeSphere(SpawnInfo.SweepSphereRadius);
FCollisionQueryParams Params;
Params.bReturnPhysicalMaterial = true;
bool Hit = GetWorld()->SweepSingleByChannel(OutHit, TraceStart, TraceEnd, FQuat::Identity, ECollisionChannel::ECC_Visibility, Sphere, Params);
if (Hit)
{
if (SpawnInfo.ValidSurfaces.IsEmpty() || (OutHit.PhysMaterial.IsValid() && SpawnInfo.ValidSurfaces.Contains(OutHit.PhysMaterial->SurfaceType)))
{
AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(
SpawnInfo.ActorClass.Get(),
FVector(OutHit.ImpactPoint.X, OutHit.ImpactPoint.Y, OutHit.ImpactPoint.Z + SpawnInfo.SurfaceOffset),
FRotator(0,0,0));
if (SpawnedActor != nullptr)
{
SpawnedActors.Add(SpawnedActor);
Result = true;
}
}
}
#ifdef LOCAL_DEBUG_LOGS
UE_LOG(LogTemp, Display, TEXT("%s:: Hit: %i, ValidPhysMat: %i, Result: %i"), *GetActorNameOrLabel(), Hit, OutHit.PhysMaterial.IsValid(), Result);
#endif // LOCAL_DEBUG_LOGS
return Result;
}
This works perfectly when I play the game in the editor, but when I run it as a standalone game, the landscape’s physical material is null when BeginPlay is called on the spawners, so OutHit.PhysMaterial.IsValid() returns false, which results in nothing spawning. The physical material doesn’t seem to load until a couple of seconds later. The only workaround I’ve been able to come up with is to have my game mode keep running a trace on tick until the physical material returns as valid and broadcast a multicast delegate to the spawners to trigger them to spawn, but that seems clunky. Is there a more elegant way to check whether the landscape has finished loading and setting up after loading a level?