Modify pathfinding cost - DetourNavMeshQuery subclass

First issue, DetourNavMeshQuery isn’t a class, it’s just the header file that contains many classes for Detour queries. I assume though, that the class you want to subclass is dtQueryFilter - which handles path costs for Detour. The reason why it doesn’t show up in the editor, is that none of the Recast/Detour library classes are UObject classes (except for the classes that Epic Provides for UE4).

Solution #3 shows you how to subclass dtQueryFilter, but it’s probably not your best solution - unless you’re invested into implementing your own Recast/Detour solution for UE4. However, the first two solutions might be of use to you.

Solution 1: UE4 Built-in Path Cost (Easy)

UE4 uses Navigation Areas to modify the cost of a path. In this case, you can subclass UNavArea and provide a DefaultCost and FixedAreaEnteringCost. For example (you can also subclass a blueprint from NavArea as well):

NavArea_Example.h

UCLASS()
class MYPROJECT_API UNavArea_Example : public UNavArea_Default
{
	GENERATED_BODY()

public:
	UNavArea_Example(const FObjectInitializer& ObjectInitializer);
};

NavArea_Example.cpp

UNavArea_Example::UNavArea_Example(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	DefaultCost = 2.f;
	FixedAreaEnteringCost = 10.f;
}

After this, you can then place a Nav Modifier Volume in the level where you desire and set the Area Class.

Solution 2: Modifying DefaultQueryFilter (Moderate)

Add the Navmesh module as a public dependency in your MyProject.build.cs - this will link in any of the Recast/Detour libraries to your project. Subclass ARecastNavMesh in C++ and declare the constructor. Within DefaultEngine.ini, add a section (as shown below), to tell the engine to use our Navmesh Implementation. Create a second class below that, which subclasses FRecastQueryFilter (you can use INavigationQueryFilterInterface, but if you’re extending from RecastNavmesh, there’s a lot of assumptions in the existing code that will mean a crash, i.e. casting, etc). In this second class, you can override any virtual functions to allow changes in the cost of a path. Next, declare a variable of our second class type, and define the AMyRecastNavMesh constructor. We will then set the filter implementation for the DefaultQueryFilter shared pointer in our constructor. You can see this in the following few classes:

MyProject.build.cs

using UnrealBuildTool;

public class MyProject : ModuleRules
{
	public MyProject(TargetInfo Target)
	{
        PublicDependencyModuleNames.AddRange(
            new string[] {
                "Core",
                "CoreUObject",
                "Engine",
                "InputCore",
                "Navmesh"
            }
        );

        PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

DefaultEngine.ini

[/Script/Engine.NavigationSystem]
+SupportedAgents=(Name="CustomNavmesh",NavigationDataClassName=/Script/MyProject.MyRecastNavMesh)

MyRecastNavMesh.h

#pragma once

#include "AI/Navigation/PImplRecastNavMesh.h"
#include "AI/Navigation/RecastNavMesh.h"
#include "MyRecastNavMesh.generated.h"

class MYPROJECT_API FNavFilter_Example : public FRecastQueryFilter
{
	// Override any functions from INavigationQueryFilterInterface/FRecastQueryFilter here
};

UCLASS()
class MYPROJECT_API AMyRecastNavMesh : public ARecastNavMesh
{
	GENERATED_BODY()

public:
	AMyRecastNavMesh(const FObjectInitializer& ObjectInitializer);

	FNavFilter_Example DefaultNavFilter;
};

MyRecastNavMesh.cpp

#include "MyProject.h"
#include "MyRecastNavMesh.h"

AMyRecastNavMesh::AMyRecastNavMesh(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	if (DefaultQueryFilter.IsValid())
	{
		DefaultQueryFilter->SetFilterImplementation(dynamic_cast<const INavigationQueryFilterInterface*>(&DefaultNavFilter));
	}
}

This will mean that every path finding query by default will use this solution - unless a QueryFilter has been chosen on PathFind. If you want to define a query filter, you can take a look at URecastFilter_UseDefaultArea. For example, the Move To node in Behavior Trees allow you to specify a move filter, if none is specified it will use the default (the one we’ve defined above).

Solution 3: Extending dtQueryFilter (Hard)

If you’re looking to make your own solution, you’re going to have to put on some pretty high boots. The default NavMesh path finding costs utilise the FRecastQueryFilter class (as mentioned above), which implements INavigationQueryFilterInterface and subclasses dtQueryFilter. As mentioned before, if you’re extending from ARecastNavmesh, your filtering will have to extend from FRecastQueryFilter - doing a search of the entire solution for this class will show you why (C style casts galore on INavigationQueryFilterInterface objects to get as a dtQueryFilter).

If you’re going to subclass dtQueryFilter, you will have to link the Navmesh module (as shown for our MyProject.build.cs above) to be able to subclass it and make sure to have that class implement INavigationQueryFilterInterface. To create the class, you can just make a new Header file and CPP file, then regenerate the project files.

Unfortuantely, you would then have to throw out ARecastNavmesh, and start implementing your own subclass of ANavigationData in the same fashion that ARecastNavmesh does - hence why this is the hardest option.


Hopefully these solutions are at least insightful or will be of some help! :slight_smile:

2 Likes