I have a super simplified method of tracking when levels should swap in my game protoype, set up in the GameModeBase subclass that all levels use.
void AOverlordGameModeBase::BeginPlay()
{
Super::BeginPlay();
check(GEngine != nullptr);
// Display a debug message for five seconds
// The -1 "Key" value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Hello World, this is Overlord!"));
ChangeMenuWidget(StartingWidgetClass);
// determine which level we are starting on
LevelIterator = 0;
for (UINT8 i = 0; i < Levels.Num(); i++) {
FName LevelName = *GetLevel()->GetOuter()->GetName();
if (Levels[i] == LevelName) {
LevelIterator = i;
break;
}
}
// grab all targets in level
TArray<AActor*> TargetsInLevel;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ATarget::StaticClass(), TargetsInLevel);
// iterate through targets to find hostiles
for (AActor* Target : TargetsInLevel) {
// cast back to target
ATarget* CastedTarget = Cast<ATarget>(Target);
// if hostile, add to member array and bind OnDestroy to our custom handler
if (CastedTarget->Hostile) {
HostileTargets.Add(CastedTarget);
CastedTarget->OnDestroyed.AddDynamic(this, &AOverlordGameModeBase::HostileDestroyed);
}
}
}
void AOverlordGameModeBase::HostileDestroyed(AActor* DestroyedActor)
{
// cast back to target
ATarget* CastedTarget = Cast<ATarget>(DestroyedActor);
// remove from array
HostileTargets.Remove(CastedTarget);
// check if level "complete"
if (HostileTargets.IsEmpty()) {
// iterate level iterator
LevelIterator++;
// temp implementation loops back to first level if at end of array
if (LevelIterator >= Levels.Num()) {
LevelIterator = 0;
// handle edge case of no stored levels
if (LevelIterator == Levels.Num()) {
return;
}
}
// open level with given name at index pointed to by level iterator
UGameplayStatics::OpenLevel(GetWorld(), Levels[LevelIterator]);
}
}
Now, the first time I start one of my levels, it works just as expected, all Target objects are collected and checked for hostile status. But when I come back around to that level a second time, it seems that GetAllActorsOfClass is missing some or all of the targets that are in the level when I start playing. This prevents my level swapping logic from working as without knowledge of the targets that need to be destroyed the level can end prematurely or in the case of finding none of the targets, it will never end. I’ve tried deleting and replacing all the targets in the level, but doing that seems to result in a different number of targets being found upon revisiting each time (ie when I placed 5 targets, it found none, then i deleted them all and placed 3 and it only found 2, then I deleted all and placed 3 again and it found 1???) I have another level with targets that manages to find them all every time, but both are filled with targets in the editor (ie not at runtime) and the only other difference is that the problem level has some terrain I threw down to experiment with, but I can’t imagine that would make any difference?
Best I can figure is that OverlordGameModeBase is firing BeginPlay/GetAllActorsOfClass before all of the target objects can be found, but I don’t know why that is the case, and why it is the case with only one of my levels. Is there a way to resolve this without relying on some arbitrary delay timer? Is there some variant of OnLevelLoaded that I can check to make sure all assets are there before performing my Get call?