Skip to content

Commit

Permalink
Merge pull request #6049 from smix8/doc_navserver_n_navagent_n_avoida…
Browse files Browse the repository at this point in the history
…nce_4.x

Add doc for NavigationServer, NavigationAgent and RVO agent avoidance
  • Loading branch information
mhilbrunner authored Oct 11, 2022
2 parents 225c9fc + 594911c commit a8382b4
Show file tree
Hide file tree
Showing 6 changed files with 497 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tutorials/navigation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ Navigation
navigation_different_actor_locomotion
navigation_using_navigationobstacles
navigation_using_navigationmeshes
navigation_using_navigationservers
navigation_using_navigationagents
navigation_using_agent_avoidance
74 changes: 74 additions & 0 deletions tutorials/navigation/navigation_using_agent_avoidance.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.. _doc_navigation_using_agent_avoidance:

Using Agent Avoidance
=====================

This section is about how to use agent avoidance with the NavigationServer and
documents how agent avoidance is implemented in Godot.

For avoidance with NavigationAgents see :ref:`doc_navigation_using_navigationagents`.

Agent avoidance helps to prevent direct collision with other agents or moving obstacles
while the agents still follow their original velocity as best as possible.

Avoidance in Godot is implemented with the help of the RVO library (Reciprocal Velocity Obstacle).
RVO places agents on a flat RVO map and gives each agent a ``radius`` and a ``position``.
Agents with overlapping radius compute a ``safe_velocity`` from their
current ``velocity``. The ``safe_velocity`` then needs to replace the original
submitted ``velocity`` to move the actor behind the agent with custom movement code.

.. note::

RVO avoidance is not involved in regular pathfinding, it is a completely separate system.
If used inappropriately, the RVO avoidance can actively harm the perceived pathfinding quality.

Creating Avoidance Agents with Scripts
--------------------------------------

Agents and obstacles share the same NavigationServer API functions.

Creating agents on the NavigationServer is only required for avoidance but not for normal pathfinding.
Pathfinding is map and region navmesh based while avoidance is purely map and agent based.

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

extends Node3D

var new_agent_rid : RID = NavigationServer3D.agent_create()
var default_3d_map_rid : RID = get_world_3d().get_navigation_map()

NavigationServer3D.agent_set_map(new_agent_rid, default_3d_map_rid)
NavigationServer3D.agent_set_radius(new_agent_rid, 0.5)
NavigationServer3D.agent_set_position(new_agent_rid, global_transform.origin)

To receive safe_velocity signals for avoidance for the agent a callback needs to be registered on the NavigationServer.

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

extends Node3D

var agent_rid : RID = NavigationServer3D.agent_create()
var agent_node3d : Node3D = self
var callback_function_name : StringName = "on_safe_velocity_computed"
NavigationServer3D.agent_set_callback(agent_rid, agent_node3d, callback_function_name)

func on_safe_velocity_computed(safe_velocity : Vector3):
# do your avoidance movement

After the current and new calculated velocity needs to be passed to the NavigationServer each physics frame to trigger the safe_velocity callback when the avoidance processing is finished.

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

func _physics_process(delta):

NavigationServer3D.agent_set_velocity(current_velocity)
NavigationServer3D.agent_set_target_velocity(new_velocity)

.. warning::

If _process() is used instead of _physics_process() at a higher framerate
than physics the agent velocity should not be updated more than ones each
physics frame e.g. by tracking the Engine.get_physics_frames().
231 changes: 231 additions & 0 deletions tutorials/navigation/navigation_using_navigationagents.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
.. _doc_navigation_using_navigationagents:

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.

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.

.. 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.

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

To use NavigationAgents for pathfinding, place a NavigationAgent2D/3D Node below a Node2D/3D inheriting parent node.

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

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_location()`` function
when called in ``_physics_process()``.

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.

.. note::

New NavigationAgents will automatically join the
default navigation map for their 2D/3D dimension.

.. warning::

Resetting the path every frame (by accident) might get the actor to stutter or spin around in place.

NavigationAgents were designed with ``_physics_process()`` in mind to keep in sync with both :ref:`NavigationServer3D<class_NavigationServer3D>` and :ref:`PhysicsServer3D<class_PhysicsServer3D>`.

They work well out of the box with :ref:`CharacterBody2D<class_CharacterBody2D>` and :ref:`CharacterBody3D<class_CharacterBody3D>` as well as any rigid bodies.

.. warning::

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.

.. warning::

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``.

.. _doc_navigation_script_templates:


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`.


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

.. image:: img/agent_avoidance_enabled.png

.. 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 ``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.
If the agents parents moves faster than this value the avoidance ``safe_velocity`` might not be accurate enough to avoid collision.

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.

.. 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.

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.

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

extends NavigationAgent2D

var agent : RID = get_rid()
NavigationServer2D::get_singleton()->agent_set_callback(agent, self, "_avoidance_done")
NavigationServer2D::get_singleton()->agent_set_callback(agent, null, "_avoidance_done")

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

extends NavigationAgent3D

var agent : RID = get_rid()
NavigationServer3D::get_singleton()->agent_set_callback(agent, self, "_avoidance_done")
NavigationServer3D::get_singleton()->agent_set_callback(agent, null, "_avoidance_done")

NavigationAgent Script Templates
--------------------------------

The following sections provides script templates for nodes commonly used with NavigationAgents.

Actor as Node3D
~~~~~~~~~~~~~~~

This script adds basic navigation movement to a Node3D with a NavigationAgent3D child node.

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

extends Node3D
# script on agent parent node, connect the agent 'velocity_computed' signal for collision avoidance

@export var movement_speed : float = 4.0
@onready var navigation_agent : NavigationAgent3D = get_node("NavigationAgent3D")
var movement_delta : float

func set_movement_target(movement_target : Vector3):
navigation_agent.set_target_location(movement_target)

func _physics_process(delta):

movement_delta = move_speed * delta
var next_path_position : Vector3 = navigation_agent.get_next_location()
var current_agent_position : Vector3 = global_transform.origin
var new_velocity : Vector3 = (next_path_position - current_agent_position).normalized() * movement_delta
navigation_agent.set_velocity(new_velocity)

func _on_NavigationAgent3D_velocity_computed(safe_velocity : Vector3):
# Move Node3D with the computed `safe_velocity` to avoid dynamic obstacles.
global_transform.origin = global_transform.origin.move_toward(global_transform.origin + safe_velocity, movement_delta)

Actor as CharacterBody3D
~~~~~~~~~~~~~~~~~~~~~~~~

This script adds basic navigation movement to a CharacterBody3D with a NavigationAgent3D child node.

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

extends CharacterBody3D
# script on agent parent node, connect the agent 'velocity_computed' signal for collision avoidance

@export var movement_speed : float = 4.0
@onready var navigation_agent : NavigationAgent3D = get_node("NavigationAgent3D")

func set_movement_target(movement_target : Vector3):
navigation_agent.set_target_location(movement_target)

func _physics_process(delta):

var next_path_position : Vector3 = navigation_agent.get_next_location()
var current_agent_position : Vector3 = global_transform.origin
var new_velocity : Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed
navigation_agent.set_velocity(new_velocity)

func _on_NavigationAgent3D_velocity_computed(safe_velocity : Vector3):
# Move KinematicBody3D with the computed `safe_velocity` to avoid dynamic obstacles.
velocity = safe_velocity
move_and_slide()

Actor as RigidBody3D
~~~~~~~~~~~~~~~~~~~~

This script adds basic navigation movement to a RigidBody3D with a NavigationAgent3D child node.

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

extends RigidBody3D
# script on agent parent node, connect the agent 'velocity_computed' signal for collision avoidance

@onready var navigation_agent : NavigationAgent3D = get_node("NavigationAgent3D")

func set_movement_target(movement_target : Vector3):
navigation_agent.set_target_location(movement_target)

func _physics_process(delta):

var next_path_position : Vector3 = navigation_agent.get_next_location()
var current_agent_position : Vector3 = global_transform.origin
var new_velocity : Vector3 = (next_path_position - current_agent_position).normalized() * velocity
navigation_agent.set_velocity(new_velocity)

func _on_NavigationAgent3D_velocity_computed(safe_velocity : Vector3):
# Move RigidBody3D with the computed `safe_velocity` to avoid dynamic obstacles.
set_linear_velocity(safe_velocity)
Loading

0 comments on commit a8382b4

Please sign in to comment.