I first spawn the actor far away outside of the level tile. Then I can get the bounds (size of the actor) and use it to check for empty space to place it. If an empty space is found I move the actor to the location just found.
bool ATile::FindEmptyLocation(FVector& OutLocation, float Radius)
{
FBox SpawnBox;
SpawnBox.Min = { 400, 1600, -165 };
SpawnBox.Max = { 3600, -1600, -165 };
FVector SpawnPoint;
const int MAX_ATTEMPTS = 100;
int Counter = 0;
do
{
Counter++;
OutLocation = FMath::RandPointInBox(SpawnBox) + GetActorLocation();
} while (IsLocationBlocked(OutLocation, Radius) && Counter <= MAX_ATTEMPTS);
return Counter <= MAX_ATTEMPTS;
}
bool ATile::IsLocationBlocked(FVector Location, float Radius)
{
FHitResult HitResult;
bool bHit = GetWorld()->SweepSingleByChannel(HitResult, Location, Location, FQuat::Identity, ECollisionChannel::ECC_GameTraceChannel2, FCollisionShape::MakeSphere(Radius));
return bHit;
}
void ATile::PlaceActors(TSubclassOf<AActor> ToSpawn, int MinSpawn, int MaxSpawn)
{
int NumberToSpawn = FMath::RandRange(MinSpawn, MaxSpawn);
for (size_t i = 0; i < NumberToSpawn; i++)
{
// Spawn actor outside of the Tile
FRotator Rotation = FRotator::ZeroRotator;
Rotation.Yaw = FMath::RandRange(0.0f, 360.0f);
AActor* Spawned = GetWorld()->SpawnActor<AActor>(ToSpawn, FVector(0, 5000, 0), Rotation);
// Get Radius for Sphere sweep
FVector Origin;
FVector BoxExtent;
Spawned->GetActorBounds(true, Origin, BoxExtent);
// Find empty place and move actor there, destroy actor if no solution found in MAX_ATTEMPTS
FVector SpawnPoint;
if (FindEmptyLocation(SpawnPoint, BoxExtent.Size()))
{
Spawned->SetActorLocation(SpawnPoint);
Spawned->AttachToActor(this, FAttachmentTransformRules(EAttachmentRule::KeepWorld, false));
}
else
{
Spawned->Destroy();
}
}
}