Hi [mention removed],
Sorry for the delay.
One approach I suggest is to calculate a set of points around the player to define the area you don’t want the AI to enter. Once you have those candidate points, you can use the Unreal Navigation System to test them and let it calculate the best path to the destination goal.
UNavigationSystemV1::ProjectPointToNavigation can help you “translate” your world-space points onto the NavMesh. You can access the navigation system through UNavigationSystemV1.
With the projected points, you can build FPathFindingQuery objects to evaluate paths around the player. This way, you ensure that the AI always moves around the player without entering the restricted radius.
Custom quick implementation of the idea:
bool AMyAIController::PickViaPointAwayFromPlayer(
UWorld* World, const FVector& Start, const FVector& Goal,
const FVector& PlayerLoc, float AvoidRadius, float RingPadding,
FVector& OutVia)
{
UNavigationSystemV1* NavSys = UNavigationSystemV1::GetCurrent(World);
ANavigationData* NavData = NavSys ? NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate) : nullptr;
if (!NavSys || !NavData) return false;
const float RingR = AvoidRadius + RingPadding;
const int Samples = 12; // 30° steps
float BestCost = TNumericLimits<float>::Max();
bool bFound = false;
for (int i = 0; i < Samples; ++i)
{
const float Angle = i * (2.f * PI / Samples);
const FVector Cand = PlayerLoc + FVector(FMath::Cos(Angle), FMath::Sin(Angle), 0.f) * RingR;
FNavLocation NavCand;
if (!NavSys->ProjectPointToNavigation(Cand, NavCand, FVector(100.f))) continue;
FSharedConstNavQueryFilter Filter =
UNavigationQueryFilter::GetQueryFilter(*NavData, this, UNavigationQueryFilter::StaticClass());
FPathFindingQuery Q1(nullptr, *NavData, Start, NavCand.Location, Filter);
FPathFindingQuery Q2(nullptr, *NavData, NavCand.Location, Goal, Filter);
FPathFindingResult R1 = NavSys->FindPathSync(Q1);
FPathFindingResult R2 = NavSys->FindPathSync(Q2);
if (!R1.IsSuccessful() || !R2.IsSuccessful()) continue;
FVector::FReal Len1 = 0.f, Len2 = 0.f;
if (NavSys->GetPathLength(Start, NavCand.Location, Len1, NavData, Filter) == ENavigationQueryResult::Success &&
NavSys->GetPathLength(NavCand.Location, Goal, Len2, NavData, Filter) == ENavigationQueryResult::Success)
{
const float Cost = Len1 + Len2;
if (Cost < BestCost)
{
BestCost = Cost;
OutVia = NavCand.Location;
bFound = true;
}
}
}
return bFound;
}
You could also achieve a similar result using EQS. Instead of generating the points yourself, you can let the EQS Donut generator create them around the player, then apply the same pathfinding logic.
Other than these two solutions, I don’t see many alternatives. I did consider UNavArea, but moving NavAreas around a character will trigger NavMesh tile rebuilds.
I hope the first solution helps you move forward. Let me know if it works for your case, or if you’d like me to investigate further.
Best,
Joan