in Ambit/Source/Ambit/Actors/Spawners/SpawnerBase.cpp [249:411]
void ASpawnerBase::SpawnActorsAtTransforms(const TArray<FTransform>& Transforms,
TMap<FString, TArray<FTransform>>& OutMap)
{
OutMap.Empty();
Random.Initialize(RandomSeed);
UWorld* World = GetWorld();
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
TMap<FString, TArray<bool>> OriginalGenerateOverlapEventsMap;
for (AActor* Actor : AllActors)
{
TArray<bool> OriginalGenerateOverlapEvents;
// Set GenerateOverlapEvents to true while Play mode is active
AmbitSpawnerCollisionHelpers::SetGenerateOverlapEventsForActor(Actor, OriginalGenerateOverlapEvents);
// Store original overlap event settings
OriginalGenerateOverlapEventsMap.Add(Actor->GetName(), OriginalGenerateOverlapEvents);
}
// Remove duplicates from array and set up collision profiles for ActorsToSpawn
TArray<TSubclassOf<AActor>> ActorsToSpawnClean;
TMap<FString, TArray<FCollisionResponseTemplate>> OriginalCollisionProfiles;
CleanAndSetUpActorsToSpawn(ActorsToSpawnClean, OriginalCollisionProfiles);
for (const FTransform& Transform : Transforms)
{
FVector SpawnedActorLocation = Transform.GetLocation();
const FRotator& SpawnedActorRotation = Transform.Rotator();
int32 RandomIndex = 0;
if (ActorsToSpawnClean.Num() > 1)
{
RandomIndex = Random.RandRange(0, ActorsToSpawnClean.Num() - 1);
}
// ActorsToSpawnClean will always have at least one element;
// it contains all elements of a non-empty ActorsToSpawn (with duplicates removed)
TSubclassOf<AActor> ChosenActor = ActorsToSpawnClean[RandomIndex];
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride =
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
// Try to spawn actor at location
// will do nothing if it would overlap with any other spawned actor
AActor* SpawnedActor = World->SpawnActor(ChosenActor.Get(), &SpawnedActorLocation, &SpawnedActorRotation,
ActorSpawnParams);
// Try to spawn actor again at offset
// if bAddPhysics is true and first spawn attempt failed
// in order to potentially "stack"
// TODO: If Add Physics is turned on, should AmbitSpawners spawn obstacles one at a time?
// TODO: Unreal Engine does not necessarily have a set order in which Actors are processed,
// so this may be non-deterministic
if (!IsValid(SpawnedActor) && bAddPhysics)
{
FVector LocationOffset(0, 0, 100);
SpawnedActorLocation = SpawnedActorLocation + LocationOffset;
SpawnedActor = World->SpawnActor(ChosenActor.Get(), &SpawnedActorLocation, &SpawnedActorRotation,
ActorSpawnParams);
}
if (IsValid(SpawnedActor))
{
UStaticMeshComponent* PhysicsComponent = SpawnedActor->FindComponentByClass<UStaticMeshComponent>();
if (bAddPhysics)
{
// Set mobility
PhysicsComponent->SetMobility(EComponentMobility::Movable);
// If actor could not be spawned at surface level,
// we want to sweep it to the surface and check for collision along the way
// before enabling SimulatePhysics
if (SpawnedActor->GetActorLocation() != Transform.GetLocation())
{
PhysicsComponent->SetWorldLocation(Transform.GetLocation(), true, nullptr,
ETeleportType::ResetPhysics);
}
}
// Check for any overlaps and verify that overlaps are not beyond the surface
// of the overlapping actor
TArray<UPrimitiveComponent*> OverlappingComponents;
SpawnedActor->GetOverlappingComponents(OverlappingComponents);
for (UPrimitiveComponent* OverlappingComponent : OverlappingComponents)
{
if (AmbitSpawnerCollisionHelpers::IsPenetratingOverlap(OverlappingComponent, SpawnedActor))
{
SpawnedActor->Destroy();
break;
}
}
// Makes sure that SpawnedActor did not have any overlaps
// and was not destroyed
if (IsValid(SpawnedActor))
{
// Set the collision profile(s) of this ActorToSpawn instance
// to the original collision profile(s) of the class default object
TArray<UStaticMeshComponent*> StaticMeshComponents;
SpawnedActor->GetComponents<UStaticMeshComponent>(StaticMeshComponents);
const FString& PathName = ChosenActor.Get()->GetPathName();
const TArray<FCollisionResponseTemplate> OriginalResponses = OriginalCollisionProfiles.FindChecked(
PathName);
for (int i = 0; i < StaticMeshComponents.Num(); i++)
{
UStaticMeshComponent* StaticMeshComponent = StaticMeshComponents[i];
FCollisionResponseTemplate Response = OriginalResponses[i];
// Restores the collision profile of this specific actor
// to match expected collision behavior of the asset
// Maintains ObjectType as "AmbitSpawnerObstacle"
// to ensure no overlaps occur with future spawned objects
StaticMeshComponent->SetCollisionResponseToChannels(Response.ResponseToChannels);
StaticMeshComponent->SetCollisionEnabled(Response.CollisionEnabled);
// Maintains that "Overlappable" Obstacles
// are continued to be recognized as such
// This is set to Overlap to ensure that
// actors (not spawned by AmbitSpawners)
// will respond to the spawned obstacle correctly
// if it is supposed to generate overlap events.
// The AmbitSpawner Overlap detection/destruction
// will ignore AMBIT_SPAWNER_OVERLAP typed objects.
StaticMeshComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel2, // AMBIT_SPAWNED_OVERLAP
ECR_Overlap);
}
// Turn on Simulate Physics if bAddPhysics is true
if (bAddPhysics && !PhysicsComponent->IsSimulatingPhysics())
{
PhysicsComponent->SetSimulatePhysics(true);
}
// Add FTransform to map for SDF export
SpawnedActors.Push(SpawnedActor);
TArray<FTransform> PathNameTransforms;
if (bAddPhysics)
{
PathNameTransforms.Add(PhysicsComponent->GetComponentTransform());
}
else
{
PathNameTransforms.Add(SpawnedActor->GetActorTransform());
}
// Update map array value to include new transform
if (OutMap.Find(PathName) != nullptr)
{
PathNameTransforms.Append(OutMap.FindAndRemoveChecked(PathName));
}
OutMap.Add(PathName, PathNameTransforms);
}
}
}
// Restore CDO collision profiles to original
AmbitSpawnerCollisionHelpers::ResetCollisionProfiles(OriginalCollisionProfiles, ActorsToSpawnClean);
for (const auto& Actor : AllActors)
{
TArray<bool> OriginalGenerateOverlapEvents = OriginalGenerateOverlapEventsMap.FindChecked(Actor->GetName());
// Reset GenerateOverlapEvents
AmbitSpawnerCollisionHelpers::SetGenerateOverlapEventsForActor(Actor, OriginalGenerateOverlapEvents, true);
}
}