Applying Custom NavModifierVolume and NavArea_Obstacle With Different Costs During Game in Unreal Engine

The solution I’d like to demonstrate allows to modify Navigation Mesh during game to allow NPC avoid high-cost areas.

This is how it looks in game (when debugging NavMesh):

Custom NavModifierVolume placed during game


The NavModifierVolume was added during a game after an NPC was killed in that area. After that the NPC does not want to come close to that area. In this video you can see how NPC behaves trying to avoid the deadly area (from 0:32 to 0:48):

By the way, modification of navigation mesh described in this article works closely with clustering that determined the deadly areas for NPC (read this article for details).

 

My Solution

In the essence, there are custom subclasses of ANavModifierVolume, UNavArea_Obstacle (each with its custom DefaultCost value), and my class UMBCG_NavSubsystem (UWorldSubsystem‘s subclass) as a manager.

During a game an instance of a custom NavModifierVolume is spawned with a custom NavArea_Obstacle specified and with the required dimensions. Thus, it modifies a NavMesh adding an area (NavMesh polygons) with a higher cost.

Classes:

  • UMBCG_NavSubsystem: custom UWorldSubsystem class that manages custom NavModiferVolumes, spawns and destroys them.
  • AMBCG_DeathPlaceNavModifierVolume: custom ANavModifierVolume class with BoxComponent which determines the dimensions of high-cost volume.
  • UNavArea_Obstacle_TierXX_MBCG: custom UNavArea_Obstacle classes (where XX is from 01 to 08). Each of UNavArea_Obstacle_TierXX_MBCG determines a different DefaultCost.

Here is the structure of the solution:

C++ code

Here is the repository where you can find the mentioned custom classes.

See:

  • MBCG_NavSubsystem.h
  • MBCG_DeathPlaceNavModifierVolume.h
  • NavArea_Obstacle_Tier01_MBCG.h, NavArea_Obstacle_Tier02_MBCG.h etc

Handled Challenges

  • You can’t directly change the DefaultCost value of the NavModifierVolume‘s NavArea_Obstacle (and even if you try to do so by AreaClass->GetDefaultObject(), it will result into changing DefaultCost for all corresponding NavModifierVolume objects where a particular NavArea_Obstacle class is used.).
    Instead, you need to create a new NavArea_Obstacle (or NavArea) class with the desired DefaultCost and assign that new class to the NavModifierVolume.
    This is because the AreaClass member in the ANavModifierVolume class is a reference to the class metadata, not an instance of the UNavArea class. In other words, TSubclassOf is a type-safe wrapper around a UClass*, which refers to the metadata for a class in Unreal Engine, not an instance of the class itself. Therefore, you cannot modify instance-specific properties like DefaultCost directly through AreaClass.
    Here are some code snippets for reference:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Default)
TSubclassOf<UNavArea> AreaClass;
void FAreaNavModifier::Init(const TSubclassOf<UNavAreaBase> InAreaClass)
{
	bExpandTopByCellHeight = false;
	bIncludeAgentHeight = false;
	ApplyMode = ENavigationAreaMode::Apply;
	Cost = 0.0f;
	FixedCost = 0.0f;
	Bounds = FBox(ForceInitToZero);
	SetAreaClass(InAreaClass);
}

void FAreaNavModifier::SetAreaClass(const TSubclassOf<UNavAreaBase> InAreaClass)
{
	AreaClassOb = (UClass*)InAreaClass;

	UClass* AreaClass1 = AreaClassOb.Get();
	UClass* AreaClass2 = ReplaceAreaClassOb.Get();
	bHasMetaAreas = (AreaClass1 && IsMetaAreaClass(*AreaClass1))
		|| (AreaClass2 && IsMetaAreaClass(*AreaClass2));
}

My approach to it:

So I had to create multiple custom NavArea_Obstacle classes with required different DefaultCost values. It’s worth noting that DefaultCost values can be changed for a single NavArea_Obstacle during a game. It means that you need to have as many custom NavArea_Obstacle classes as many different DefaultCost values you need to have simultaneously at any time in your game.
In my case, I created eight custom UNavArea_Obstacle classes (from UNavArea_Obstacle_Tier01_MBCG to UNavArea_Obstacle_Tier08_MBCG) which is enough for my game.