void ASpawnerBase::SpawnActorsAtTransforms()

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);
    }
}