Skip to content

Commit

Permalink
Update NavigationAgent doc for avoidance rework
Browse files Browse the repository at this point in the history
Updates NavigationAgent doc for avoidance rework.
  • Loading branch information
smix8 committed May 14, 2023
1 parent e7c5dd3 commit a06fc7b
Showing 1 changed file with 102 additions and 70 deletions.
172 changes: 102 additions & 70 deletions tutorials/navigation/navigation_using_navigationagents.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,145 +3,177 @@
Using NavigationAgents
======================

NavigationsAgents are helper nodes to facilitate common calls to the NavigationServer API
on behalf of the parent actor node in a more convenient manner for beginners.
NavigationsAgents are helper nodes that combine functionality
for pathfinding, pathfollowing and agent avoidance for a Node2D/3D inheriting parent node.
They facilitate common calls to the NavigationServer API on
behalf of the parent actor node in a more convenient manner for beginners.

2D and 3D version of NavigationAgents are available as
:ref:`NavigationAgent2D<class_NavigationAgent2D>` and
:ref:`NavigationAgent3D<class_NavigationAgent3D>` respectively.

NavigationsAgents are entirely optional for navigation pathfinding.
The functionality of NavigationsAgents can be recreated with scripts and direct
calls to the NavigationServer API. If the default NavigationsAgent does not do what you want
for your game feel free to design your own NavigationsAgent with scripts.
New NavigationAgent nodes will automatically join the default navigation map on the World2D/World3D.

.. warning::

NavigationsAgent nodes and NavigationServer ``agents`` are not the same.
The later is an RVO avoidance agent and solely used for avoidance.
RVO avoidance agents are not involved in regular pathfinding.
The functionality of NavigationsAgent nodes can be recreated with scripts and direct
calls to the NavigationServer API should the default NavigationsAgent
nodes not do what is required for a project and specific gameplay.

NavigationAgent Pathfinding
---------------------------

To use NavigationAgents for pathfinding, place a NavigationAgent2D/3D Node below a Node2D/3D inheriting parent node.
NavigationAgents query a new navigation path on their current navigation map when their ``target_position`` is set with a global position.

To have the agent query a path to a target position use the ``set_target_position()`` method.
Once the target has been set, the next position to follow in the path
can be retrieved with the ``get_next_path_position()`` function. Move the parent actor node
to this position with your own movement code. On the next ``physics_frame``, call
``get_next_path_position()`` again for the next position and repeat this until the path ends.
The result of the pathfinding can be influenced with the following properties.

NavigationAgents have their own internal logic to proceed with the current path and call for updates.
NavigationAgents recognize by distance when a path point or the final target is reached.
NavigationAgents refresh a path automatically when too far away from the current pathpoint.
The important updates are all triggered with the ``get_next_path_position()`` function
when called in ``_physics_process()``.
- The ``navigation_layers`` bitmask can be used to limit the navigation meshes that the agent can use.
- The ``pathfinding_algorithm`` controls how the pathfinding travels through the navigation mesh polygons in the path search.
- The ``path_postprocessing`` sets if or how the raw path corridor found by the pathfinding is altered before it is returned.
- The ``path_metadata_flags`` enable the collection of additional path point meta data returned by the path.

.. warning::

Disabling path meta flags will disable related signal emissions on the agent.

NavigationAgent Pathfollowing
-----------------------------

Be careful calling other NavigationAgent functions not required for path movement
while the actor is following a path, as many function trigger a full path refresh.
After a ``target_position`` has been set for the agent, the next position to follow in the path
can be retrieved with the ``get_next_path_position()`` function.

Once the next path position is received move the parent actor node of the agent
towards this path position with your own movement code.

.. note::

New NavigationAgents will automatically join the
default navigation map for their 2D/3D dimension.
The navigation system never moves the parent node of a NavigationAgent.
The movement is entirely in the hands of users and their custom scripts.

.. warning::
The ``get_next_path_position()`` function is responsible for updating many of the agent's internal states and properties.
The function should be repeatly called `once` every ``physics_process`` until ``is_navigation_finished()`` tells that the path is finished.
The function should not be called after the target position or path end has been reached
as it can make the agent jitter in place due to the repeated path updates.
Always check very early in script with ``is_navigation_finished()`` if the path is already finished.

Resetting the path every frame (by accident) might get the actor to stutter or spin around in place.
NavigationAgents have their own internal logic to proceed with the current path and call for updates.

NavigationAgents were designed with ``_physics_process()`` in mind to keep in sync with both :ref:`NavigationServer3D<class_NavigationServer3D>` and :ref:`PhysicsServer3D<class_PhysicsServer3D>`.
The following properties influence the pathfollowing behavior.

They work well out of the box with :ref:`CharacterBody2D<class_CharacterBody2D>` and :ref:`CharacterBody3D<class_CharacterBody3D>` as well as any rigid bodies.
- The ``path_desired_distance`` defines the distance at which the agent advances its internal path index to the next path position.
- The ``target_desired_distance`` defines the dinstance at which the agent considers the target position to be reached and the path at its end.
- The ``path_max_distance`` defines when an agent requests a new path cause it was moved to far away from the current path point segment.

.. warning::
The important updates are all triggered with the ``get_next_path_position()`` function
when called in ``_physics_process()``.

The important restriction for non-physics characters is that the NavigationAgent node only accepts a single update each ``physics_frame`` as further updates will be blocked.
NavigationAgents can be used with ``process`` but are still limited to a single update that happens in ``physics_process``.

.. warning::
Script examples for various nodes commonly used with NavigationAgents can be found further below.

Pathfollowing common problems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If a NavigationAgent is used with ``_process()`` at high framerate make sure to accumulate the values of multiple frames and call the NavigationAgent function only once each ``physics_frame``.
There are some common user problems and important caveats to consider when writing agent movement scripts.

.. _doc_navigation_script_templates:
- The path is returned empty
If an agent queries a path before the navigation map synchronisation, e.g. in a _ready() function, the path might return empty. In this case the get_next_path_position() function will return the same position as the agent parent node and the agent will consider the path end reached. This is fixed by making a deferred call or using a callback e.g. waiting for the navigation map changed signal.

- The agent is stuck dancing between two positions
This is usually caused by very frequent path updates every single frame, either deliberate or by accident (e.g. max path distance set too short). The pathfinding needs to find the closest position that are valid on navigation mesh. If a new path is requested every single frame the first path positions might end up switching constantly in front and behind the agent's current position, causing it to dance between the two positions.

- The agent is backtracking sometimes
If an agent moves very fast it might overshoot the path_desired_distance check without ever advancing the path index. This can lead to the agent backtracking to the path point now behind it until it passes the distance check to increase the path index. Increase the desired distances accordingly for your agent speed and update rate usually fixes this as well as a more balanced navigation mesh polygon layout with not too many polygon edges cramped together in small spaces.

- The agent is sometimes looking backwards for a frame
Same as with stuck dancing agents between two positions, this is usually caused by very frequent path updates every single frame. Depending on your navigation mesh layout, and especially when an agent is directly placed over a navigation mesh edge or edge connection, expect path positions to be sometimes slightly "behind" your actors current orientation. This happens due to precision issues and can not always be avoided. This is usually only a visible problem if actors are instantly rotated to face the current path position.

NavigationAgent Avoidance
-------------------------

This section explains how to use the built-in avoidance specific
to NavigationAgent nodes. For general avoidance use and more technical details
on RVO avoidance see :ref:`doc_navigation_using_agent_avoidance`.
on agent avoidance system see :ref:`doc_navigation_using_agent_avoidance`.


In order for NavigationAgents to use the avoidance feature the ``enable_avoidance`` property must be set to ``true``.

.. image:: img/agent_avoidance_enabled.png

The velocity_computed signal of the NavigationAgent node must be connected to receive the ``safe_velocity`` calculation result.

.. image:: img/agent_safevelocity_signal.png

In order to trigger the avoidance velocity calculation the current velocity of the agent's parent node must be set with ``set_velocity()`` on the NavigationAgent node in ``_physics_process()``.

After a short wait for processing the avoidance (still in the same frame) the ``safe_velocity`` vector will be received with the signal.
This velocity vector should be used to move the NavigationAgent's parent node in order to avoidance collision with other avoidance using agents or avoidance obstacles.

.. note::

Only other agents on the same map that are registered for avoidance themself will be considered in the avoidance calculation.

The following NavigationAgent properties are relevant for avoidance:

- The property ``radius`` controls the size of the avoidance circle around the agent. This area describes the agents body and not the avoidance maneuver distance.
- The property ``height`` is available in 3D only. The height together with the current global y-axis position of the agent determines the vertical placement of the agent in the avoidance simulation. Agents using the 2D avoidance will automatically ignore other agents or obstacles that are below or above them.
- The property ``radius`` controls the size of the avoidance circle, or in case of 3D sphere, around the agent. This area describes the agents body and not the avoidance maneuver distance.
- The property ``neighbor_distance`` controls the search radius of the agent when searching for other agents that should be avoided. A lower value reduces processing cost.
- The property ``max_neighbors`` controls how many other agents are considered in the avoidance calculation if they all have overlapping radius.
A lower value reduces processing cost but a too low value may result in agents ignoring the avoidance.
- The property ``time_horizion`` controls the avoidance maneuver start and end distance.
How early and for how long an agents reacts to other agents within the ``neighbor_distance`` radius to correct its own velocity.
A lower value results in avoidance kicking in with a very intense velocity change at a short distance while a high value results in very early but small velocity changes.
- The property ``max_speed`` controls the maximum velocity assumed for the agents avoidance calculation.
- The properties ``time_horizon_agents`` and ``time_horizon_obstacles`` control the avoidance prediction time for other agents or obstacles in seconds. When agents calculate their safe velocities they choose velocities that can be kept for this amount of seconds without colliding with another avoidance object. The prediction time should be kept as low as possible as agents will slow down their velocities to avoid collision in that timeframe.
- The property ``max_speed`` controls the maximum velocity allowed for the agents avoidance calculation.
If the agents parents moves faster than this value the avoidance ``safe_velocity`` might not be accurate enough to avoid collision.
- The property ``use_3d_avoidance`` switches the agent between the 2D avoidance (xz axis) and the 3D avoidance (xyz axis) on the next update.
Note that 2D avoidance and 3D avoidance run in separate avoidance simulations so agents split between them do not affect each other.
- The properties ``avoidance_layers`` and ``avoidance_mask`` are bitmasks similar to e.g. physics layers. Agents will only avoid other avoidance objects that are on an avoidance layer that matches at least one of their own avoidance mask bits.
- The ``avoidance_priority`` makes agents with a higher priority ignore agents with a lower priority. This can be used to give certain agents more importance in the avoidance simulation, e.g. important npcs characters, without constantly changing their entire avoidance layers or mask.

The ``velocity_computed`` signal of the agent node must be connected to receive the ``safe_velocity`` calculation result.

.. image:: img/agent_safevelocity_signal.png

Additional the current velocity of the agents parent must be set for the agent in ``_physics_process()`` with ``set_velocity()``.

After a short wait for processing the avoidance (still in the same frame) the ``safe_velocity`` vector will be received with the signal.
This velocity vector should be used to move the NavigationAgent's parent node in order to avoidance collision with other avoidance registered agents in proximity.

RVO exists in its own space and has no information from navigation meshes or physics collision.
Behind the scene avoidance agents are just circles with different radius on a flat plane.
In narrow places obstructed with collision objects, the avoidance maneuver radius needs to be
reduced considerably or disabled else the avoidance velocity will get actors stuck on collision easily.
Avoidance exists in its own space and has no information from navigation meshes or physics collision.
Behind the scene avoidance agents are just circles with different radius on a flat 2D plane or spheres in an otherwise empty 3D space.
NavigationObstacles can be used to add some environment constrains to the avoidance simulation, see :ref:`doc_navigation_using_navigationobstacles`.

.. note::

Avoidance should be seen as a last resort option for constantly moving objects that cannot be re(baked) to a navigationmesh efficiently in order to move around them.

.. warning::

Actors that move according to their avoidance agent velocity will not move at
full speed, can leave the navigation mesh bounds and can make movement
pauses when the avoidance simulation becomes unsolvable.
Avoidance does not affect the pathfinding. It should be seen as an additional option for constantly moving objects that cannot be re(baked) to a navigation mesh efficiently in order to move around them.

Using the NavigationAgent ``enable_avoidance`` property is the preferred option
to toggle avoidance but the following scripts for NavigationAgents can be
used to create or delete avoidance callbacks for the agent RID.
to toggle avoidance. The following code snippets can be used to
toggle avoidance on agents, create or delete avoidance callbacks or switch avoidance modes.

.. tabs::
.. code-tab:: gdscript GDScript

extends NavigationAgent2D

var agent: RID = get_rid()
# Enable
NavigationServer2D::get_singleton()->agent_set_callback(agent, self._avoidance_done)
# Disable
NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable())
# Enable avoidance
NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(agent, true)
# Create avoidance callback
NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, self._avoidance_done)

# Disable avoidance
NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(agent, false)
# Delete avoidance callback
NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, Callable())

.. tabs::
.. code-tab:: gdscript GDScript

extends NavigationAgent3D

var agent: RID = get_rid()
# Enable
NavigationServer3D::get_singleton()->agent_set_callback(agent, self._avoidance_done)
# Disable
NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable())
# Enable avoidance
NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(agent, true)
# Create avoidance callback
NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, self._avoidance_done)
# Switch to 3D avoidance
NavigationServer3D::get_singleton()->agent_set_use_3d_avoidance(agent, true)

# Disable avoidance
NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(agent, false)
# Delete avoidance callback
NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, Callable())
# Switch to 2D avoidance
NavigationServer3D::get_singleton()->agent_set_use_3d_avoidance(agent, false)

NavigationAgent Script Templates
--------------------------------
Expand Down

0 comments on commit a06fc7b

Please sign in to comment.