Best way to make AI avoid player character without rebuilding navmesh every frame

Hi everyone,

We want AI characters to avoid going near the player character.

Our current approach is to add a NavigationModifier component to the player, with a very high navigation cost. This works fine in terms of behavior, but it also causes the affected parts of the navmesh to be rebuilt every tick while the character is moving.

I also saw in another thread that updating navigation every frame might not be the best idea [Content removed]

So my question is: are we using the right approach here? Or is there a better way to achieve the desired behavior (AI avoids being close to the player character) without triggering navmesh rebuilds?

Thanks in advance!

You want to look at the Avoidance systems: https://dev.epicgames.com/documentation/en\-us/unreal\-engine/using\-avoidance\-with\-the\-navigation\-system\-in\-unreal\-engine They’re not perfect and have their own issues, but they generally do what you’re looking for.

Hi [mention removed],

As James mentioned, you should try using the Avoidance system, specifically the Detour Crowd feature. Detour Crowd provides agent–agent avoidance without requiring the NavMesh to be rebuilt every frame.

You can find most of the relevant settings under Project Settings → Engine → Crowd Manager. To set it up, assign a DetourCrowdAIController to your AI and register the actor(s) you want them to avoid, in this case, the player. You can also organize agents into groups to determine which ones should avoid each other.

The link James shared is the official documentation for the Avoidance system and will give you a solid overview of how it works. Let me know if this helps or if you need any additional details.

Best,

Joan

Thanks James and Joan!

We’re already using Crowd Avoidance (Detour Crowd) to prevent bots from colliding with each other, and it works fine for that purpose.

What we’re trying to achieve here is slightly different: we want AI to avoid planning paths near the player at all. In other words, we need to create a relatively large “no-go” zone around the player (around 5–10 capsule radii), so that paths are built around it rather than just relying on local avoidance when agents get close.

Crowd Avoidance handles short-range steering, but it doesn’t seem to influence the higher-level pathfinding in the way we need. Do you know if there’s a recommended approach for dynamically adding such avoidance zones around moving actors, without triggering constant NavMesh rebuilds?

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