Closest Enemy Targeting

Hello! I currently am making a game with UE4 where i have AI players as well as 1 local player. I’m trying to make a Team Deathmatch gamemode. I’ve got everything done but i have one problem. I need my AIController class to target the nearest ‘enemy’ (the enemy pawns have teams on them) so basically the closest actor of my pawn class that isn’t on the AiController’s team. What is an elegant way of implementing this?

What i do so far is spawn all controllers and pawns in the gamemode class and store references to those on every spawned pawn. Then once one dies a delegate fires off removing the killed pawn reference from all the pawns that are still alive. Vice versa for re-spawning. Then each tick (which is Totally gross and i know this is wrong) all the pawns iterate through their respective enemy pawn references and get the distance to the referenced pawns and return whatever pawn had the closest distance.

Before asking this here. I did a lot of searches on the web for solutions to this problem but the closest i could get is a tab targeting system you can buy on in the workshop.

I’m fairly new to the entirety of developing games (started studying C++ and UE4 2 months ago). So if there is a obvious solution, I am very sorry.

Looking forward from hearing form you, thanks in advance
Ruben Versavel

Edit:

I’ve taken a new approach. I iterate through all objects of class ATank every frame and get their distance and compare it to the previous calculated distance. when its a smaller distance it sets the new pending target. This does it fine. The problem is when a tank’s hp reaches 0 it is destroyed

ATank* ATankAIController::GetClosestEnemyTank()
{
    ATank* PendingTarget = TargetTank;
    for (TObjectIterator<ATank> Itr; Itr; ++Itr) //for all tanks in the world
    {
        if (Itr->GetTeam() != Team)
        {
            if (PendingTarget == nullptr) { PendingTarget = *Itr; continue; }
            if (Itr->GetDistanceTo(PossessedTank) < PendingTarget->GetDistanceTo(PossessedTank))
            {
                PendingTarget = *Itr;
            }
        }
    }
    return PendingTarget;
}


float ATank::TakeDamage(float DamageAmount, FDamageEvent const & DamageEvent, AController * EventInstigator, AActor * DamageCauser)
{
    int32 DamagePoints = FPlatformMath::RoundToInt(DamageAmount);
    int32 DamageToApply = FMath::Clamp<int32>(DamagePoints, 0, TankCurrentHealth);

    TankCurrentHealth -= DamageToApply;

    if (TankCurrentHealth == 0) 
    { 
        OnTankDeathEvent.Broadcast(this); 
    }
    return DamageToApply;
}

void ATankAIController::OnTankDeath(AActor* TankThatDied)
{
    UnPossess();
    Spawnpoint->bInUse = false;
    Cast<ATeamDeatmatchGameMode>(GetWorld()->GetAuthGameMode())->OnRespawnRequestEvent.Broadcast(this);
    TankThatDied->Destroy();
}

The problem is there are 12 tanks in total. When one dies at that split moment where in the iteration the distance gets calculated i get an exception and the game crashes. At least that’s what i think it is…

Here are the catches of the exception by Visual Studio and crash report of Unreal.

Access violation - code c0000005 (first/second chance not available)

UE4Editor_Engine!AActor::GetDistanceTo()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\actor.cpp:4413]
UE4Editor_BattleTank_727!ATankAIController::GetClosestEnemyTank()
[f:\unreal
projects\battle-tank\battletank\source\battletank\private\tankaicontroller.cpp:70]
UE4Editor_BattleTank_727!ATankAIController::Tick()
[f:\unreal
projects\battle-tank\battletank\source\battletank\private\tankaicontroller.cpp:37]
UE4Editor_Engine!AController::TickActor()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\leveltick.cpp:408]
UE4Editor_Engine!FActorTickFunction::ExecuteTick()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\actor.cpp:134]
UE4Editor_Engine!FTickFunctionTask::DoTask()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\ticktaskmanager.cpp:273]
UE4Editor_Engine!TGraphTask::ExecuteTask()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\core\public\async\taskgraphinterfaces.h:829]
UE4Editor_Core!FNamedTaskThread::ProcessTasksNamedThread()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\core\private\async\taskgraph.cpp:665]
UE4Editor_Core!FNamedTaskThread::ProcessTasksUntilQuit()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\core\private\async\taskgraph.cpp:574]
UE4Editor_Core!FTaskGraphImplementation::WaitUntilTasksComplete()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\core\private\async\taskgraph.cpp:1355]
UE4Editor_Engine!FTickTaskSequencer::ReleaseTickGroup()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\ticktaskmanager.cpp:542]
UE4Editor_Engine!FTickTaskManager::RunTickGroup()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\ticktaskmanager.cpp:1449]
UE4Editor_Engine!UWorld::RunTickGroup()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\leveltick.cpp:770]
UE4Editor_Engine!UWorld::Tick()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\engine\private\leveltick.cpp:1429]
UE4Editor_UnrealEd!UEditorEngine::Tick()
[d:\build++ue4+release-4.19+compile\sync\engine\source\editor\unrealed\private\editorengine.cpp:1693]
UE4Editor_UnrealEd!UUnrealEdEngine::Tick()
[d:\build++ue4+release-4.19+compile\sync\engine\source\editor\unrealed\private\unrealedengine.cpp:401]
UE4Editor!FEngineLoop::Tick()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\launch\private\launchengineloop.cpp:3339]
UE4Editor!GuardedMain()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\launch\private\launch.cpp:166]
UE4Editor!GuardedMainWrapper()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\launch\private\windows\launchwindows.cpp:144]
UE4Editor!WinMain()
[d:\build++ue4+release-4.19+compile\sync\engine\source\runtime\launch\private\windows\launchwindows.cpp:223]
UE4Editor!__scrt_common_main_seh()
[f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:253]
kernel32 ntdll

If you are looking for the most “elegant” way I would strongly suggest you go with the already provided AI infrastructure in UE4. Namely Behavior Trees and Blackboards. You can also find a ton of resources on Youtube and in Documentation regarding these.

They are very flexible and highly* optimized. Keep in mind that even if you want to go “Your way” it would be greatly beneficial to check the code for these classes as that will provide insight on how they hook up to controllers and movement components.

*They are sometimes slower if you need something small and trivial.

Thank you a lot for your response! I will make sure i look into this and at the moment i get the problem solved i will mark this as the correct answer.

For your crash problem:

Can you please try using Actor Iterator instead of Object Iterator and say how it went. Actor terators are supposed to be safe.

for (TActorIterator<ATank> Itr(GetWorld()); Itr; ++Itr){...};

Happy coding :slight_smile:

Hey this might be late marked as the correct answer but i did my research into the AI infractructure that UE4 Offers like you said and i managed to get it working. The crash does not happen anymore since the behaviour trees have this passive try-catch thing going on. For those with a similar issue: I made a custom behaviour node from code and then added it inside my behaviour tree. For the closest target i used world Querying then sorting it by distance.