GetAllActorsOfClass not working as expected in BeginPlay

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?

Are you spawning in the collected actors dynamically in the level through a blueprint or are they placed within the level from within the editor?

Are you streaming in any assets or just using OpenLevel to load the level

Could a specific level have it’s game mode overridden by accident?

  • All targets are placed in the level via the editor before runtime, no dynamic spawning at the moment
  • As far as I am aware I am just using OpenLevel. All targets & the player controller object are made using basic cube/sphere/cone meshes and collision components.
  • Maybe, but whenever the levels swap, the debugger prints the “Hello World, this is Overlord!” line, indicating it is running the correct game mode each time…

Try adding a delay node / timer with delay before getting all of the actors.

Begin play != all actors spawned.

Manage your references, rather than trying to get all actors of class, make the targets add themselves into some array in GameMode or some other class in their begin play.

1 Like

This does the trick! Just moved the add-to-array and OnDestroyed logic from the GetAllActors loop in the game mode BeginPlay into Target’s BeginPlay and viola! Curious though, how is the game mode always garunteed to be spawned in when the Targets run their BeginPlays? With my old implementation working some of the time, it’d lead me to believe some actors would run that code before the GameMode was set up, but that doesn’t appear to be the case now…

Indeed they are not! I just wanted to see if there was a solution that didn’t involve a delay timer, as that could grow cumbersome when scaling the project up to include many more Targets.