Usage

Last updated: April 28rd, 2024

Terminology

Recast

Recast is a third-party library for automatic navmesh generation, distributed with Unreal Engine. This is the normal navigation you would be familiar with for NavWalking characters.

UNavigationSystem

A singleton class owned by the World which handles all navigation queries and building requests, delegating to the relevant NavigationData actor. It is implementation agnostic.

ANavigationData

This is an actor that is automatically spawned in the world when a NavMeshBoundsVolume is added to the level. This actor contains a specific navigation implementation. ARecastNavMesh and AFlyingNavigationData are both subclasses of ANavigationData, where ARecastNavMesh implements Recast pathfinding and AFlyingNavigationData implements a Sparse Voxel Octree for 3D pathfinding.

FNavAgentProperties

Used in Project Settings->Navigation System/Supported Agents and UNavMovementComponent/Movement Capabilities. This is a simple struct that defines an agent to the navigation system. It specifies the agent radius, height, step height and most importantly which ANavigationData actor it uses for pathfinding. To use the Flying Navigation System, set PreferredNavData to AFlyingNavigationData in your flying Pawn's movement component.

How the Navigation System chooses a NavigationData for a given pawn

The pawn must have a MovementComponent derived from UNavMovementComponent in order to be involved in AI pathfinding. This includes UPawnMovementComponent, and therefore UFloatingPawnMovement and UCharacterMovementComponent.

UNavMovementComponent has a FNavAgentProperties setting called Movement Capabilities, which determines what kind of agent the pawn is. The Radius and Height can be auto-set with the Update Nav Agent With Owners Collision option, but I wouldn't recommend this for reasons detailed below.

Setting the PreferredNavData to FlyingNavigationData will make sure the pawn uses the Flying Navigation System. However, sometimes you will want to build multiple octrees, one for a large radius and one for a small radius. In this case, you would set up multiple agents in Project Settings, each with a different radius and/or height. This will spawn multiple FlyingNavigationData actors in your level, for which you can set different MaxDetailSize etc.

Now, to choose which FlyingNavigationData to query, the agent calls:

NavSys->GetNavDataForProps(AgentProps, AsNavAgent->GetNavAgentLocation());

This function iterates through the available NavigationData actors to find the one with the closest Radius and Height.

In essence, all this means is that the first priority is PreferredNavData, then AgentRadius and AgentHeight. This is why I wouldn't recommend automatically setting the value with the Update Nav Agent With Owners Collision option. If you match up the values exactly between Project Settings and Movement Capabilities everything should work as expected.

Octree

A data structure which partitions a 3D space by recursively subdividing a cube into eight subcubes. Each node as 0 or 8 children. A Sparse Voxel Octree (SVO) is an octree which is designed to have most of its volume empty. Storage space and computation time are concentrated in the detailed areas, and not wasted in large open spaces.

Nodes and SubNodes

A node of the octree is one of those subdivisions in the diagram above. It’s easiest to visualise it as a node of the tree on the right. For memory efficiency, the Flying Navigation System also has SubNodes, due to the way it stores the smallest voxels. Each ‘Leaf node’ is actually a 4x4x4 voxel grid of SubNodes, stored in a 64-bit integer. As such, each SubNode uses 1 bit of space. Actual Voxel Size in the FlyingNavigationData properties refers to the size of these SubNodes. For more details on how this works, please see 3D Flight Navigation Using Sparse Voxel Octrees by Daniel Brewer.

Rasterization (or Rasterisation)

In general, a process that converts a perfect mathematical representation (such as an SVG image) of something into a regular grid (such as a bitmap image). In video games this usually refers to converting a scene into screen pixels, but in the Flying Navigation System rasterisation is the step in building the octree in which voxels are tested against the world geometry to check if they intersect. The building process creates a version of the scene made entirely of voxels ("Volumetric Pixels").

Async Processes

Most of the code we write for video games is run synchronously, which means its designed to run in the same process as the Tick function. If this code takes longer than a frame, then the framerate of the entire game drops. Asynchronous processes do not block the frame rate if they take too long, and therefore can be used for intensive processes and calculated in the background. All Octree building processes are asynchronous except for the visualisation. Disabling Allow Drawing in Game World will make sure there will be no drop in frame rate from dynamic rebuilding.

Pathfinding operations from AI Move To, Find Path To Actor/Location Synchronously and behaviour trees are all run on the main thread, and can cause frame rate drops with large scenes. Find Path To Actor/Location Asynchronously along with the Request Move node perform pathfinding in the background, this is the recommended approach for scenes with many octree layers (Subdivisions > 7).

Generation

For all of these properties, each time they're updated, the viewport display is also updated. For a responsive details panel, disable the Enable Drawing checkbox before changing any values.

Clear Navigation Data

Deletes SVO stored by this navigation data.

Rebuild Navigation Data

Rebuilds SVO stored by this navigation data. This button does the same thing as Build->Build Paths in the toolbar.

Stop Rebuild

Cancels rebuild of SVO.

Max Detail Size

Size in Unreal Units (cm) of the smallest details that can be captured. Display only.

Subdivisions

Number of layers the Sparse Voxel Octree will generate, including the SubNode layers. Display only.
Updated on nav bounds or MaxDetailSize change.

Actual Voxel Size

Actual side length of the smallest voxels. Display only.
Updated on nav bounds or MaxDetailSize change.

How the Subdivisions and Actual Voxel Size values are calculated

The SVO cube side length is the largest of the edges of the Nav Mesh Bounds Volume. The number of subdivisions is calculated as the lowest integer N such that (SideLength / 2^N) <= MaxDetailSize. In code, this is

Subdivisions = FMath::CeilToInt(FMath::Log2(SideLength / MaxDetailSize));

ActualVoxelSize will update to the actual (smaller) voxel size when the nav bounds or MaxDetailSize change, to indicate the size of the SubNodes.

The actual voxel side length can be calculated as the inverse operation: ActualVoxelSize = SideLength / (2^Subdivisions).
Due to the Ceiling operation, ActualVoxelSize <= MaxDetailSize.

Please note that the minimum number of subdivisions is 4, so the maximum Actual Voxel Size is SideLength / 16.

Currently Built

Shows if the cached nav data can be used for pathfinding. Display only.
Updated on rebuild, nav bounds or MaxDetailSize change.

Build on Begin Play

When using RuntimeGeneration = Dynamic, whether to build once on BeginPlay.
Useful for procedurally generated levels.

Warning!

Large performance hit if used with very small detail size or very large scene.

Multithreaded

Build on multiple threads (highly recommended). Runs 8Thread Subdivisions threads.

Thread Subdivisions

How many times to initially subdivide the generation volume. Runs each subdivision on a separate thread. Increases the allowed minimum subdivisions.

Max Threads

Maximum number of threads to spawn when rasterising the level. If set to >= available CPU threads, can lock your computer for a while.

Use Agent Radius

Whether to expand the voxel collision test by the AgentRadius (See Project Settings->Navigation System).

This is useful for making sure large agents don’t collide with objects. The building process will expand triangles along their normals to ‘inflate’ the geometry by the agent radius. However, this option should be used with caution, as you don’t want small agents to end up inside geometry.

MaxDetailSize can be a better option than Use Agent Radius

Instead of using this option, a larger MaxDetailSize is a good and fast way to navigate scenes with a large agent. Remember, you can build multiple octrees by specifying multiple agents in Project Settings->Navigation System/Supported Agents.

BSP level geometry

Utilising BSP level geometry with subtraction volumes can give inaccurate results when using Use Agent Radius, because the resulting mesh is non-watertight.

Pathfinding

The default query settings in the details panel of the FlyingNavigationData actor are used by default, if no AControllers implement the UFlyingObjectInterface.

Pathfinding Algorithm

Algorithm to use for pathfinding.
A* is the fastest, but produces jagged paths (no line-of-sight checks).
Theta* is the slowest and finds the shortest path.
Lazy Theta* is faster but less accurate than Theta* (recommended).

Allow Partial Paths

Whether to find a path despite the goal not being accessible.

Warning!

Can cause a large performance due to the algorithm searching the entirety of the available space. Not recommended.

Heuristic Scale

How much to scale the A* heuristic by. High values can speed up pathfinding, at the cost of accuracy. All three pathfinding algorithms use the standard distance heuristic to speed up processing time. This scale weights the algorithm towards or away from using the heuristic, using the following formula:

float TotalCost = TraversalCost + DistanceToGoal * HeuristicScale;

Use Unit Cost

Makes all nodes, regardless of size, the same cost. Speeds up pathfinding at the cost of accuracy (AI prefers open spaces).

By default, the cost to traverse a node will be the distance between nodes. However, this can make the algorithm spend lots of processing time in the dense areas trying to find an optimal route. By making each node cost the same, the algorithm can quickly find a path in open spaces without looking through the smaller nodes.

Use Node Compensation

Compensates node size even more, by multiplying node cost by 1 for a leaf node, and 0.2 for the root node. It uses the following formula:

TotalCost *= (1.f - LayerProportion * 0.8f);

Where LayerProportion is 0 for a leaf neighbour and 1 for the root node.

Use Pawn Centre For Path Following

Compensate path points to make flying pawns follow the path through their centre, rather than their feet.

Due to the fact the Flying Navigation System uses the existing navigation infrastructure, there are certain default behaviours for walking nav agents that are not useful for flying agents. One of these is the PathFollowingComponent of the AIController, which will move the pawn along the ‘feet’ location, defined by the Bounds.BoxExtent.Z of the UpdatedComponent of the MovementComponent. This option will translate the path up to compensate, to make sure the pawn flies through its centre.

Debug Path Color

The color used to debug draw the navigation paths (see Display/DrawDebugPaths)

Geometry

Clear Geometry Drawing

Clears lines from viewport.
Calls FlushPersistentDebugLines to remove drawn debug geometry (will clear other debug drawings).

Draw Geometry

Draw triangles the flying navigation system will use to rasterise octree. Useful for checking functionality of Use Agent Radius, which expands geometry to prevent clipping.

Display

It is recommended to test out these settings on a large max detail size, so the viewport updates quickly.

Enable Drawing

If set to true then this navigation data will be drawing itself when requested as part of "Show Navigation" (shortcut P).

Draw Octree Nodes

Whether to draw the Nodes of the Octree. This option does not apply to SubNodes.

Draw Octree SubNodes

Whether to draw the SubNodes of the Octree. This option does not apply to Nodes.

Draw Only Overlapped SubNodes

Draw only SubNodes that overlap geometry.

Colour By Connected

Colours nodes based on which are connected sections. If disabled, colours from Red to Blue based on layer (Red for root, Blue for SubNode).

Node Margin

Amount to shrink display voxel extent by, to make it easier to read (absolute measure). Can be useful to prevent flickering with high Wire Thickness.

Wire Thickness

Thickness of wire in octree visualisation, relative to box size. 1.0 is a solid box.

Warning!

Don't use as a slider because the viewport can lag if the octree is built with a small detail size. Enter a discrete value instead.

Draw Neighbour Connections

Whether to draw neighbour lines between Nodes and SubNodes. For connections between SubNodes, the Draw Only Overlapped SubNodes should be false.

Warning!

Uses slow drawing - do not use for high resolution visualisation.

Draw Simplified Connections

Whether to only draw node connections for performance and clarity. Disable to show all connections, but not recommended.

Node Centre Radius

Radius of the sphere used to indicate each node centre.

Draw Debug Paths

Draw NavPath when queried (such as when MoveTo is called). Editor only.

Allow Drawing In Game World

Allow octree visualisation in PIE or Game World. To see navigation in a game world, type ` then show Navigation (only in non-shipping builds).

Warning: Dynamic Rebuilding

Using a small detail size and hi-res level WILL cause a performance hit when rebuilding in a game world. This is what causes the performance hit, because the visualisation data gathering runs on the game thread.

UFlyingObjectInterface

By default, pathfinding queries will use the Default Query Settings of the Flying Navigation Data (set in the details panel). If you wish to override these on a per-agent basis, you can implement the UFlyingObjectInterface in the appropriate AController. It only requires one function, Get Pathfinding Query Settings, for which you can make the required structure (or split the return pin). These settings will override the defaults when this controller queries the navigation data.

Runtime Generation

There are three ways to generate and store the octree data structure.

  1. Static geometry: cached on disk and loaded into memory on level load.
  2. Static procedural geometry: Generated once at the start of the level, no caching.
  3. Manually triggered generation: Only generated when RebuildFlyingNavigation is called from Blueprints or C++.

For option 2 and 3, setting RuntimeGeneration = Dynamic is required.

DynamicModifiersOnly option

RuntimeGeneration = DynamicModifiersOnly is the same as Static for the Flying Navigation System, which doesn't support modifiers.

The Build on Begin Play option (under Generation) is probably what you want for option 2.

Manually triggering a full-level rebuild is useful for opening a new area of the map. For example, pressing a button opens a door. A rebuild can be triggered once the door is open and stopped moving, allowing AI to move through it.

This can be achieved by the Get Flying Navigation Data function in blueprints. The function takes a Pawn as reference and returns the corresponding FlyingNavigationData actor. A rebuild of the data can be called on this data (which rebuilds the nav data for the entire level), and optionally an On Flying Nav Generation Finished delegate can be set up, to handle unlocking the game.

Something like this in Blueprints:

Remember, rebuilding is done asynchronously, so the Rebuild Navigation Data node returning does not signify the completion of the build.

The Pawn should contain a movement component of some kind, with AFlyingNavigationData set in the MovementCapabilities (otherwise the function will return nullptr).

Constantly moving objects such as moving platforms are not taken into account. This is a limitation of the UE4 geometry gathering system, rather than the Flying Navigation System (Recast has this limitation too).

It is recommended that a blocking volume is placed anywhere a moving object might be, to prevent AI collision. For example, a moving platform between two points:

After building:

Network Replication

The flying navigation system uses the ANavigationData class for all pathfinding, which is a server-only class by default (bNetLoadOnClient is false). This means that all AI pathfinding queries need to be done on the server, and that trying to access the navigation system will return nullptr on a client.

You should use the HasAuthority() check before calling MoveTo or similar on the AIController, and make sure actor movement is replicated on the Pawn. If you require pathfinding access for a player controller or similar, I believe the best method is to use a Server RPC.

There is also the Allow Client Side Navigation option in Project Settings->Navigation System which will replicate the navigation system and navigation data actors to clients. However, the SVO data is not replicated because it has the potential to use a lot of bandwidth, so this option will not work. If you require a solution of this kind please open an issue on GitHub.

Checking Memory Usage

You can check roughly how much memory the octree is using by right-clicking the map in the content explorer and opening the size map. The octree memory is inside the *SELF* section, which will expand as you add more layers to the tree. It also contains other things, but at small detail sizes it can be the majority.

Turning off bCompileRecast

I was considering allowing support for the compile-time parameter bCompileRecast = false, for small mobile builds. The plugin currently piggy-backs off the Recast geometry gathering code, so I would need to duplicate much of it for the Flying Navigation System to continue to work. However, I was not able to get the engine Shipping target (even without the plugin) to compile without Recast. Please open an issue on GitHub if this is something you require.

Blueprint Functions

These functions can be called from anywhere.

Get Flying Navigation Data

Get the FlyingNavigationData actor for a given Pawn.
Will return nullptr if the Pawn's movement component specifies a different Preferred Nav Data.

Get Flying Navigation Data node

It's recommended that you use an Is Valid node to make sure the return value exists.

Rebuild All Flying Navigation

Rebuild all Flying Navigation Data agents.
To build a specific navigation data, use Get Flying Navigation Data and call Rebuild Navigation Data on it.

Rebuild All Flying Navigation node

Rebuild Navigation Data / Rebuild Flying Navigation

Rebuild cached SVO Data for the given Flying Navigation Actor. Rebuild Navigation Data will return instantly and not wait for the building to complete. Rebuild Flying Navigation will only trigger once building is completed (or cancelled).

Rebuild Navigation Data and Rebuild Flying Navigation nodes

Warning: Dynamic Rebuilding

Using a small detail size and hi-res level can cause a performance hit when rebuilding. Use with caution (and profiling). Turning off Allow Drawing In Game World helps.

Stop Rebuild

Cancels rebuild of cached Navigation Data. Will trigger Rebuild Flying Navigation completed pin, so check Is Navigation Data Built to make sure data is available. Rebuild will not overwrite data until completed.

Stop Rebuild node

Is Navigation Data Built

Checks if navigation data is available for pathfinding or raycasting.

Is Navigation Data Built node

Currently Built Voxel Size

Returns voxel size of currently built navigation data. Returns 0 if not built.

Is Navigation Data Built node

Octree Raycast

Fast raycast against the octree. Returns true if object was hit.

Octree raycast node

Draw Nav Path (Development Only)

Draw the navigation path returned by the Find Path To Actor/Location Synchronously/Asynchronously node. Path Offset will translate the path by a fixed amount. Persistent draws will persist between frames.

Draw Nav Path node

Find Path To Location Asynchronously

Finds path on separate thread. The Completed pin is triggered once a path has been found
The Pathfinding Context could be one of following: NavigationData (like FlyingNavigationData actor), Pawn or Controller. This parameter determines which navigation data actor is chosen and allows override of SVO Query Settings (see UFlyingObjectInterface).

Find Path To Location Asynchronously

Find Path To Actor Asynchronously

Finds path on separate thread. The Completed pin is triggered once a path has been found
Main advantage over Find Path To Location Asynchronously is that the resulting path will automatically get updated if the goal actor moves more than Tether Distance away from last path node. Updates when the Goal Actor moves are also asynchronous, but only when doing Flying pathfinding (Recast query updates are processed in the usual fashion). The Pathfinding Context could be one of following: NavigationData (like FlyingNavigationData actor), Pawn or Controller. This parameter determines which navigation data actor is chosen and allows override of SVO Query Settings (see UFlyingObjectInterface).

Find Path To Location Asynchronously

This can be used in a setup similar to the following:

Find Path To Actor Asynchronously use case

On BeginPlay, the actor tries to find a path to the player. Before the pathfinding completes, BeginPlay exits and the level loads normally. The Tick event runs every frame, but doesn't do anything because the Found Path variable is invalid. Once the pathfinding is finished, the Completed pin triggers, and saves the path in Found Path. Tick can now run Draw Nav Path each frame, and as the player moves the path will update periodically without stalling the game thread.

Pathfinding Context

When using the Find Path To Actor/Location Synchronously/Asynchronously family of nodes, to prevent bugs it’s a good idea to use the controlled pawn (GetControlledPawn from a Controller) or NavigationData as the Pathfinding Context. The Pathfinding Context is used to choose the correct navigation data. This is only important if you have multiple agents defined in Project Settings.

Find Path To Actor Synchronously node with controlled pawn

The Pathfinding Context is also important for providing custom FSVOQuerySettings. Pathfinding Context can implement the FFlyingObjectInterface to override the default settings.

Get Pathfinding Result

Takes a path output from Find Path To Actor/Location Synchronously/Asynchronously and returns if it was invalid, an error, fail or success:
Invalid: If start or end point is blocked or out of bounds.
Error: Algorithm got stuck in infinite loop.
Fail: If start and end points are not connected and partial paths are not enabled.
Success: Path is valid.

Get Pathfinding Result

For example, to switch on the result:

Get Pathfinding Result use case

Request Move

Takes a path output from Find Path To Actor/Location Synchronously/Asynchronously and requests an AIController to follow it.

Request Move

For example, to make a pawn find the player when the level starts:

Request Move use case

Smooth Path

Smooths navigation path (from Find Path To Actor/Location Synchronously/Asynchronously) by constructing a centripetal Catmull-Rom Spline and sampling points at equidistant intervals.

The Sample Length is the distance between samples along the path in Unreal units.

Request Move

Smoothing looks like this:

Request Move use case

Generated using this graph:

Request Move use case

CatmullRomSpline Object

A Catmull-Rom Spline object can be created and sampled at will using the Make Catmull Rom Spline node. Path Points must have at least 2 points.

This can be used to move objects along splines manually, without sampling at intervals and passing to Request Move.

Request Move

C++ API Reference

If you're building custom query functionality, its a good idea to check out the source code to see how everything works.

If you downloaded the plugin from the Marketplace, the source will be found at
C:\Program Files\Epic Games\UE_4.XX\Engine\Plugins\Marketplace\FlyingNavSystem\Source\FlyingNavSystem
for Windows and
/Users/Shared/Epic Games/UE_4.XX/Engine/Plugins/Marketplace/FlyingNavSystem/Source/FlyingNavSystem
for macOS. You should know where it is if you built for Linux 😜.

Here I have put together an easy reference of the most common classes. If you're doing custom querying, you should call GetSVOData() on the AFlyingNavigationData actor, which will return a reference to a FSVOData struct. You should save it in a FSVODataRef if you're holding on to it for a while. This will give you access to the underlying octree data (Layers, Nodes and SubNodes).

There is also the FSVOPathfindingGraph struct for the underlying pathfinding algorithm, which can be accessed from AFlyingNavigationData::GetSyncPathfindingGraph() on the game thread.

In SVOGraph.h


FSVOGraph struct: Defines neighbour connections, accessed through FSVOPathfindingGraph.Graph
struct FSVOGraph
{
	typedef FSVOLink FNodeRef;

	FSVOGraph(const FSVOData& InNavigationData);

    // Number of neighbours in a given direction. Not trivial, but faster than GetNeighbours
	int32 NumNeighbours(const int32 Direction, const FNodeRef NeighbourRef) const;

	// Adds all neighbours on a given face of a node
	// Direction is the index into FSVOGenerator::Delta_ neighbour directions, 0 <= Direction < 6
	void SubdivideNeighbours(const int32 Direction, const FNodeRef NeighbourRef, TArray<FNodeRef>& Neighbours) const;

	// Returns a link to the neighbour of a leaf node in a given direction
	FSVOLink GetLeafNeighbour(const FIntVector& LeafPos, const FSVONode& LeafParent, const int32 Direction) const;

    // Returns complete list of all adjacent nodes of a given node ref
	void GetNeighbours(const FNodeRef NodeRef, TArray<FNodeRef>& Neighbours) const;

	// Returns directions in 26 DOF that are available. Used for 'projecting' points to free space. AgentPosition is used for sorting directions by connected components.
	void GetAvailableDirections(const FVector& Position, const FVector& AgentPosition, TArray<FDirection>& Directions) const;

	// Returns whether given node identification is correct
	static bool IsValidRef(FNodeRef NodeRef);

	// Returns number of neighbours that the graph node identified with NodeRef has (DO NOT USE, inefficient. Use GetNeighbours)
	int32 GetNeighbourCount(FNodeRef NodeRef) const;

	// Returns neighbour ref (DO NOT USE, inefficient. Use GetNeighbours)
	FNodeRef GetNeighbour(const FNodeRef NodeRef, const int32 NeighbourIndex) const;
};

FSVOPathfindingGraph struct:
struct FSVOPathfindingGraph : FGraphAStar<FSVOGraph, FGraphAStarDefaultPolicy, FGraphAStarDefaultNode<FSVOGraph>>
{
	FSVOPathfindingGraph(const FSVOGraph& InGraph);


	void UpdateNavData(const FSVOData& InNavigationData) const;

	/**
	* Single run of pathfinding loop: get node from open set and process neighbors
	* returns true if loop should be continued
	*/
	bool ProcessSingleAStarNode        (const FGraphNodeRef EndNodeRef, const bool bIsBound, const FSVOQuerySettings& Filter, int32& OutBestNodeIndex, float& OutBestNodeCost);
	bool ProcessSingleThetaStarNode    (const FGraphNodeRef EndNodeRef, const bool bIsBound, const FSVOQuerySettings& Filter, int32& OutBestNodeIndex, float& OutBestNodeCost);
	bool ProcessSingleLazyThetaStarNode(const FGraphNodeRef EndNodeRef, const bool bIsBound, const FSVOQuerySettings& Filter, int32& OutBestNodeIndex, float& OutBestNodeCost);

	/**
	*	Performs the actual search.
	*	@param StartNodeRef - Link to the first node
	*	@param EndNodeRef - Link to the destination node
	*	@param Filter - Filter to determine heuristics, edge costs etc
	*	@param [OUT] OutPath - on successful search contains a sequence of graph nodes representing
	*		solution optimal within given constraints
	*/
    EGraphAStarResult FindSVOPath(const FGraphNodeRef StartNodeRef, const FGraphNodeRef EndNodeRef, const FSVOQuerySettings& Filter, TArray<FGraphNodeRef>& OutPath);

	// Find a path from StartLocation to EndLocation through the Sparse Voxel Octree
	ENavigationQueryResult::Type FindPath(const FVector& StartLocation, const FVector& EndLocation, const FSVOQuerySettings& QueryFilter, TArray<FNavPathPoint>& PathPoints, bool& bPartialSolution);
    // Version without bPartialSolution for convenience
	ENavigationQueryResult::Type FindPath(const FVector& StartLocation, const FVector& EndLocation, const FSVOQuerySettings& QueryFilter, TArray<FNavPathPoint>& PathPoints);
};

If you like the Flying Navigation System

Please consider leaving a review on the Unreal Marketplace

Leave Review

Thank you for your support