How to rebuild navigation instantly (Turn based strategy)

I have a turn based strategy game and I want units to block other units, enemy and ally alike. I tried using RVO Avoidance for a while, but it created a lot of problems. 1) Units could often end up overlapping each other. 2) When I would build a path to an enemy they would not end up where originally predicted, because the RVO Avoidance would push them just as they were finishing moving the path. 3) In some cases it looked like the RVO Avoidance was completely stopping AI from moving since they were overlapping with too many other AIs. 4) It doesn’t allow the AIs to block door ways and such.

So my current solution is having the characters by dynamic obstacles with CanEverAffectNavigation enabled (I noticed a weird thing where CanEverAffectNavigation and or DynamicObstacle are set to false by default even if you set them to true in the character blueprint, so I changed them to be true in the BeginPlay of my C++ class.)

The problem is that the character now blocks their own navigation. There doesn’t seem to be any way, that I could find, to tell the navigation / pathfinding to ignore a specific obstacle / actor. I could make it ignore all the actors with the same NavArea class, but that wouldn’t help my problem.

Even though it doesn’t make the pathfinding fail completely, it’s still affecting the pathfinding in an undesired way. Path from the selected unit often start from incorrect angles. Paths to enemies usually approach from incorrect angles. If there was multiple AIs in the area it might break navigation completely.

The optimal solution would be to be able to tell the navigation system: “Ignore these two actors when building this path.” I would need this to work with UNavigationSystem::FindPathToLocationSynchronously, AAIController::MoveToLocation, and AAIController::MoveToActor. I was hoping the UNavigationQueryFilter would somehow allow me to do this, but there doesn’t seem to be any way.

So the solution I am using now disabling CanEverAffectNavigation for the unit I am selecting as well as the enemy I am targeting. This works fine for the player turn, mostly. When I select the enemy it will first show the “old” path. The one with the incorrect approach angle. It hasn’t finished building the navigation and it will still use the old navigation data. This isn’t a huge problem during the player turn, since the player can just wait for the path finding to finish to see where they can actually move, but it’s a problem during the AI turn.

During the AI Enemy turn I need the AI to select their enemy based on who they can get to. I don’t want them to select a player units behind a locked door for example, or a player units that are blocked by another enemy/player unit in the door way. I need to go through all of the player units. First, I check if they are even in the radius or if they can see the player unit, but I still need to check the pathfinding. So, I call SetCanEverAffectNavigation() on all the player units disabling and enabling them affecting navigation and rebuilding the navigation every time. This might be kind of expensive, but it might not be a problem since there’s only one AI doing this at any given time.

The problem is that I haven’t figured out how to rebuild navigation data instantly. I have switched Data Gathering Mode to Instant. I have tried changing DirtyAreasUpdateFreq to 0. I have tried turning on “Do Fully Async Nav Data Gathering” though if I understand, that should be the opposite of what I want. I have tried calling the Tick, Build, OnNavigationBoundsUpdate functions on UNavigationSystem, but nothing seems to work. If I have “show navigation” turned on, the areas still update to green in the same rate as they did before. Even if this visual presentation is something that is not up to date at every given time, it’s still showing the incorrect path to the enemy when I select an enemy unit for that one frame.

TLDR: 1) How do I rebuild navigation data instantly (in an area), or 2) How do I make pathfinding ignore specific actors / obstacles.

Okay, I think I figured out how to update navigation data instantly. I am not sure if this is the correct way to do it. I feel like there should be some kind of project setting for this, but I just can’t find it or I am using the settings incorrectly. Alternatively, I still hope there was a way to just set the pathfinding to ignore specific actors.

//=================================================================
// 
//=================================================================
void ALevelSpawner::RebuildNavigation()
{
	UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld());
	if (NavSys)
	{
		NavSys->SetGeometryGatheringMode(ENavDataGatheringModeConfig::Instant);
		
		//Make sure all dirty areas are instantly updated
		NavSys->Tick(1.0f / NavSys->DirtyAreasUpdateFreq);

		for (int32 i = 0; i < NavSys->NavDataSet.Num(); i++)
		{
			NavSys->NavDataSet[i]->EnsureBuildCompletion();
		}
	}
}

Calling Tick with the correct delta time ensures the navigation data is being marked to rebuild in dirty areas. Calling EnsureBuildCompletion makes sure that it’s finished. I tried the code with just the Tick and with just EnsureBuildCompletion and neither worked alone, you need both.

Again, this might be more expensive if you do it a whole lot during one frame, but since this is a turn based strategy game I am doing it should be fine. Hope this helps someone else that might have the same problem.

1 Like