In UAbilityTask_SpawnActor::BeginSpawningActor, there is a SpawnedActor passed in by reference. Each time this function is called via the Spawn Actor Latent Blueprint node the same actor is passed back in that was previously spawned.
This generally has not caused an issue but if this ends up being called during World Teardown (which causes the first branch to not go into as ShouldBroadcastAbilityTaskDelegates return false), then the flow of code will still return true and return the previous actor from before (which in this case will be stale garbage).
bool UAbilityTask_SpawnActor::BeginSpawningActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, TSubclassOf<AActor> InClass, AActor*& SpawnedActor)
{
if (Ability && Ability->GetCurrentActorInfo()->IsNetAuthority() && ShouldBroadcastAbilityTaskDelegates())
{
UWorld* const World = GEngine->GetWorldFromContextObject(OwningAbility, EGetWorldErrorMode::LogAndReturnNull);
if (World)
{
SpawnedActor = World->SpawnActorDeferred<AActor>(InClass, FTransform::Identity, NULL, NULL, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
}
}
if (SpawnedActor == nullptr)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
DidNotSpawn.Broadcast(nullptr);
}
return false;
}
return true;
}
The fix appears to be just set SpawnedActor to nullptr at the top of the function. This is identical to how the other ability tasks that spawn actors work.
bool UAbilityTask_VisualizeTargeting::BeginSpawningActor(UGameplayAbility* OwningAbility, TSubclassOf<AGameplayAbilityTargetActor> InTargetClass, AGameplayAbilityTargetActor*& SpawnedActor)
{
SpawnedActor = nullptr;
...
bool UAbilityTask_WaitTargetData::BeginSpawningActor(UGameplayAbility* OwningAbility, TSubclassOf<AGameplayAbilityTargetActor> InTargetClass, AGameplayAbilityTargetActor*& SpawnedActor)
{
SpawnedActor = nullptr;
So the end result would be:
bool UAbilityTask_SpawnActor::BeginSpawningActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, TSubclassOf<AActor> InClass, AActor*& SpawnedActor)
{
SpawnedActor = nullptr; //FIX
if (Ability && Ability->GetCurrentActorInfo()->IsNetAuthority() && ShouldBroadcastAbilityTaskDelegates())
{
UWorld* const World = GEngine->GetWorldFromContextObject(OwningAbility, EGetWorldErrorMode::LogAndReturnNull);
if (World)
{
SpawnedActor = World->SpawnActorDeferred<AActor>(InClass, FTransform::Identity, NULL, NULL, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
}
}
if (SpawnedActor == nullptr)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
DidNotSpawn.Broadcast(nullptr);
}
return false;
}
return true;
}
[Attachment Removed]