Added the new navigation section from godot docs master.
BIN
tutorials/navigation/img/agent_avoidance_enabled.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tutorials/navigation/img/agent_safevelocity_signal.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 478 KiB |
BIN
tutorials/navigation/img/nav_2d_min_setup_step1.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
tutorials/navigation/img/nav_2d_min_setup_step2.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
tutorials/navigation/img/nav_2d_min_setup_step3.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
tutorials/navigation/img/nav_3d_min_setup_step1.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
tutorials/navigation/img/nav_3d_min_setup_step2.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
tutorials/navigation/img/nav_3d_min_setup_step3.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
tutorials/navigation/img/nav_3d_min_setup_step4.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
tutorials/navigation/img/nav_actor_doorbitmask.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
tutorials/navigation/img/nav_actor_doors.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
tutorials/navigation/img/nav_actor_locomotion.png
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
tutorials/navigation/img/nav_actor_sizes.png
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
tutorials/navigation/img/nav_debug_settings.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tutorials/navigation/img/nav_debug_xray_edge_lines.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
tutorials/navigation/img/nav_edge_connection2d.gif
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
tutorials/navigation/img/nav_edge_connection3d.gif
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
tutorials/navigation/img/nav_link_debug_visuals.png
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
tutorials/navigation/img/nav_link_properties.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
tutorials/navigation/img/nav_maps.png
Normal file
After Width: | Height: | Size: 224 KiB |
BIN
tutorials/navigation/img/nav_navmesh_links.png
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
tutorials/navigation/img/nav_optimization.webp
Normal file
After Width: | Height: | Size: 161 KiB |
BIN
tutorials/navigation/img/nav_polydrawtool.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
tutorials/navigation/img/nav_polymatroschka.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
tutorials/navigation/img/nav_polyoutlinefail.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
tutorials/navigation/img/nav_static_obstacle_build.gif
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
tutorials/navigation/img/navigation_debug_performance1.webp
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
tutorials/navigation/img/navigation_debug_performance2.webp
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
tutorials/navigation/img/navigation_debug_toggle.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tutorials/navigation/img/navigation_edge_connection.png
Normal file
After Width: | Height: | Size: 225 KiB |
BIN
tutorials/navigation/img/navigation_vertex_merge.png
Normal file
After Width: | Height: | Size: 239 KiB |
BIN
tutorials/navigation/img/navigation_vertex_merge2.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
tutorials/navigation/img/navigationlayers_naming.png
Normal file
After Width: | Height: | Size: 10 KiB |
25
tutorials/navigation/index.rst
Normal file
@ -0,0 +1,25 @@
|
||||
Navigation
|
||||
==========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: toc-learn-features-navigation
|
||||
|
||||
navigation_introduction_2d
|
||||
navigation_introduction_3d
|
||||
navigation_using_navigationservers
|
||||
navigation_using_navigationmaps
|
||||
navigation_using_navigationregions
|
||||
navigation_using_navigationmeshes
|
||||
navigation_using_navigationpaths
|
||||
navigation_using_navigationpathqueryobjects
|
||||
navigation_using_navigationagents
|
||||
navigation_using_navigationobstacles
|
||||
navigation_using_navigationlinks
|
||||
navigation_using_navigationlayers
|
||||
navigation_debug_tools
|
||||
navigation_connecting_navmesh
|
||||
navigation_different_actor_types
|
||||
navigation_different_actor_locomotion
|
||||
navigation_different_actor_area_access
|
||||
navigation_optimizing_performance
|
64
tutorials/navigation/navigation_connecting_navmesh.rst
Normal file
@ -0,0 +1,64 @@
|
||||
.. _doc_navigation_connecting_navmesh:
|
||||
|
||||
Connecting NavigationMeshes
|
||||
===========================
|
||||
|
||||
Different NavigationMeshes are automatically merged by the NavigationServer
|
||||
when at least two vertex positions of one edge exactly overlap.
|
||||
|
||||
To connect over arbitrary distances see :ref:`doc_navigation_using_navigationlinks`.
|
||||
|
||||
.. image:: img/navigation_vertex_merge.png
|
||||
|
||||
The same is true for multiple NavigationPolygon resources. As long as their
|
||||
outline points overlap exactly the NavigationServer will merge them.
|
||||
NavigationPolygon outlines must be from different NavigationPolygon resources to connect.
|
||||
|
||||
Overlapping or intersecting outlines on the same NavigationPolygon
|
||||
will fail the navigation mesh creation. Overlapping or intersecting
|
||||
outlines from different NavigationPolygons will often fail to create the
|
||||
navigation region edge connections on the NavigationServer and should be avoided.
|
||||
|
||||
.. image:: img/navigation_vertex_merge2.png
|
||||
|
||||
.. warning::
|
||||
|
||||
Exactly means exactly for the vertex position merge. Small float errors
|
||||
that happen quite regularly with imported meshes will prevent a successful vertex merge.
|
||||
|
||||
Alternatively ``NavigationMesh``s are not merged but still considered as ``connected`` by
|
||||
the NavigationServer when their edges are nearly parallel and within distance
|
||||
to each other. The connection distance is defined by the ``edge_connection_margin`` for each
|
||||
navigation map. In many cases NavigationMesh edges cannot properly connect when they partly overlap.
|
||||
Better avoid any navigation mesh overlap at all time for a consistent merge behavior.
|
||||
|
||||
.. image:: img/navigation_edge_connection.png
|
||||
|
||||
If navigation debug is enabled and the NavigationServer active the established navigation mesh connections will be visualized.
|
||||
See :ref:`doc_navigation_debug_tools` for more info about navigation debug options.
|
||||
|
||||
The default 2D ``edge_connection_margin`` can be changed in the ProjectSettings under ``navigation/2d/default_edge_connection_margin``.
|
||||
|
||||
The default 3D ``edge_connection_margin`` can be changed in the ProjectSettings under ``navigation/3d/default_edge_connection_margin``.
|
||||
|
||||
The edge connection margin value of any navigation map can also be changed at runtime with the NavigationServer API.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node2D
|
||||
# 2D margins are designed to work with "pixel" values
|
||||
var default_2d_map_rid: RID = get_world_2d().get_navigation_map()
|
||||
NavigationServer2D.map_set_edge_connection_margin(default_2d_map_rid, 50.0)
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
# 3D margins are designed to work with 3D unit values
|
||||
var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
NavigationServer3D.map_set_edge_connection_margin(default_3d_map_rid, 0.5)
|
||||
|
||||
.. note::
|
||||
|
||||
Changing the edge connection margin will trigger a full update of all navigation mesh connections on the NavigationServer.
|
89
tutorials/navigation/navigation_debug_tools.rst
Normal file
@ -0,0 +1,89 @@
|
||||
.. _doc_navigation_debug_tools:
|
||||
|
||||
Navigation Debug Tools
|
||||
======================
|
||||
|
||||
.. note::
|
||||
|
||||
The debug tools, properties and functions are only available in Godot debug builds.
|
||||
Do not use any of them in code that will be part of a release build.
|
||||
|
||||
Enabling debug navigation
|
||||
-------------------------
|
||||
|
||||
The navigation debug visualization is enabled by default inside the Editor.
|
||||
To visualize navigation meshes and connections also at runtime
|
||||
enable the option ``Visible Navigation`` in the editor debug menu.
|
||||
|
||||
.. image:: img/navigation_debug_toggle.png
|
||||
|
||||
In Godot debug builds the navigation debug can also be toggled on the NavigationServers from scripts.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
NavigationServer2D.set_debug_enabled(false)
|
||||
NavigationServer3D.set_debug_enabled(true)
|
||||
|
||||
Debug navigation settings
|
||||
-------------------------
|
||||
|
||||
The appearance of navigation debug can be change in the ProjectSettings under ``debug/shapes/navigation``.
|
||||
Certain debug features can also be enabled or disabled at will but may require a scene restart to apply.
|
||||
|
||||
.. image:: img/nav_debug_settings.png
|
||||
|
||||
Debug navigation mesh polygons
|
||||
------------------------------
|
||||
|
||||
If ``enable_edge_lines`` is enabled the edges of navigation mesh polygons will be highlighted.
|
||||
If ``enable_edge_lines_xray`` is also enabled the edges of navigationmeshes will be visible through geometry.
|
||||
|
||||
if ``enable_geometry_face_random_color`` is enabled each navigation mesh face receives
|
||||
a random color that is mixed with the main color from ``geometry_face_color``.
|
||||
|
||||
.. image:: img/nav_debug_xray_edge_lines.png
|
||||
|
||||
|
||||
Debug edge connections
|
||||
----------------------
|
||||
|
||||
Different navigation meshes connected within ``edge_connection_margin`` distance are overlaid.
|
||||
The color of the overlay is controlled with the navigation debug ``edge_connection_color``.
|
||||
The connections can be made visible through geometry with the navigation debug ``enable_edge_connections_xray`` property.
|
||||
|
||||
.. image:: img/nav_edge_connection2d.gif
|
||||
|
||||
.. image:: img/nav_edge_connection3d.gif
|
||||
|
||||
.. note::
|
||||
|
||||
Edge connections are only visible when the NavigationServer is active.
|
||||
|
||||
Debug Performance
|
||||
-----------------
|
||||
|
||||
To measure NavigationServer performance a dedicated monitor exists that can be found within the Editor Debugger under `Debugger->Monitors->NavigationProcess`.
|
||||
|
||||
.. image:: img/navigation_debug_performance1.webp
|
||||
|
||||
NavigationProcess shows how long the NavigationServer spends updating its internals this update frame in milliseconds.
|
||||
NavigationProcess works similar to Process for visual frame rendering and PhysicsProcess for collision and fixed updates.
|
||||
|
||||
NavigationProcess accounts for all updates to ``navigation maps``, ``navigation regions`` and ``navigation agents`` as well as all the ``avoidance calculations`` for the update frame.
|
||||
|
||||
.. note::
|
||||
|
||||
NavigationProcess does NOT include pathfinding performance cause pathfinding operates on the navigation map data independently from the server process update.
|
||||
|
||||
NavigationProcess should be in general kept as low and as stable as possible for runtime performance to avoid frame rate issues.
|
||||
Note that since the NavigationServer process update happens in the middle of the physics update an increase in NavigationProcess will automatically increase PhysicsProcess by the same amount.
|
||||
|
||||
Navigation also provides more detailed statistics about the current navigation related objects and navigation map composition on the NavigationServer.
|
||||
|
||||
.. image:: img/navigation_debug_performance2.webp
|
||||
|
||||
Navigation statistics shown here can not be judged as good or bad for performance as it depends entirely on the project what can be considered as reasonable or horribly excessive.
|
||||
|
||||
Navigation statistics help with identifying performance bottlenecks that are less obvious because the source might not always have a visible representation.
|
||||
E.g. pathfinding performance issues created by overly detailed navigation meshes with thousand of edges / polygons or problems caused by procedural navigation gone wrong.
|
@ -0,0 +1,29 @@
|
||||
.. _doc_navigation_different_actor_area_access:
|
||||
|
||||
Support different actor area access
|
||||
===================================
|
||||
|
||||
.. image:: img/nav_actor_doors.png
|
||||
|
||||
A typical example for different area access in gameplay are doors that connect rooms
|
||||
with different navigation meshes and are not accessible by all actors all the time.
|
||||
|
||||
Add a NavigationRegion at the door position.
|
||||
Add an appropriated navigationmesh the size of the door that can connect with the surrounding navigationmeshes.
|
||||
In order to control access enable / disable navigation layer bits so path queries
|
||||
that use the same navigation layer bits can find a path through the "door" navigationmesh.
|
||||
|
||||
The bitmask can act as a set of door keys or abilities and only actors with at least
|
||||
one matching and enabled bit layer in their pathfinding query will find a path through this region.
|
||||
See :ref:`doc_navigation_advanced_using_navigationlayers` for more information on how to work with navigation layers and the bitmask.
|
||||
|
||||
.. image:: img/nav_actor_doorbitmask.png
|
||||
|
||||
The entire "door" region can also be enabled / disable if required but if disabled will block access for all path queries.
|
||||
|
||||
Prefer working with navigation layers in path queries whenever possible as enabling or disabling
|
||||
navigation layers on a region triggers a performance costly recalculation of the navigation map connections.
|
||||
|
||||
.. warning::
|
||||
|
||||
Changing navigation layers will only affect new path queries but not automatically update existing paths.
|
@ -0,0 +1,43 @@
|
||||
.. _doc_navigation_different_actor_locomotion:
|
||||
|
||||
Support different actor locomotion
|
||||
==================================
|
||||
|
||||
.. image:: img/nav_actor_locomotion.png
|
||||
|
||||
To support different actor locomotion like crouching and crawling, a similar
|
||||
map setup as supporting :ref:`doc_navigation_different_actor_types` is required.
|
||||
|
||||
Bake different navigation meshes with an appropriate height for crouched
|
||||
or crawling actors so they can find paths through those narrow sections in your game world.
|
||||
|
||||
When an actor changes locomotion state, e.g. stands up, starts
|
||||
crouching or crawling, query the appropriate map for a path.
|
||||
|
||||
If the avoidance behavior should also change with the locomotion e.g. only avoid while standing or only avoid
|
||||
other agents in the same locomotion state, switch the actors's avoidance agent to another avoidance map with each locomotion change.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func update_path():
|
||||
|
||||
if actor_standing:
|
||||
path = NavigationServer3D.map_get_path(standing_navigation_map_rid, start_position, target_position, true)
|
||||
elif actor_crouching:
|
||||
path = NavigationServer3D.map_get_path(crouched_navigation_map_rid, start_position, target_position, true)
|
||||
elif actor_crawling:
|
||||
path = NavigationServer3D.map_get_path(crawling_navigation_map_rid, start_position, target_position, true)
|
||||
|
||||
func change_agent_avoidance_state():
|
||||
|
||||
if actor_standing:
|
||||
NavigationServer3D.agent_set_map(avoidance_agent_rid, standing_navigation_map_rid)
|
||||
elif actor_crouching:
|
||||
NavigationServer3D.agent_set_map(avoidance_agent_rid, crouched_navigation_map_rid)
|
||||
elif actor_crawling:
|
||||
NavigationServer3D.agent_set_map(avoidance_agent_rid, crawling_navigation_map_rid)
|
||||
|
||||
.. note::
|
||||
|
||||
While a path query can be execute immediately for multiple maps, the avoidance agent map switch will only take effect after the next server synchronization.
|
81
tutorials/navigation/navigation_different_actor_types.rst
Normal file
@ -0,0 +1,81 @@
|
||||
.. _doc_navigation_different_actor_types:
|
||||
|
||||
Support different actor types
|
||||
=============================
|
||||
|
||||
.. image:: img/nav_actor_sizes.png
|
||||
|
||||
To support different actor types due to e.g. their sizes each type requires its own
|
||||
navigation map and navigation mesh baked with an appropriated agent radius and height.
|
||||
The same approach can be used to distinguish between e.g. landwalking, swimming or flying agents.
|
||||
|
||||
.. note::
|
||||
|
||||
Agents are exclusively defined by a radius and height value for baking navigation meshes, pathfinding and avoidance. More complex shapes are not supported.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# Create a navigation mesh resource for each actor size.
|
||||
var navigation_mesh_standard_size: NavigationMesh = NavigationMesh.new()
|
||||
var navigation_mesh_small_size: NavigationMesh = NavigationMesh.new()
|
||||
var navigation_mesh_huge_size: NavigationMesh = NavigationMesh.new()
|
||||
|
||||
# Set appropriated agent parameters.
|
||||
navigation_mesh_standard_size.agent_radius = 0.5
|
||||
navigation_mesh_standard_size.agent_height = 1.8
|
||||
navigation_mesh_small_size.agent_radius = 0.25
|
||||
navigation_mesh_small_size.agent_height = 0.7
|
||||
navigation_mesh_huge_size.agent_radius = 1.5
|
||||
navigation_mesh_huge_size.agent_height = 2.5
|
||||
|
||||
# Get the root node to parse geometry for the baking.
|
||||
var root_node: Node3D = get_node("NavigationMeshBakingRootNode")
|
||||
|
||||
# Create the source geometry resource that will hold the parsed geometry data.
|
||||
var source_geometry_data: NavigationMeshSourceGeometryData3D = NavigationMeshSourceGeometryData3D.new()
|
||||
|
||||
# Parse the source geometry from the SceneTree on the main thread.
|
||||
# The navigation mesh is only required for the parse settings so any of the three will do.
|
||||
NavigationServer3D.parse_source_geometry_data(navigation_mesh_standard_size, source_geometry_data, root_node)
|
||||
|
||||
# Bake the navigation geometry for each agent size from the same source geometry.
|
||||
# If required for performance this baking step could also be done on background threads.
|
||||
NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_standard_size, source_geometry_data)
|
||||
NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_small_size, source_geometry_data)
|
||||
NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_huge_size, source_geometry_data)
|
||||
|
||||
# Create different navigation maps on the NavigationServer.
|
||||
var navigation_map_standard: RID = NavigationServer3D.map_create()
|
||||
var navigation_map_small: RID = NavigationServer3D.map_create()
|
||||
var navigation_map_huge: RID = NavigationServer3D.map_create()
|
||||
|
||||
# Set the new navigation maps as active.
|
||||
NavigationServer3D.map_set_active(navigation_map_standard, true)
|
||||
NavigationServer3D.map_set_active(navigation_map_small, true)
|
||||
NavigationServer3D.map_set_active(navigation_map_huge, true)
|
||||
|
||||
# Create a region for each map.
|
||||
var navigation_region_standard: RID = NavigationServer3D.region_create()
|
||||
var navigation_region_small: RID = NavigationServer3D.region_create()
|
||||
var navigation_region_huge: RID = NavigationServer3D.region_create()
|
||||
|
||||
# Add the regions to the maps.
|
||||
NavigationServer3D.region_set_map(navigation_region_standard, navigation_map_standard)
|
||||
NavigationServer3D.region_set_map(navigation_region_small, navigation_map_small)
|
||||
NavigationServer3D.region_set_map(navigation_region_huge, navigation_map_huge)
|
||||
|
||||
# Set navigation mesh for each region.
|
||||
NavigationServer3D.region_set_navigation_mesh(navigation_region_standard, navigation_mesh_standard_size)
|
||||
NavigationServer3D.region_set_navigation_mesh(navigation_region_small, navigation_mesh_small_size)
|
||||
NavigationServer3D.region_set_navigation_mesh(navigation_region_huge, navigation_mesh_huge_size)
|
||||
|
||||
# Create start and end position for the navigation path query.
|
||||
var start_pos: Vector3 = Vector3(0.0, 0.0, 0.0)
|
||||
var end_pos: Vector3 = Vector3(2.0, 0.0, 0.0)
|
||||
var use_corridorfunnel: bool = true
|
||||
|
||||
# Query paths for each agent size.
|
||||
var path_standard_agent = NavigationServer3D.map_get_path(navigation_map_standard, start_pos, end_pos, use_corridorfunnel)
|
||||
var path_small_agent = NavigationServer3D.map_get_path(navigation_map_small, start_pos, end_pos, use_corridorfunnel)
|
||||
var path_huge_agent = NavigationServer3D.map_get_path(navigation_map_huge, start_pos, end_pos, use_corridorfunnel)
|
223
tutorials/navigation/navigation_introduction_2d.rst
Normal file
@ -0,0 +1,223 @@
|
||||
.. _doc_navigation_overview_2d:
|
||||
|
||||
2D Navigation Overview
|
||||
======================
|
||||
|
||||
Godot provides multiple objects, classes and servers to facilitate grid-based or mesh-based navigation and pathfinding for 2D and 3D games.
|
||||
The following section provides a quick overview over all available navigation related objects in Godot for 2D scenes and their primary use.
|
||||
|
||||
Godot provides the following objects and classes for 2D navigation:
|
||||
|
||||
- :ref:`Astar2D<class_Astar2D>`
|
||||
``Astar2D`` objects provide an option to find the shortest path in a graph of weighted **points**.
|
||||
|
||||
The AStar2D class is best suited for cellbased 2D gameplay that does not require actors to reach any possible position within an area but only predefined, distinct positions.
|
||||
|
||||
- :ref:`NavigationServer2D<class_NavigationServer2D>`
|
||||
``NavigationServer2D`` provides a powerful server API to find the shortest path between two positions on a area defined by a navigation mesh.
|
||||
|
||||
The NavigationServer is best suited for 2D realtime gameplay that does require actors to reach any possible position within an navmesh defined area.
|
||||
Meshbased navigation scales well with large gameworlds as a large area can often be defined with a single polygon when it would require many, many grid cells.
|
||||
|
||||
The NavigationServer holds different navigation maps that each consist of regions that hold navigation mesh data.
|
||||
Agents can be placed on a map for avoidance calculation.
|
||||
RIDs are used to reference the internal maps, regions and agents when communicating with the server.
|
||||
|
||||
The following NavigationServer RID types are available.
|
||||
- NavMap RID
|
||||
Reference to a specific navigation map that holds regions and agents.
|
||||
The map will attempt to join changed navigation meshes of regions by proximity.
|
||||
The map will synchronize regions and agents each physics frame.
|
||||
- NavRegion RID
|
||||
Reference to a specific navigation region that can hold navigation mesh data.
|
||||
The region can be enabled / disabled or the use restricted with a navigationlayer bitmask.
|
||||
- NavLink RID
|
||||
Reference to a specific navigation link that connects two navigation mesh positions over arbitrary distances.
|
||||
- NavAgent RID
|
||||
Reference to a specific avoidance agent with a radius value use solely in avoidance.
|
||||
|
||||
The following SceneTree Nodes are available as helpers to work with the NavigationServer2D API.
|
||||
|
||||
- :ref:`NavigationRegion2D<class_NavigationRegion2D>` Node
|
||||
A Node that holds a NavigationPolygon resource that defines a navigation mesh for the NavigationServer2D.
|
||||
|
||||
- The region can be enabled / disabled.
|
||||
- The use in pathfinding can be further restricted through the navigationlayers bitmask.
|
||||
- Regions can join their navigation meshes by proximity for a combined navigation mesh.
|
||||
|
||||
- :ref:`NavigationLink2D<class_NavigationLink2D>` Node
|
||||
A Node that connects two positions on navigation mesh over arbitrary distances for pathfinding.
|
||||
|
||||
- The link can be enabled / disabled.
|
||||
- The link can be made one-way or bidirectional.
|
||||
- The use in pathfinding can be further restricted through the navigationlayers bitmask.
|
||||
|
||||
Links tell the pathfinding that a connection exists and at what cost. The actual agent handling and movement needs to happen in custom scripts.
|
||||
|
||||
- :ref:`NavigationAgent2D<class_NavigationAgent2D>` Node
|
||||
An optional helper Node to facilitate common NavigationServer2D API calls for pathfinding and avoidance
|
||||
for a Node2D inheriting parent Node.
|
||||
|
||||
- :ref:`NavigationObstacle2D<class_NavigationObstacle2D>` Node
|
||||
A Node that acts as an agent with avoidance radius, to work it needs to be added under a Node2D
|
||||
inheriting parent Node. Obstacles are intended as a last resort option for constantly moving objects
|
||||
that cannot be re(baked) to a navigation mesh efficiently. This node also only works if RVO processing
|
||||
is being used.
|
||||
|
||||
The 2D navigation meshes are defined with the following resources:
|
||||
|
||||
- :ref:`NavigationPolygon<class_NavigationPolygon>` Resource
|
||||
A resource that holds 2D navigation mesh data and provides polygon drawtools to define navigation areas inside the Editor as well as at runtime.
|
||||
|
||||
- The NavigationRegion2D Node uses this resource to define its navigation area.
|
||||
- The NavigationServer2D uses this resource to update navmesh of individual regions.
|
||||
- The TileSet Editor creates and uses this resource internally when defining tile navigation areas.
|
||||
|
||||
.. seealso::
|
||||
|
||||
You can see how 2D navigation works in action using the
|
||||
`2D Navigation Polygon <https://github.com/godotengine/godot-demo-projects/tree/master/2d/navigation>`__
|
||||
and `Grid-based Navigation with AStarGrid2D <https://github.com/godotengine/godot-demo-projects/tree/master/2d/navigation_astar>`__
|
||||
demo projects.
|
||||
|
||||
Setup for 2D scene
|
||||
------------------
|
||||
|
||||
The following steps show the basic setup for a minimum viable navigation in 2D that uses the
|
||||
NavigationServer2D and a NavigationAgent2D for path movement.
|
||||
|
||||
#. Add a NavigationRegion2D Node to the scene.
|
||||
|
||||
#. Click on the region node and add a new NavigationPolygon Resource to the region node.
|
||||
|
||||
.. image:: img/nav_2d_min_setup_step1.png
|
||||
|
||||
#. Define the moveable navigation area with the NavigationPolygon draw tool.
|
||||
|
||||
.. image:: img/nav_2d_min_setup_step2.png
|
||||
|
||||
.. note::
|
||||
|
||||
The navigation mesh defines the area where an actor can stand and move with its center.
|
||||
Leave enough margin between the navpolygon edges and collision objects to not get path
|
||||
following actors repeatedly stuck on collision.
|
||||
|
||||
#. Add a CharacterBody2D node in the scene with a basic collision shape and a sprite or mesh
|
||||
for visuals.
|
||||
|
||||
#. Add a NavigationAgent2D node below the character node.
|
||||
|
||||
.. image:: img/nav_2d_min_setup_step3.webp
|
||||
|
||||
#. Add the following script to the CharacterBody2D node. We make sure to set a movement target
|
||||
after the scene has fully loaded and the NavigationServer had time to sync.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends CharacterBody2D
|
||||
|
||||
var movement_speed: float = 200.0
|
||||
var movement_target_position: Vector2 = Vector2(60.0,180.0)
|
||||
|
||||
@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
|
||||
|
||||
func _ready():
|
||||
# These values need to be adjusted for the actor's speed
|
||||
# and the navigation layout.
|
||||
navigation_agent.path_desired_distance = 4.0
|
||||
navigation_agent.target_desired_distance = 4.0
|
||||
|
||||
# Make sure to not await during _ready.
|
||||
call_deferred("actor_setup")
|
||||
|
||||
func actor_setup():
|
||||
# Wait for the first physics frame so the NavigationServer can sync.
|
||||
await get_tree().physics_frame
|
||||
|
||||
# Now that the navigation map is no longer empty, set the movement target.
|
||||
set_movement_target(movement_target_position)
|
||||
|
||||
func set_movement_target(movement_target: Vector2):
|
||||
navigation_agent.target_position = movement_target
|
||||
|
||||
func _physics_process(delta):
|
||||
if navigation_agent.is_navigation_finished():
|
||||
return
|
||||
|
||||
var current_agent_position: Vector2 = global_position
|
||||
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
|
||||
|
||||
var new_velocity: Vector2 = next_path_position - current_agent_position
|
||||
new_velocity = new_velocity.normalized()
|
||||
new_velocity = new_velocity * movement_speed
|
||||
|
||||
velocity = new_velocity
|
||||
move_and_slide()
|
||||
|
||||
.. code-tab:: csharp C#
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class MyCharacterBody2D : CharacterBody2D
|
||||
{
|
||||
private NavigationAgent2D _navigationAgent;
|
||||
|
||||
private float _movementSpeed = 200.0f;
|
||||
private Vector2 _movementTargetPosition = new Vector2(70.0f, 226.0f);
|
||||
|
||||
public Vector2 MovementTarget
|
||||
{
|
||||
get { return _navigationAgent.TargetPosition; }
|
||||
set { _navigationAgent.TargetPosition = value; }
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
_navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
|
||||
|
||||
// These values need to be adjusted for the actor's speed
|
||||
// and the navigation layout.
|
||||
_navigationAgent.PathDesiredDistance = 4.0f;
|
||||
_navigationAgent.TargetDesiredDistance = 4.0f;
|
||||
|
||||
// Make sure to not await during _Ready.
|
||||
Callable.From(ActorSetup).CallDeferred();
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
base._PhysicsProcess(delta);
|
||||
|
||||
if (_navigationAgent.IsNavigationFinished())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 currentAgentPosition = GlobalTransform.Origin;
|
||||
Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
|
||||
|
||||
Vector2 newVelocity = (nextPathPosition - currentAgentPosition).Normalized();
|
||||
newVelocity *= _movementSpeed;
|
||||
|
||||
Velocity = newVelocity;
|
||||
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
private async void ActorSetup()
|
||||
{
|
||||
// Wait for the first physics frame so the NavigationServer can sync.
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
|
||||
|
||||
// Now that the navigation map is no longer empty, set the movement target.
|
||||
MovementTarget = _movementTargetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
On the first frame the NavigationServer map has not synchronized region data and any path query
|
||||
will return empty. Await one frame to pause scripts until the NavigationServer had time to sync.
|
228
tutorials/navigation/navigation_introduction_3d.rst
Normal file
@ -0,0 +1,228 @@
|
||||
.. _doc_navigation_overview_3d:
|
||||
|
||||
3D Navigation Overview
|
||||
======================
|
||||
|
||||
Godot provides multiple objects, classes and servers to facilitate grid-based or mesh-based navigation and pathfinding for 2D and 3D games.
|
||||
The following section provides a quick overview over all available navigation related objects in Godot for 3D scenes and their primary use.
|
||||
|
||||
Godot provides the following objects and classes for 3D navigation:
|
||||
|
||||
- :ref:`Astar3D<class_Astar3D>`
|
||||
``Astar3D`` objects provide an option to find the shortest path in a graph of weighted **points**.
|
||||
|
||||
The AStar3D class is best suited for cellbased 3D gameplay that does not require actors to reach any
|
||||
possible position within an area but only predefined, distinct positions.
|
||||
|
||||
- :ref:`NavigationServer3D<class_NavigationServer3D>`
|
||||
``NavigationServer3D`` provides a powerful server API to find the shortest path between two positions
|
||||
on a area defined by a navigation mesh.
|
||||
|
||||
The NavigationServer is best suited for 3D realtime gameplay that does require actors to reach any
|
||||
possible position within an navmesh defined area. Meshbased navigation scales well with large gameworlds
|
||||
as a large area can often be defined with a single polygon when it would require many, many grid cells.
|
||||
|
||||
The NavigationServer holds different navigation maps that each consist of regions that hold navigation mesh
|
||||
data. Agents can be placed on a map for avoidance calculation. RIDs are used to reference the internal maps,
|
||||
regions and agents when communicating with the server.
|
||||
|
||||
The following NavigationServer RID types are available.
|
||||
- NavMap RID
|
||||
Reference to a specific navigation map that holds regions and agents.
|
||||
The map will attempt to join changed navigation meshes of regions by proximity.
|
||||
The map will synchronize regions and agents each physics frame.
|
||||
- NavRegion RID
|
||||
Reference to a specific navigation region that can hold navigation mesh data.
|
||||
The region can be enabled / disabled or the use restricted with a navigationlayer bitmask.
|
||||
- NavLink RID
|
||||
Reference to a specific navigation link that connects two navigation mesh positions over arbitrary distances.
|
||||
- NavAgent RID
|
||||
Reference to a specific avoidance agent with a radius value use solely in avoidance.
|
||||
|
||||
The following SceneTree Nodes are available as helpers to work with the NavigationServer3D API.
|
||||
|
||||
- :ref:`NavigationRegion3D<class_NavigationRegion3D>` Node
|
||||
A Node that holds a Navigation Mesh resource that defines a navigation mesh for the NavigationServer3D.
|
||||
|
||||
- The region can be enabled / disabled.
|
||||
- The use in pathfinding can be further restricted through the navigationlayers bitmask.
|
||||
- Regions can join their navigation meshes by proximity for a combined navigation mesh.
|
||||
|
||||
- :ref:`NavigationLink3D<class_NavigationLink3D>` Node
|
||||
A Node that connects two positions on navigation mesh over arbitrary distances for pathfinding.
|
||||
|
||||
- The link can be enabled / disabled.
|
||||
- The link can be made one-way or bidirectional.
|
||||
- The use in pathfinding can be further restricted through the navigationlayers bitmask.
|
||||
|
||||
Links tell the pathfinding that a connection exists and at what cost. The actual agent handling and movement needs to happen in custom scripts.
|
||||
|
||||
- :ref:`NavigationAgent3D<class_NavigationAgent3D>` Node
|
||||
An optional helper Node to facilitate common NavigationServer3D API calls for pathfinding and avoidance for
|
||||
a Node3D inheriting parent Node.
|
||||
|
||||
- :ref:`NavigationObstacle3D<class_NavigationObstacle3D>` Node
|
||||
A Node that acts as an agent with avoidance radius, to work it needs to be added under a Node3D
|
||||
inheriting parent Node. Obstacles are intended as a last resort option for constantly moving objects
|
||||
that cannot be re(baked) to a navigation mesh efficiently. This node also only works if RVO processing
|
||||
is being used.
|
||||
|
||||
The 3D navigation meshes are defined with the following resources:
|
||||
|
||||
- :ref:`NavigationMesh<class_NavigationMesh>` Resource
|
||||
A resource that holds 3D navigation mesh data and provides 3D geometry baking options to define navigation
|
||||
areas inside the Editor as well as at runtime.
|
||||
|
||||
- The NavigationRegion3D Node uses this resource to define its navigation area.
|
||||
- The NavigationServer3D uses this resource to update navmesh of individual regions.
|
||||
- The GridMap Editor uses this resource when specific navigation meshes are defined for each gridcell.
|
||||
|
||||
.. seealso::
|
||||
|
||||
You can see how 3D navigation works in action using the
|
||||
`3D Navigation demo project <https://github.com/godotengine/godot-demo-projects/tree/master/3d/navigation>`__.
|
||||
|
||||
Setup for 3D scene
|
||||
------------------
|
||||
|
||||
The following steps show how to setup a minimum viable navigation in 3D that uses the NavigationServer3D and
|
||||
a NavigationAgent3D for path movement.
|
||||
|
||||
#. Add a NavigationRegion3D Node to the scene.
|
||||
|
||||
#. Click on the region node and add a new :ref:`NavigationMesh<class_NavigationMesh>` Resource to
|
||||
the region node.
|
||||
|
||||
.. image:: img/nav_3d_min_setup_step1.png
|
||||
|
||||
#. Add a new MeshInstance3D node as a child of the region node.
|
||||
|
||||
#. Select the MeshInstance3D node and add a new PlaneMesh and increase the xy size to 10.
|
||||
|
||||
#. Select the region node again and press the "Bake Navmesh" button on the top bar.
|
||||
|
||||
.. image:: img/nav_3d_min_setup_step2.png
|
||||
|
||||
#. Now a transparent navigation mesh appeared that hovers some distance on top the planemesh.
|
||||
|
||||
.. image:: img/nav_3d_min_setup_step3.png
|
||||
|
||||
#. Add a CharacterBody3D node in the scene with a basic collision shape and some mesh for visuals.
|
||||
|
||||
#. Add a NavigationAgent3D node below the character node.
|
||||
|
||||
.. image:: img/nav_3d_min_setup_step4.webp
|
||||
|
||||
#. Add a script to the CharacterBody3D node with the following content. We make sure to set a
|
||||
movement target after the scene has fully loaded and the NavigationServer had time to sync.
|
||||
Also, add a Camera3D and some light and environment to see something.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends CharacterBody3D
|
||||
|
||||
var movement_speed: float = 2.0
|
||||
var movement_target_position: Vector3 = Vector3(-3.0,0.0,2.0)
|
||||
|
||||
@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D
|
||||
|
||||
func _ready():
|
||||
# These values need to be adjusted for the actor's speed
|
||||
# and the navigation layout.
|
||||
navigation_agent.path_desired_distance = 0.5
|
||||
navigation_agent.target_desired_distance = 0.5
|
||||
|
||||
# Make sure to not await during _ready.
|
||||
call_deferred("actor_setup")
|
||||
|
||||
func actor_setup():
|
||||
# Wait for the first physics frame so the NavigationServer can sync.
|
||||
await get_tree().physics_frame
|
||||
|
||||
# Now that the navigation map is no longer empty, set the movement target.
|
||||
set_movement_target(movement_target_position)
|
||||
|
||||
func set_movement_target(movement_target: Vector3):
|
||||
navigation_agent.set_target_position(movement_target)
|
||||
|
||||
func _physics_process(delta):
|
||||
if navigation_agent.is_navigation_finished():
|
||||
return
|
||||
|
||||
var current_agent_position: Vector3 = global_position
|
||||
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
|
||||
|
||||
var new_velocity: Vector3 = next_path_position - current_agent_position
|
||||
new_velocity = new_velocity.normalized()
|
||||
new_velocity = new_velocity * movement_speed
|
||||
|
||||
velocity = new_velocity
|
||||
move_and_slide()
|
||||
|
||||
.. code-tab:: csharp C#
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class MyCharacterBody3D : CharacterBody3D
|
||||
{
|
||||
private NavigationAgent3D _navigationAgent;
|
||||
|
||||
private float _movementSpeed = 2.0f;
|
||||
private Vector3 _movementTargetPosition = new Vector3(-3.0f, 0.0f, 2.0f);
|
||||
|
||||
public Vector3 MovementTarget
|
||||
{
|
||||
get { return _navigationAgent.TargetPosition; }
|
||||
set { _navigationAgent.TargetPosition = value; }
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
_navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
|
||||
|
||||
// These values need to be adjusted for the actor's speed
|
||||
// and the navigation layout.
|
||||
_navigationAgent.PathDesiredDistance = 0.5f;
|
||||
_navigationAgent.TargetDesiredDistance = 0.5f;
|
||||
|
||||
// Make sure to not await during _Ready.
|
||||
Callable.From(ActorSetup).CallDeferred();
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
base._PhysicsProcess(delta);
|
||||
|
||||
if (_navigationAgent.IsNavigationFinished())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 currentAgentPosition = GlobalTransform.Origin;
|
||||
Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
|
||||
|
||||
Vector3 newVelocity = (nextPathPosition - currentAgentPosition).Normalized();
|
||||
newVelocity *= _movementSpeed;
|
||||
|
||||
Velocity = newVelocity;
|
||||
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
private async void ActorSetup()
|
||||
{
|
||||
// Wait for the first physics frame so the NavigationServer can sync.
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
|
||||
|
||||
// Now that the navigation map is no longer empty, set the movement target.
|
||||
MovementTarget = _movementTargetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
On the first frame the NavigationServer map has not synchronized region data and any path query
|
||||
will return empty. Await one frame to pause scripts until the NavigationServer had time to sync.
|
117
tutorials/navigation/navigation_optimizing_performance.rst
Normal file
@ -0,0 +1,117 @@
|
||||
.. _doc_navigation_optimizing_performance:
|
||||
|
||||
Optimizing Navigation Performance
|
||||
=================================
|
||||
|
||||
.. image:: img/nav_optimization.webp
|
||||
|
||||
Common Navigation related performance problems can be categorized into the following topics:
|
||||
|
||||
- Performance problems with parsing SceneTree nodes for navigation mesh baking.
|
||||
- Performance problems with baking the actual navigation mesh.
|
||||
- Performance problems with NavigationAgent path queries.
|
||||
- Performance problems with the actual path search.
|
||||
- Performance problems with synchronizing the navigation map.
|
||||
|
||||
In the following sections information can be found on how to identify and fix or at least mitigate their impact on framerates.
|
||||
|
||||
Performance problems with parsing SceneTree nodes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
Prefer using simple shapes with as few edges as possible e.g. nothing rounded like a circle, sphere or torus.
|
||||
|
||||
Prefer using physics collision shapes over complex visual meshes as source geometry as meshes need to be copied from the GPU and are commonly much more detailed than necessary.
|
||||
|
||||
In general avoid using very complex geometry as source geometry for baking navigation meshes.
|
||||
E.g. never use a very detailed visual mesh, as parsing its shape to data arrays and voxelizing it for the navigation mesh baking will take a long time for no real quality gain on the final navigation mesh.
|
||||
Instead, use a very simplified level of detail version of a shape. Even better, use very primitive shapes like boxes and rectangles that only roughly cover the same geometry but still yield a baked result good enough for pathfinding.
|
||||
|
||||
Prefer using simple physics collision shapes over visual meshes, as the source geometry for baking navigation meshes.
|
||||
Physics shapes are by default very limited and optimized shapes that are easy and quick to parse. A visual mesh on the other hand can range from simple to complex.
|
||||
On top, to gain access to visual mesh data the parser needs to request the mesh data arrays from the RenderingServer as visual mesh data is stored directly on the GPU and is not cached on the CPU.
|
||||
This requires locking the RenderingServer thread and can severely impact framerate at runtime while the rendering runs multi-threaded.
|
||||
If the rendering runs single-threaded, the framerate impact might be even worse and the mesh parsing might freeze the entire game for a few seconds on complex meshes.
|
||||
|
||||
Performance problems with navigation mesh baking
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
At runtime, always prefer to use a background thread for baking navigation meshes.
|
||||
|
||||
Increase NavigationMesh ``cell_size`` and ``cell_height`` to create less voxels.
|
||||
|
||||
Change the ``SamplePartitionType`` from watershed to monotone or layers to gain baking performance.
|
||||
|
||||
.. warning::
|
||||
NEVER scale source geometry with nodes to avoid precision errors. Most scale applies only visually and shapes that are very large at their base scale require still a lot of extra processing even while downscaled.
|
||||
|
||||
Baking navigation meshes at runtime should always be done in a background thread if possible. Even small sized navigation meshes can take far longer to bake than what is possible to squeeze into a single frame, at least if the framerate should stay at a bearable level.
|
||||
|
||||
Complexity of source geometry data parsed from SceneTree nodes has big impact on baking performance as everything needs to be mapped to a grid / voxels.
|
||||
For runtime baking performance the NavigationMesh cell size and cell height should be set as high as possible without causing navigation mesh quality problems for a game.
|
||||
If cell size or cell height is set too low the baking is forced to create an excessive amount of voxels to process the source geometry.
|
||||
If the source geometry spans over a very large game world it is even possible that the baking process runs out off memory in the middle and crashes the game.
|
||||
The partition type can also be lowered depending on how complex the games source geometry is to gain some performance.
|
||||
E.g. games with mostly flat surfaces with blocky geometry can get away with the monotone or layers mode that are a lot faster to bake (e.g. because they require no distance field pass).
|
||||
|
||||
Never scale source geometry with nodes. Not only can it result in a lot of precision errors with wrongly matched vertices and edges but also some scaling only exists as visuals and not in the actual parsed data.
|
||||
E.g. if a mesh is downscaled visually in the Editor, e.g. the scale set to 0.001 on a MeshInstance, the mesh still requires a gigantic and very complex voxel grid to be processed for the baking.
|
||||
|
||||
Performance problems with NavigationAgent path queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
Avoid unnecessary path resets and queries every frame in NavigationAgent scripts.
|
||||
|
||||
Avoid updating all NavigationAgent paths in the same frame.
|
||||
|
||||
Logical errors and wasteful operations in the custom NavigationAgent scripts are very common causes of performance issues, e.g. watch out for resetting the path every single frame.
|
||||
By default NavigationAgents are optimized to only query new paths when the target position changes, the navigation map changes or they are forced too far away from the desired path distance.
|
||||
|
||||
E.g. when AI should move to the player, the target position should not be set to the player position every single frame as this queries a new path every frame.
|
||||
Instead, the distance from the current target position to the player position should be compared and only when the player has moved too far away a new target position should be set.
|
||||
|
||||
Do not check beforehand if a target position is reachable every frame. What looks like an innocent check is the equivalent of an expensive path query behind the scene.
|
||||
If the plan is to request a new path anyway should the position be reachable, a path should be queried directly.
|
||||
By looking at the last position of the returned path and if that position is in a "reachable" distance to the checked position it answers the "is this position reachable?" question.
|
||||
This avoids doing the equivalent of two full path queries every frame for the same NavigationAgent.
|
||||
|
||||
Divide the total number of NavigationAgents into update groups or use random timers so that they do not all request new paths in the same frame.
|
||||
|
||||
Performance problems with the actual path search
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
Optimize overdetailed navigation meshes by reducing the amount of polygons and edges.
|
||||
|
||||
The cost of the actual path search correlates directly with the amount of navigation mesh polygons and edges and not the real size of a game world.
|
||||
If a giant game world uses very optimized navigation meshes with only few polygons that cover large areas, performance should be acceptable.
|
||||
If the game world is splintered into very small navigation meshes that each have tiny polygons (like for TileMaps) pathfinding performance will be reduced.
|
||||
|
||||
A common problem is a sudden performance drop when a target position is not reachable in a path query.
|
||||
This performance drop is "normal" and the result of a too large, too unoptimized navigation mesh with way to much polygons and edges to search through.
|
||||
In normal path searches where the target position can be reached quickly the pathfinding will do an early exit as soon as the position is reached which can hide this lack of optimization for a while.
|
||||
If the target position can not be reached the pathfinding has to do a far longer search through the available polygons to confirm that the position is absolutely not reachable.
|
||||
|
||||
Performance problems with navigation map synchronization
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
Merge navigation meshes polygons by vertex instead of by edge connection wherever possible.
|
||||
|
||||
When changes are made to e.g. navigation meshes or navigation regions, the NavigationServer needs to synchronize the navigation map.
|
||||
Depending on the complexity of navigation meshes, this can take a significant amount of time which may impact the framerate.
|
||||
|
||||
The NavigationServer merges navigation meshes either by vertex or by edge connection.
|
||||
The merge by vertex happens when the two vertex of two different edges land in the same map grid cells. This is a rather quick and low-cost operation.
|
||||
The merge by edge connection happens in a second pass for all still unmerged edges. All the free edges are checked for possible edge connections by both distance and angle which is rather costly.
|
||||
|
||||
So apart from the general rule to have as few polygon edges as possible, as many edges as possible should be merged by vertex upfront so only a few edges are left for the more costly edge connection calculation.
|
||||
The debug Navigation PerformanceMonitor can be used to get statistics on how many polygons and edges are available and how many of them are unmerged or not merged by vertex.
|
||||
If the ratio between vertex merged and edge connections is way off (vertex should be significantly higher) the navigation meshes are properly created or placed very inefficient.
|
282
tutorials/navigation/navigation_using_navigationagents.rst
Normal file
@ -0,0 +1,282 @@
|
||||
.. _doc_navigation_using_navigationagents:
|
||||
|
||||
Using NavigationAgents
|
||||
======================
|
||||
|
||||
NavigationsAgents are helper nodes that combine functionality
|
||||
for pathfinding, path following 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.
|
||||
|
||||
New NavigationAgent nodes will automatically join the default navigation map on the World2D/World3D.
|
||||
|
||||
NavigationsAgent nodes are optional and not a hard requirement to use the navigation system.
|
||||
Their entire functionality can be replaced with scripts and direct calls to the NavigationServer API.
|
||||
|
||||
NavigationAgent Pathfinding
|
||||
---------------------------
|
||||
|
||||
NavigationAgents query a new navigation path on their current navigation map when their ``target_position`` is set with a global position.
|
||||
|
||||
The result of the pathfinding can be influenced with the following properties.
|
||||
|
||||
- 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
|
||||
-----------------------------
|
||||
|
||||
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::
|
||||
|
||||
The navigation system never moves the parent node of a NavigationAgent.
|
||||
The movement is entirely in the hands of users and their custom scripts.
|
||||
|
||||
NavigationAgents have their own internal logic to proceed with the current path and call for updates.
|
||||
|
||||
The ``get_next_path_position()`` function is responsible for updating many of the agent's internal states and properties.
|
||||
The function should be repeatedly 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.
|
||||
|
||||
The following properties influence the path following behavior.
|
||||
|
||||
- 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 distance 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 too far away from the current path point segment.
|
||||
|
||||
The important updates are all triggered with the ``get_next_path_position()`` function
|
||||
when called in ``_physics_process()``.
|
||||
|
||||
NavigationAgents can be used with ``process`` but are still limited to a single update that happens in ``physics_process``.
|
||||
|
||||
Script examples for various nodes commonly used with NavigationAgents can be found further below.
|
||||
|
||||
Pathfollowing common problems
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are some common user problems and important caveats to consider when writing agent movement scripts.
|
||||
|
||||
- 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 navigation avoidance specific to NavigationAgents.
|
||||
|
||||
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
|
||||
|
||||
Use ``set_velocity()`` on the NavigationAgent node in ``_physics_process()`` to update the agent with the current velocity of the agent's parent node.
|
||||
|
||||
While avoidance is enabled on the agent the ``safe_velocity`` vector will be received with the velocity_computed signal every physics frame.
|
||||
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 ``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 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.
|
||||
|
||||
|
||||
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 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. 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 avoidance
|
||||
NavigationServer2D.agent_set_avoidance_enabled(agent, true)
|
||||
# Create avoidance callback
|
||||
NavigationServer2D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done"))
|
||||
|
||||
# Disable avoidance
|
||||
NavigationServer2D.agent_set_avoidance_enabled(agent, false)
|
||||
# Delete avoidance callback
|
||||
NavigationServer2D.agent_set_avoidance_callback(agent, Callable())
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends NavigationAgent3D
|
||||
|
||||
var agent: RID = get_rid()
|
||||
# Enable avoidance
|
||||
NavigationServer3D.agent_set_avoidance_enabled(agent, true)
|
||||
# Create avoidance callback
|
||||
NavigationServer3D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done"))
|
||||
# Switch to 3D avoidance
|
||||
NavigationServer3D.agent_set_use_3d_avoidance(agent, true)
|
||||
|
||||
# Disable avoidance
|
||||
NavigationServer3D.agent_set_avoidance_enabled(agent, false)
|
||||
# Delete avoidance callback
|
||||
NavigationServer3D.agent_set_avoidance_callback(agent, Callable())
|
||||
# Switch to 2D avoidance
|
||||
NavigationServer3D.agent_set_use_3d_avoidance(agent, false)
|
||||
|
||||
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
|
||||
|
||||
@export var movement_speed: float = 4.0
|
||||
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
|
||||
var movement_delta: float
|
||||
|
||||
func _ready() -> void:
|
||||
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
|
||||
|
||||
func set_movement_target(movement_target: Vector3):
|
||||
navigation_agent.set_target_position(movement_target)
|
||||
|
||||
func _physics_process(delta):
|
||||
if navigation_agent.is_navigation_finished():
|
||||
return
|
||||
|
||||
movement_delta = movement_speed * delta
|
||||
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
|
||||
var current_agent_position: Vector3 = global_position
|
||||
var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_delta
|
||||
if navigation_agent.avoidance_enabled:
|
||||
navigation_agent.set_velocity(new_velocity)
|
||||
else:
|
||||
_on_velocity_computed(new_velocity)
|
||||
|
||||
func _on_velocity_computed(safe_velocity: Vector3) -> void:
|
||||
global_position = global_position.move_toward(global_position + 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
|
||||
|
||||
@export var movement_speed: float = 4.0
|
||||
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
|
||||
|
||||
func _ready() -> void:
|
||||
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
|
||||
|
||||
func set_movement_target(movement_target: Vector3):
|
||||
navigation_agent.set_target_position(movement_target)
|
||||
|
||||
func _physics_process(delta):
|
||||
if navigation_agent.is_navigation_finished():
|
||||
return
|
||||
|
||||
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
|
||||
var current_agent_position: Vector3 = global_position
|
||||
var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed
|
||||
if navigation_agent.avoidance_enabled:
|
||||
navigation_agent.set_velocity(new_velocity)
|
||||
else:
|
||||
_on_velocity_computed(new_velocity)
|
||||
|
||||
func _on_velocity_computed(safe_velocity: Vector3):
|
||||
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
|
||||
|
||||
@export var movement_speed: float = 4.0
|
||||
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
|
||||
|
||||
func _ready() -> void:
|
||||
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
|
||||
|
||||
func set_movement_target(movement_target: Vector3):
|
||||
navigation_agent.set_target_position(movement_target)
|
||||
|
||||
func _physics_process(delta):
|
||||
if navigation_agent.is_navigation_finished():
|
||||
return
|
||||
|
||||
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
|
||||
var current_agent_position: Vector3 = global_position
|
||||
var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed
|
||||
if navigation_agent.avoidance_enabled:
|
||||
navigation_agent.set_velocity(new_velocity)
|
||||
else:
|
||||
_on_velocity_computed(new_velocity)
|
||||
|
||||
func _on_velocity_computed(safe_velocity: Vector3):
|
||||
linear_velocity = safe_velocity
|
67
tutorials/navigation/navigation_using_navigationlayers.rst
Normal file
@ -0,0 +1,67 @@
|
||||
.. _doc_navigation_advanced_using_navigationlayers:
|
||||
|
||||
Using NavigationLayers
|
||||
======================
|
||||
|
||||
NavigationLayers are an optional feature to further control which navigation meshes are considered in a path query and which regions can be connected.
|
||||
They work similar to how physics layers control collision between collision objects or how visual layers control what is rendered to the Viewport.
|
||||
|
||||
NavigationLayers can be named in the ``ProjectSettings`` the same as PhysicsLayers or VisualLayers.
|
||||
|
||||
.. image:: img/navigationlayers_naming.png
|
||||
|
||||
If two regions have not a single compatible layer they will not be merged by the NavigationServer. See :ref:`doc_navigation_connecting_navmesh` for more information on merging navmesh.
|
||||
|
||||
If a region has not a single compatible navigation layer with the ``navigation_layers`` parameter of a path query this regions navigation mesh will be skipped in pathfinding.
|
||||
See :ref:`doc_navigation_using_navigationpaths` for more information on querying the NavigationServer for paths.
|
||||
|
||||
NavigationLayers are a single ``int`` value that is used as a ``bitmask``.
|
||||
Many navigation related nodes have ``set_navigation_layer_value()`` and
|
||||
``get_navigation_layer_value()`` functions to set and get a layer number directly
|
||||
without the need for more complex bitwise operations.
|
||||
|
||||
In scripts the following helper functions can be used to work with the navigation_layers bitmask.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func change_layers():
|
||||
var region: NavigationRegion3D = get_node("NavigationRegion3D")
|
||||
# enables 4-th layer for this region
|
||||
region.navigation_layers = enable_bitmask_inx(region.navigation_layers, 4)
|
||||
# disables 1-rst layer for this region
|
||||
region.navigation_layers = disable_bitmask_inx(region.navigation_layers, 1)
|
||||
|
||||
var agent: NavigationAgent3D = get_node("NavigationAgent3D")
|
||||
# make future path queries of this agent ignore regions with 4-th layer
|
||||
agent.navigation_layers = disable_bitmask_inx(agent.navigation_layers, 4)
|
||||
|
||||
var path_query_navigation_layers: int = 0
|
||||
path_query_navigation_layers = enable_bitmask_inx(path_query_navigation_layers, 2)
|
||||
# get a path that only considers 2-nd layer regions
|
||||
var path: PoolVector3Array = NavigationServer3D.map_get_path(
|
||||
map,
|
||||
start_position,
|
||||
target_position,
|
||||
true,
|
||||
path_query_navigation_layers
|
||||
)
|
||||
|
||||
static func is_bitmask_inx_enabled(_bitmask: int, _index: int) -> bool:
|
||||
return _bitmask & (1 << _index) != 0
|
||||
|
||||
static func enable_bitmask_inx(_bitmask: int, _index: int) -> int:
|
||||
return _bitmask | (1 << _index)
|
||||
|
||||
static func disable_bitmask_inx(_bitmask: int, _index: int) -> int:
|
||||
return _bitmask & ~(1 << _index)
|
||||
|
||||
Changing navigation layers for path queries is a performance friendly alternative to
|
||||
enabling / disabling entire navigation regions. Compared to region changes a
|
||||
navigation path query with different navigation layers does not
|
||||
trigger large scale updates on the NavigationServer.
|
||||
|
||||
Changing the navigation layers of NavigationAgent nodes will have an immediate
|
||||
effect on the next path query. Changing the navigation layers of
|
||||
regions will have an immediate effect on the region but any new region
|
||||
connect or disconnect will only be in effect after the next physics_frame.
|
53
tutorials/navigation/navigation_using_navigationlinks.rst
Normal file
@ -0,0 +1,53 @@
|
||||
.. _doc_navigation_using_navigationlinks:
|
||||
|
||||
Using NavigationLinks
|
||||
=====================
|
||||
|
||||
.. image:: img/nav_navmesh_links.png
|
||||
|
||||
NavigationLinks are used to connect navigation mesh polygons from :ref:`NavigationRegion2D<class_NavigationRegion2D>`
|
||||
and :ref:`NavigationRegion3D<class_NavigationRegion3D>` over arbitrary distances for pathfinding.
|
||||
|
||||
NavigationLinks are also used to consider movement shortcuts in pathfinding available through
|
||||
interacting with gameplay objects e.g. ladders, jump pads or teleports.
|
||||
|
||||
2D and 3D versions of NavigationJumplinks nodes are available as
|
||||
:ref:`NavigationLink2D<class_NavigationLink2D>` and
|
||||
:ref:`NavigationLink3D<class_NavigationLink3D>` respectively.
|
||||
|
||||
Different NavigationRegions can connect their navigation meshes without the need for a NavigationLink
|
||||
as long as they are within navigation map ``edge_connection_margin`` and have compatible ``navigation_layers``.
|
||||
As soon as the distance becomes too large, building valid connections becomes a problem - a problem that NavigationLinks can solve.
|
||||
|
||||
See :ref:`doc_navigation_using_navigationregions` to learn more about the use of navigation regions.
|
||||
See :ref:`doc_navigation_connecting_navmesh` to learn more about how to connect navigation meshes.
|
||||
|
||||
.. image:: img/nav_link_properties.png
|
||||
|
||||
NavigationLinks share many properties with NavigationRegions like ``navigation_layers``.
|
||||
NavigationLinks add a single connection between two positions over an arbitrary distance
|
||||
compared to NavigationRegions that add a more local traversable area with a navigation mesh resource.
|
||||
|
||||
NavigationLinks have a ``start_position`` and ``end_position`` and can go in both directions when ``bidirectional`` is enabled.
|
||||
When placed a navigationlink connects the navigation mesh polygons closest to its ``start_position`` and ``end_position`` within search radius for pathfinding.
|
||||
|
||||
The polygon search radius can be configured globally in the ProjectSettings under ``navigation/2d_or_3d/default_link_connection_radius``
|
||||
or set for each navigation ``map`` individually using the ``NavigationServer.map_set_link_connection_radius()`` function.
|
||||
|
||||
Both ``start_position`` and ``end_position`` have debug markers in the Editor.
|
||||
The visible radius of a position shows the polygon search radius.
|
||||
All navigation mesh polygons inside are compared and the closest is picked for the edge connection.
|
||||
If no valid polygon is found within the search radius the navigation link gets disabled.
|
||||
|
||||
.. image:: img/nav_link_debug_visuals.png
|
||||
|
||||
The link debug visuals can be changed in the Editor :ref:`ProjectSettings<class_ProjectSettings>` under ``debug/shapes/navigation``.
|
||||
The visibility of the debug can also be controlled in the Editor 3D Viewport gizmo menu.
|
||||
|
||||
.. note::
|
||||
|
||||
NavigationLinks do not move agents between the two link positions by themselves.
|
||||
|
||||
A navigation link does not provide any automated movement through the link. Instead, when
|
||||
an agent reaches the position of a link, game code needs to react (e.g. through area triggers) and provide means for the agent
|
||||
to move through the link to end up at the links other position (e.g. through teleport or animation) to continue along the path.
|
77
tutorials/navigation/navigation_using_navigationmaps.rst
Normal file
@ -0,0 +1,77 @@
|
||||
.. _doc_navigation_using_navigationmaps:
|
||||
|
||||
Using NavigationMaps
|
||||
====================
|
||||
|
||||
.. image:: img/nav_maps.png
|
||||
|
||||
A NavigationMap is an abstract navigation world on the NavigationServer identified by a NavigationServer :ref:`RID<class_RID>`.
|
||||
|
||||
A map can hold and connect a near infinite number of navigation regions with navigation meshes to build the traversable areas of a game world for pathfinding.
|
||||
|
||||
A map can be joined by avoidance agents to process collision avoidance between the avoidance agents.
|
||||
|
||||
.. note::
|
||||
|
||||
Different NavigationMaps are completely isolated from each other but navigation regions
|
||||
and avoidance agents can switch between different maps once every server synchronization.
|
||||
|
||||
Default navigation maps
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default Godot creates a navigation map RID for each :ref:`World2D<class_World2D>` and :ref:`World3D<class_World3D>` of the root viewport.
|
||||
|
||||
The 2D default navigation ``map`` can be obtained with ``get_world_2d().get_navigation_map()`` from any :ref:`Node2D<class_Node2D>` inheriting Node.
|
||||
|
||||
The 3D default navigation ``map`` can be obtained with ``get_world_3d().get_navigation_map()`` from any :ref:`Node3D<class_Node3D>` inheriting Node.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node2D
|
||||
|
||||
var default_2d_navigation_map_rid: RID = get_world_2d().get_navigation_map()
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
|
||||
var default_3d_navigation_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
|
||||
Creating new navigation maps
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The NavigationServer can create and support as many navigation maps as are required for specific gameplay.
|
||||
Additional navigation maps are created and maintained by using the NavigationServer API
|
||||
directly e.g. to support different avoidance agent or actor locomotion types.
|
||||
|
||||
For example uses of different navigation maps see :ref:`doc_navigation_different_actor_types` and :ref:`doc_navigation_different_actor_locomotion`.
|
||||
|
||||
Each navigation map synchronizes queued changes to its navigation regions and avoidance agents individually.
|
||||
A navigation map that has not received changes will consume little to no processing time.
|
||||
Navigation regions and avoidance agents can only be part of a single navigations map but they can switch maps at any time.
|
||||
|
||||
.. note::
|
||||
|
||||
A navigation map switch will take effect only after the next NavigationServer synchronization.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node2D
|
||||
|
||||
var new_navigation_map: RID = NavigationServer2D.map_create()
|
||||
NavigationServer2D.map_set_active(true)
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
|
||||
var new_navigation_map: RID = NavigationServer3D.map_create()
|
||||
NavigationServer3D.map_set_active(true)
|
||||
|
||||
.. note::
|
||||
|
||||
There is no difference between navigation maps created with the NavigationServer2D API or the NavigationServer3D API.
|
268
tutorials/navigation/navigation_using_navigationmeshes.rst
Normal file
@ -0,0 +1,268 @@
|
||||
.. _doc_navigation_using_navigationmeshes:
|
||||
|
||||
Using NavigationMeshes
|
||||
======================
|
||||
|
||||
2D and 3D version of the navigation mesh are available as
|
||||
:ref:`NavigationPolygon<class_NavigationPolygon>` and
|
||||
:ref:`NavigationMesh<class_NavigationMesh>` respectively.
|
||||
|
||||
.. note::
|
||||
|
||||
A navigation mesh describes the traversable safe area for an agent with its center position at zero radius.
|
||||
If you want pathfinding to account for an agent's (collision) size you need to shrink the navigation mesh accordingly.
|
||||
|
||||
Navigation works independent from other engine parts like rendering and physics. A navigation mesh is the data format to exchange information from those other systems as it describes the traversable safe area for a specific agent. All the necessary information from other engine parts need to be already factored in when creating a navigation mesh. E.g. like visuals that an agent should not clip through or physics collision shapes that an agent should not collide with. This process of factoring in all those wanted navigation restrictions from other engine parts like visuals and collision is commonly called navigation mesh baking.
|
||||
|
||||
If you experience clipping or collision problems while following navigation paths always remember that you need to tell the navigation system through an appropriated navigation mesh what your intentions are. By itself the navigation system will never know "this is a tree / rock / wall collision shape or visual mesh" because it only knows "here I was told I can path safely cause it is on navigation mesh".
|
||||
|
||||
.. _doc_navigation_navmesh_baking:
|
||||
|
||||
Creating 2D NavigationMeshes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Navigation meshes in the 2D editor are created with the help of the NavigationPolygon draw tools
|
||||
that appear in the top bar of the editor when a NavigationRegion2D is selected.
|
||||
|
||||
.. image:: img/nav_polydrawtool.png
|
||||
|
||||
The NavigationPolygon draw tools can be used to create and edit navigation meshes by defining ``outline`` polygons.
|
||||
The outline polygons are later converted to real NavigationMesh resources for the NavigationServer regions.
|
||||
|
||||
.. image:: img/nav_polymatroschka.png
|
||||
|
||||
Multiple outlines can be added to the same NavPolygon resource as long as they **do not intersect or overlap**.
|
||||
Each additional outline will cut a hole in the polygon created by the larger outline.
|
||||
If the larger polygon is already a hole it will create a new navigation mesh polygon inside.
|
||||
|
||||
Outlines are not a replacement if the intention is to merge aligned polygons e.g. from grid cells.
|
||||
Outlines, as the name would suggest, cannot intersect each other or have any overlapping vertex positions.
|
||||
|
||||
.. image:: img/nav_polyoutlinefail.png
|
||||
|
||||
Outline layouts like seen in this picture will fail the convex partitioning required by the navigation mesh generation.
|
||||
In this layout cases the outline tool cannot be used. Use the :ref:`Geometry2D<class_Geometry2D>` class for
|
||||
polygon merge or intersect operations to create a valid merged mesh for navigation.
|
||||
|
||||
.. note::
|
||||
|
||||
The NavigationServer does not connect navigation mesh islands from the same NavigationMesh resource.
|
||||
Do not create multiple disconnected islands in the same NavigationRegion2D and NavPoly resource if they should be later connected.
|
||||
|
||||
For 2D no similar navigation mesh baking with geometry parsing exists like in 3D.
|
||||
The Geometry2D class functions for offset, merge, intersect and clip can be used
|
||||
to shrink or enlarge existing NavigationPolygons to different actor sizes.
|
||||
|
||||
Creating 3D NavigationMeshes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: img/baked_navmesh.png
|
||||
|
||||
Navigation meshes in the 3D editor are created with the help of the
|
||||
:ref:`NavigationMeshGenerator<class_NavigationMeshGenerator>` singleton
|
||||
and the NavigationMesh bake settings that appear in the editor inspector.
|
||||
|
||||
NavigationMesh baking is the process of creating a simplified mesh used for pathfinding out of (complex) 3D level geometry.
|
||||
For this process Godot parses scene geometry and hands the raw mesh or collision data to the
|
||||
third-party ReCast library for processing and creation of the final navigationmesh.
|
||||
|
||||
The resulting NavigationMesh is an approximation of the source geometry surfaces
|
||||
for both performance and technical reasons. Do not expect the NavigationMesh
|
||||
to perfectly follow the original surfaces. Especially navigation polygons placed
|
||||
over ramps will not keep an equal distance to the ground surface. To align an
|
||||
actor perfectly with the ground use other means like physics.
|
||||
|
||||
.. warning::
|
||||
|
||||
Meshes need to be triangulated to work as navigation meshes. Other mesh face formats like quad or ngon are not supported.
|
||||
|
||||
NavigationMesh rebaking at runtime
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To rebake a ``NavigationMesh`` at runtime, use the NavigationRegion3D.bake_navigation_mesh() function.
|
||||
Another option is to use the NavigationMeshGenerator.bake() Singleton function with the NavigationMesh resource directly.
|
||||
If the navigation mesh resource is already prepared, the region can be updated with the NavigationServer3D API directly as well.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends NavigationRegion3D
|
||||
|
||||
func update_navigation_mesh():
|
||||
|
||||
# use bake and update function of region
|
||||
var on_thread: bool = true
|
||||
bake_navigation_mesh(on_thread)
|
||||
|
||||
# or use the NavigationMeshGenerator Singleton
|
||||
var _navigationmesh: NavigationMesh = navigation_mesh
|
||||
NavigationMeshGenerator.bake(_navigationmesh, self)
|
||||
# remove old resource first to trigger a full update
|
||||
navigation_mesh = null
|
||||
navigation_mesh = _navigationmesh
|
||||
|
||||
# or use NavigationServer API to update region with prepared navigation mesh
|
||||
var region_rid: RID = get_region_rid()
|
||||
NavigationServer3D.region_set_navigation_mesh(region_rid, navigation_mesh)
|
||||
|
||||
.. note::
|
||||
|
||||
Baking a NavigationMesh at runtime is a costly operation.
|
||||
Complex navigation mesh take some time to bake and if done on the main thread can freeze a game.
|
||||
(Re)baking a large navigation mesh is preferably done in a separate thread.
|
||||
|
||||
.. warning::
|
||||
|
||||
Property values on a NavigationMesh resource like ``cell_size`` need
|
||||
to match the actual mesh data stored inside in order to merge
|
||||
different navigation meshes without issues.
|
||||
|
||||
NavigationRegion2D and Navigation3D both use meshes to mark traversable areas, only the tools to create them are different.
|
||||
|
||||
For 2D NavigationPolygon resources are used to draw outline points in the editor. From these outline points the NavigationServer2D creates a mesh to upload navigation data to the NavigationServer.
|
||||
|
||||
For 3D NavigationMesh resources are used. Instead of providing draw tools the 3D variant
|
||||
provides an extensive amount of parameters to bake a navigation mesh directly from 3D source geometry.
|
||||
|
||||
.. note::
|
||||
|
||||
Technically there is no hard distinction between 2D and 3D how to use the given toolsets to create flat navigation meshes. The 2D drawing tool can be used to create a flat 3D navmesh and the 3D baking tool can be used to parse flat 3D geometry into 2D appropriated navigationmeshes.
|
||||
|
||||
2D Navmesh from CollisionPolygons
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following script parses all child nodes of a NavigationRegion2D for CollisionPolygons
|
||||
and bakes their shape into the NavigationPolygon. As the NavigationPolygon creates the
|
||||
navigationmesh from outline data the shapes cannot overlap.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends NavigationRegion2D
|
||||
|
||||
var new_navigation_polygon: NavigationPolygon = get_navigation_polygon()
|
||||
|
||||
func _ready():
|
||||
|
||||
parse_2d_collisionshapes(self)
|
||||
|
||||
new_navigation_polygon.make_polygons_from_outlines()
|
||||
set_navigation_polygon(new_navigation_polygon)
|
||||
|
||||
func parse_2d_collisionshapes(root_node: Node2D):
|
||||
|
||||
for node in root_node.get_children():
|
||||
|
||||
if node.get_child_count() > 0:
|
||||
parse_2d_collisionshapes(node)
|
||||
|
||||
if node is CollisionPolygon2D:
|
||||
|
||||
var collisionpolygon_transform: Transform2D = node.get_global_transform()
|
||||
var collisionpolygon: PackedVector2Array = node.polygon
|
||||
|
||||
var new_collision_outline: PackedVector2Array = collisionpolygon_transform * collisionpolygon
|
||||
|
||||
new_navigation_polygon.add_outline(new_collision_outline)
|
||||
|
||||
Procedual 2D NavigationMesh
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following script creates a new 2D navigation region and fills it with procedual generated navigation mesh data from a NavigationPolygon resource.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node2D
|
||||
|
||||
var new_2d_region_rid: RID = NavigationServer2D.region_create()
|
||||
|
||||
var default_2d_map_rid: RID = get_world_2d().get_navigation_map()
|
||||
NavigationServer2D.region_set_map(new_2d_region_rid, default_2d_map_rid)
|
||||
|
||||
var new_navigation_polygon: NavigationPolygon = NavigationPolygon.new()
|
||||
var new_outline: PackedVector2Array = PackedVector2Array([
|
||||
Vector2(0.0, 0.0),
|
||||
Vector2(50.0, 0.0),
|
||||
Vector2(50.0, 50.0),
|
||||
Vector2(0.0, 50.0),
|
||||
])
|
||||
new_navigation_polygon.add_outline(new_outline)
|
||||
new_navigation_polygon.make_polygons_from_outlines()
|
||||
|
||||
NavigationServer2D.region_set_navigation_polygon(new_2d_region_rid, new_navigation_polygon)
|
||||
|
||||
Procedual 3D NavigationMesh
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following script creates a new 3D navigation region and fills it with procedual generated navigation mesh data from a NavigationMesh resource.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
|
||||
var new_3d_region_rid: RID = NavigationServer3D.region_create()
|
||||
|
||||
var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
NavigationServer3D.region_set_map(new_3d_region_rid, default_3d_map_rid)
|
||||
|
||||
var new_navigation_mesh: NavigationMesh = NavigationMesh.new()
|
||||
# Add vertices for a triangle.
|
||||
new_navigation_mesh.vertices = PackedVector3Array([
|
||||
Vector3(-1.0, 0.0, 1.0),
|
||||
Vector3(1.0, 0.0, 1.0),
|
||||
Vector3(1.0, 0.0, -1.0)
|
||||
])
|
||||
# Add indices for the polygon.
|
||||
new_navigation_mesh.add_polygon(
|
||||
PackedInt32Array([0, 1, 2])
|
||||
)
|
||||
NavigationServer3D.region_set_navigation_mesh(new_3d_region_rid, new_navigation_mesh)
|
||||
|
||||
Navmesh for 3D GridMaps
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following script creates a new 3D navigation mesh for each GridMap items, clears the current grid cells and adds new procedual grid cells with the new navigation mesh.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends GridMap
|
||||
|
||||
# enable navigation mesh for grid items
|
||||
set_bake_navigation(true)
|
||||
|
||||
# get grid items, create and set a new navigation mesh for each item in the MeshLibrary
|
||||
var gridmap_item_list: PackedInt32Array = mesh_library.get_item_list()
|
||||
for item in gridmap_item_list:
|
||||
var new_item_navigation_mesh: NavigationMesh = NavigationMesh.new()
|
||||
# Add vertices and polygons that describe the traversable ground surface.
|
||||
# E.g. for a convex polygon that resembles a flat square.
|
||||
new_item_navigation_mesh.vertices = PackedVector3Array([
|
||||
Vector3(-1.0, 0.0, 1.0),
|
||||
Vector3(1.0, 0.0, 1.0),
|
||||
Vector3(1.0, 0.0, -1.0),
|
||||
Vector3(-1.0, 0.0, -1.0),
|
||||
])
|
||||
new_item_navigation_mesh.add_polygon(
|
||||
PackedInt32Array([0, 1, 2, 3])
|
||||
)
|
||||
mesh_library.set_item_navigation_mesh(item, new_item_navigation_mesh)
|
||||
mesh_library.set_item_navigation_mesh_transform(item, Transform3D())
|
||||
|
||||
# clear the cells
|
||||
clear()
|
||||
|
||||
# add procedual cells using the first item
|
||||
var _position: Vector3i = Vector3i(global_transform.origin)
|
||||
var _item: int = 0
|
||||
var _orientation: int = 0
|
||||
for i in range(0,10):
|
||||
for j in range(0,10):
|
||||
_position.x = i
|
||||
_position.z = j
|
||||
gridmap.set_cell_item(_position, _item, _orientation)
|
||||
_position.x = -i
|
||||
_position.z = -j
|
||||
gridmap.set_cell_item(_position, _item, _orientation)
|
105
tutorials/navigation/navigation_using_navigationobstacles.rst
Normal file
@ -0,0 +1,105 @@
|
||||
.. _doc_navigation_using_navigationobstacles:
|
||||
|
||||
Using NavigationObstacles
|
||||
=========================
|
||||
|
||||
NavigationObstacles can be used either as static or dynamic obstacles to affect avoidance controlled agents.
|
||||
|
||||
- When used statically NavigationObstacles constrain avoidance controlled agents outside or inside a polygon defined area.
|
||||
- When used dynamically NavigationObstacles push away avoidance controlled agents in a radius around them.
|
||||
|
||||
2D and 3D versions of NavigationObstacles nodes are available as
|
||||
:ref:`NavigationObstacle2D<class_NavigationObstacle2D>` and
|
||||
:ref:`NavigationObstacle3D<class_NavigationObstacle3D>` respectively.
|
||||
|
||||
.. note::
|
||||
NavigationObstacles do not change or influence the pathfinding in any way.
|
||||
NavigationObstacles only affect the avoidance velocities of agents controlled by avoidance.
|
||||
|
||||
Static obstacles
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
A NavigationObstacle is considered static when its ``vertices`` property is populated with an outline array of positions to form a polygon.
|
||||
|
||||
.. image:: img/nav_static_obstacle_build.gif
|
||||
|
||||
- Static obstacles act as hard do-not-cross boundaries for avoidance using agents, e.g. similar to physics collision but for avoidance.
|
||||
- Static obstacles define their boundaries with an array of outline ``vertices`` (positions), and in case of 3D with an additional ``height`` property.
|
||||
- Static obstacles only work for agents that use the 2D avoidance mode.
|
||||
- Static obstacles define through winding order of the vertices if agents are pushed out or sucked in.
|
||||
- Static obstacles can not change their position. They can only be warped to a new position and rebuild from scratch. Static obstacles as a result are ill-suited for usages where the position is changed every frame as the constant rebuild has a high performance cost.
|
||||
- Static obstacles that are warped to another position can not be predicted by agents. This creates the risk of getting agents stuck should a static obstacle be warped on top of agents.
|
||||
|
||||
When the 2D avoidance is used in 3D the y-axis of Vector3 vertices is ignored. Instead, the global y-axis position of the obstacle is used as the elevation level. Agents will ignore static obstacles in 3D that are below or above them. This is automatically determined by global y-axis position of both obstacle and agent as the elevation level as well as their respective height properties.
|
||||
|
||||
Dynamic obstacles
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
A NavigationObstacle is considered dynamic when its ``radius`` property is greater than zero.
|
||||
|
||||
- Dynamic obstacles act as a soft please-move-away-from-me object for avoidance using agents, e.g. similar to how they avoid other agents.
|
||||
- Dynamic obstacles define their boundaries with a single ``radius`` for a 2D circle, or in case of 3D avoidance a sphere shape.
|
||||
- Dynamic obstacles can change their position every frame without additional performance cost.
|
||||
- Dynamic obstacles with a set velocity can be predicted in their movement by agents.
|
||||
- Dynamic obstacles are not a reliable way to constrain agents in crowded or narrow spaces.
|
||||
|
||||
While both static and dynamic properties can be active at the same time on the same obstacle this is not recommended for performance.
|
||||
Ideally when an obstacle is moving the static vertices are removed and instead the radius activated. When the obstacle reaches the new final position it should gradually enlarge its radius to push all other agents away. With enough created save space around the obstacle it should add the static vertices again and remove the radius. This helps to avoid getting agents stuck in the suddenly appearing static obstacle when the rebuild static boundary is finished.
|
||||
|
||||
Similar to agents the obstacles can make use of the ``avoidance_layers`` bitmask.
|
||||
All agents with a matching bit on their own avoidance mask will avoid the obstacle.
|
||||
|
||||
Procedual obstacles
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
New obstacles can be created without a Node directly on the NavigationServer.
|
||||
|
||||
Obstacles created with scripts require at least a ``map`` and a ``position``.
|
||||
For dynamic use a ``radius`` is required.
|
||||
For static use an array of ``vertices`` is required.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# For 2D
|
||||
|
||||
# create a new "obstacle" and place it on the default navigation map.
|
||||
var new_obstacle_rid: RID = NavigationServer2D.obstacle_create()
|
||||
var default_2d_map_rid: RID = get_world_2d().get_navigation_map()
|
||||
|
||||
NavigationServer2D.obstacle_set_map(new_obstacle_rid, default_2d_map_rid)
|
||||
NavigationServer2D.obstacle_set_position(new_obstacle_rid, global_position)
|
||||
|
||||
# Use obstacle dynamic by increasing radius above zero.
|
||||
NavigationServer2D.obstacle_set_radius(new_obstacle_rid, 5.0)
|
||||
|
||||
# Use obstacle static by adding a square that pushes agents out.
|
||||
var outline = PackedVector2Array([Vector2(-100, -100), Vector2(100, -100), Vector2(100, 100), Vector2(-100, 100)])
|
||||
NavigationServer2D.obstacle_set_vertices(new_obstacle_rid, outline)
|
||||
|
||||
# Enable the obstacle.
|
||||
NavigationServer2D.obstacle_set_avoidance_enabled(new_obstacle_rid, true)
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# For 3D
|
||||
|
||||
# Create a new "obstacle" and place it on the default navigation map.
|
||||
var new_obstacle_rid: RID = NavigationServer3D.obstacle_create()
|
||||
var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
|
||||
NavigationServer3D.obstacle_set_map(new_obstacle_rid, default_3d_map_rid)
|
||||
NavigationServer3D.obstacle_set_position(new_obstacle_rid, global_position)
|
||||
|
||||
# Use obstacle dynamic by increasing radius above zero.
|
||||
NavigationServer3D.obstacle_set_radius(new_obstacle_rid, 0.5)
|
||||
|
||||
# Use obstacle static by adding a square that pushes agents out.
|
||||
var outline = PackedVector3Array([Vector3(-5, 0, -5), Vector3(5, 0, -5), Vector3(5, 0, 5), Vector3(-5, 0, 5)])
|
||||
NavigationServer3D.obstacle_set_vertices(new_obstacle_rid, outline)
|
||||
# Set the obstacle height on the y-axis.
|
||||
NavigationServer3D.obstacle_set_height(new_obstacle_rid, 1.0)
|
||||
|
||||
# Enable the obstacle.
|
||||
NavigationServer3D.obstacle_set_avoidance_enabled(new_obstacle_rid, true)
|
@ -0,0 +1,62 @@
|
||||
.. _doc_navigation_using_navigationpathqueryobjects:
|
||||
|
||||
Using NavigationPathQueryObjects
|
||||
================================
|
||||
|
||||
``NavigationPathQueryObjects`` can be used together with ``NavigationServer.query_path()``
|
||||
to obtain a heavily **customized** navigation path including optional **meta data** about the path.
|
||||
|
||||
This requires more setup compared to obtaining a normal NavigationPath but lets you tailor
|
||||
the pathfinding and provided path data to the different needs of a project.
|
||||
|
||||
NavigationPathQueryObjects consist of a pair of objects, a ``NavigationPathQueryParameters`` object holding the customization options
|
||||
for the query and a ``NavigationPathQueryResult`` that receives (regular) updates with the resulting path and meta data from the query.
|
||||
|
||||
2D and 3D versions of ``NavigationPathQueryParameters`` are available as
|
||||
:ref:`NavigationPathQueryParameters2D<class_NavigationPathQueryParameters2D>` and
|
||||
:ref:`NavigationPathQueryParameters3D<class_NavigationPathQueryParameters3D>` respectively.
|
||||
|
||||
2D and 3D versions of ``NavigationPathQueryResult`` are available as
|
||||
:ref:`NavigationPathQuerResult2D<class_NavigationPathQueryResult2D>` and
|
||||
:ref:`NavigationPathQueryResult3D<class_NavigationPathQueryResult3D>` respectively.
|
||||
|
||||
Both parameters and result are used as a pair with the ``NavigationServer.query_path()`` function.
|
||||
|
||||
For the available customization options and their use see the class doc of the parameters.
|
||||
|
||||
While not a strict requirement, both objects are intended to be created once in advance, stored in a
|
||||
persistent variable for the agent and reused for every followup path query with updated parameters.
|
||||
This reuse avoids performance implications from frequent object creation if a project
|
||||
has a large quantity of simultaneous agents that regularly update their paths.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# prepare query objects
|
||||
var query_parameters = NavigationPathQueryParameters2D.new()
|
||||
var query_result = NavigationPathQueryResult2D.new()
|
||||
|
||||
# update parameters object
|
||||
query_parameters.map = get_world_2d().get_navigation_map()
|
||||
query_parameters.start_position = agent2d_current_global_position
|
||||
query_parameters.target_position = agent2d_target_global_position
|
||||
|
||||
# update result object
|
||||
NavigationServer2D.query_path(query_parameters, query_result)
|
||||
var path: PackedVector2Array = query_result.get_path()
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# prepare query objects
|
||||
var query_parameters = NavigationPathQueryParameters3D.new()
|
||||
var query_result = NavigationPathQueryResult3D.new()
|
||||
|
||||
# update parameters object
|
||||
query_parameters.map = get_world_3d().get_navigation_map()
|
||||
query_parameters.start_position = agent3d_current_global_position
|
||||
query_parameters.target_position = agent3d_target_global_position
|
||||
|
||||
# update result object
|
||||
NavigationServer3D.query_path(query_parameters, query_result)
|
||||
var path: PackedVector3Array = query_result.get_path()
|
126
tutorials/navigation/navigation_using_navigationpaths.rst
Normal file
@ -0,0 +1,126 @@
|
||||
.. _doc_navigation_using_navigationpaths:
|
||||
|
||||
Using NavigationPaths
|
||||
=====================
|
||||
|
||||
Obtaining a Navigationpath
|
||||
--------------------------
|
||||
|
||||
Navigation paths can be directly queried from the NavigationServer and do not require any
|
||||
additional nodes or objects as long as the navigation map has a navigationmesh to work with.
|
||||
|
||||
To obtain a 2D path, use ``NavigationServer2D.map_get_path(map, from, to, optimize, navigation_layers)``.
|
||||
|
||||
To obtain a 3D path, use ``NavigationServer3D.map_get_path(map, from, to, optimize, navigation_layers)``.
|
||||
|
||||
For more customizable navigation path queries that require additional setup see :ref:`doc_navigation_using_navigationpathqueryobjects`.
|
||||
|
||||
One of the required parameters for the query is the RID of the navigation map.
|
||||
Each game ``World`` has a default navigation map automatically created.
|
||||
The default navigation maps can be retrieved with ``get_world_2d().get_navigation_map()`` from
|
||||
any Node2D inheriting node or ``get_world_3d().get_navigation_map()`` from any Node3D inheriting node.
|
||||
The second and third parameters are the starting position and the target position as Vector2 for 2D or Vector3 for 3D.
|
||||
|
||||
If the ``optimized`` parameter is ``true``, path positions will be shortened along polygon
|
||||
corners with an additional funnel algorithm pass. This works well for free movement
|
||||
on navigationmeshes with unequal sized polygons as the path will hug around corners
|
||||
along the polygon corridor found by the A* algorithm. With small cells the A* algorithm
|
||||
creates a very narrow funnel corridor that can create ugly corner paths when used with grids.
|
||||
|
||||
If the ``optimized`` parameter is ``false``, path positions will be placed at the center of each polygon edge.
|
||||
This works well for pure grid movement on navmeshes with equal sized polygons as the path will go through the center of the grid cells.
|
||||
Outside of grids due to polygons often covering large open areas with a single, long edge this can create paths with unnecessary long detours.
|
||||
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node2D
|
||||
# basic query for a navigation path in 2D using the default navigation map
|
||||
var default_2d_map_rid: RID = get_world_2d().get_navigation_map()
|
||||
var start_position: Vector2 = Vector2(0.0, 0.0)
|
||||
var target_position: Vector2 = Vector2(5.0, 0.0)
|
||||
var path: PackedVector2Array = NavigationServer2D.map_get_path(
|
||||
default_2d_map_rid,
|
||||
start_position,
|
||||
target_position,
|
||||
true
|
||||
)
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
# basic query for a navigation path in 3D using the default navigation map
|
||||
var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
var start_position: Vector3 = Vector3(0.0, 0.0, 0.0)
|
||||
var target_position: Vector3 = Vector3(5.0, 0.0, 3.0)
|
||||
var path: PackedVector3Array = NavigationServer3D.map_get_path(
|
||||
default_3d_map_rid,
|
||||
start_position,
|
||||
target_position,
|
||||
true
|
||||
)
|
||||
|
||||
A returned ``path`` by the NavigationServer will be a ``PackedVector2Array`` for 2D or a ``PackedVector3Array`` for 3D.
|
||||
These are just a memory-optimized ``Array`` of vector positions.
|
||||
All position vectors inside the array are guaranteed to be inside a NavigationPolygon or NavigationMesh.
|
||||
The path array, if not empty, has the navigationmesh position closest to the starting position at the first index ``path[0]`` position.
|
||||
The closest available navigationmesh position to the target position is the last index ``path[path.size()-1]`` position.
|
||||
All index between are the pathpoints that an actor should follow to reach the target without leaving the navigation mesh.
|
||||
|
||||
.. note::
|
||||
|
||||
If the target position is on a different navigation mesh that is not merged or connected
|
||||
the navigation path will lead to the closest possible position on the starting position navigation mesh.
|
||||
|
||||
The following script moves a Node3D inheriting node along a navigation path using
|
||||
the default navigation map by setting the target position with ``set_movement_target()``.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
var movement_speed: float = 4.0
|
||||
var movement_delta: float
|
||||
var path_point_margin: float = 0.5
|
||||
var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
|
||||
var current_path_index: int = 0
|
||||
var current_path_point: Vector3
|
||||
var current_path: PackedVector3Array
|
||||
|
||||
func set_movement_target(target_position: Vector3):
|
||||
|
||||
var start_position: Vector3 = global_transform.origin
|
||||
|
||||
current_path = NavigationServer3D.map_get_path(
|
||||
default_3d_map_rid,
|
||||
start_position,
|
||||
target_position,
|
||||
true
|
||||
)
|
||||
|
||||
if not current_path.is_empty():
|
||||
current_path_index = 0
|
||||
current_path_point = current_path[0]
|
||||
|
||||
func _physics_process(delta):
|
||||
|
||||
if current_path.is_empty():
|
||||
return
|
||||
|
||||
movement_delta = move_speed * delta
|
||||
|
||||
if global_transform.origin.distance_to(current_path_point) <= path_point_margin:
|
||||
current_path_index += 1
|
||||
if current_path_index >= current_path.size():
|
||||
current_path = []
|
||||
current_path_index = 0
|
||||
current_path_point = global_transform.origin
|
||||
return
|
||||
|
||||
current_path_point = current_path[current_path_index]
|
||||
|
||||
var new_velocity: Vector3 = (current_path_point - global_transform.origin).normalized() * movement_delta
|
||||
|
||||
global_transform.origin = global_transform.origin.move_toward(global_transform.origin + new_velocity, movement_delta)
|
73
tutorials/navigation/navigation_using_navigationregions.rst
Normal file
@ -0,0 +1,73 @@
|
||||
.. _doc_navigation_using_navigationregions:
|
||||
|
||||
Using NavigationRegions
|
||||
=======================
|
||||
|
||||
NavigationRegions are the visual Node representation of a ``region`` of the navigation ``map`` on the NavigationServer.
|
||||
Each NavigationRegion node holds a resource for the navigationmesh data.
|
||||
|
||||
Both 2D and 3D version are available as :ref:`NavigationRegion2D<class_NavigationRegion2D>`
|
||||
and :ref:`NavigationRegion3D<class_NavigationRegion3D>` respectively.
|
||||
|
||||
Individual NavigationRegions upload their 2D NavigationPolygon or 3D NavigationMesh resource data to the NavigationServer.
|
||||
The NavigationServer map turns this information into a combined navigation map for pathfinding.
|
||||
|
||||
To create a navigation region using the SceneTree add a ``NavigationRegion2D`` or ``NavigationRegion3D`` node to the scene.
|
||||
All regions require a navigationmesh resource to function. See :ref:`doc_navigation_using_navigationmeshes` to learn how to create and apply navigationmeshes.
|
||||
|
||||
NavigationRegions will automatically push ``global_transform`` changes to the region on the NavigationServer which makes them suitable for moving platforms.
|
||||
The NavigationServer will attempt to connect navmeshes of individual regions when they are close enough. For more detail see :ref:`doc_navigation_connecting_navmesh`.
|
||||
To connect NavigationRegions over arbitrary distances see :ref:`doc_navigation_using_navigationlinks` to learn how to create and use ``NavigationLinks``.
|
||||
|
||||
.. warning::
|
||||
|
||||
While changing the transform of a NavigationRegion node does update the region position on the
|
||||
NavigationServer changing the scale does not. A navigationmesh resource has no scale and needs
|
||||
to be fully updated when source geometry changes scale.
|
||||
|
||||
Regions can be enabled / disabled and if disabled will not contribute to future pathfinding queries.
|
||||
|
||||
.. note::
|
||||
|
||||
Existing paths will not be automatically updated when a region gets enabled / disabled.
|
||||
|
||||
Creating new navigation regions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
New NavigationRegion nodes will automatically register to the default world navigation map for their 2D/3D dimension.
|
||||
|
||||
The region RID can then be obtained from NavigationRegion Nodes with ``get_region_rid()``.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends NavigationRegion3D
|
||||
|
||||
var navigationserver_region_rid: RID = get_region_rid()
|
||||
|
||||
New regions can also be created with the NavigationServer API and added to any existing map.
|
||||
|
||||
If regions are created with the NavigationServer API directly they need to be assigned a navigation map manually.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node2D
|
||||
|
||||
var new_2d_region_rid: RID = NavigationServer2D.region_create()
|
||||
var default_2d_map_rid: RID = get_world_2d().get_navigation_map()
|
||||
NavigationServer2D.region_set_map(new_2d_region_rid, default_2d_map_rid)
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
|
||||
var new_3d_region_rid: RID = NavigationServer3D.region_create()
|
||||
var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
|
||||
NavigationServer3D.region_set_map(new_3d_region_rid, default_3d_map_rid)
|
||||
|
||||
.. note::
|
||||
|
||||
NavigationRegions can only be assigned to a single NavigationMap.
|
||||
If an existing region is assigned to a new map it will leave the old map.
|
188
tutorials/navigation/navigation_using_navigationservers.rst
Normal file
@ -0,0 +1,188 @@
|
||||
.. _doc_navigation_using_navigationservers:
|
||||
|
||||
Using NavigationServer
|
||||
======================
|
||||
|
||||
2D and 3D version of the NavigationServer are available as
|
||||
:ref:`NavigationServer2D<class_NavigationServer2D>` and
|
||||
:ref:`NavigationServer3D<class_NavigationServer3D>` respectively.
|
||||
|
||||
Both 2D and 3D use the same NavigationServer with NavigationServer3D being the primary server. The NavigationServer2D is a frontend that converts 2D positions into 3D positions and back.
|
||||
Hence it is entirely possible (if not a little cumbersome) to exclusively use the NavigationServer3D API for 2D navigation.
|
||||
|
||||
Communicating with the NavigationServer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To work with the NavigationServer means to prepare parameters for a ``query`` that can be send to the NavigationServer for updates or requesting data.
|
||||
|
||||
To reference the internal NavigationServer objects like maps, regions and agents RIDs are used as identification numbers.
|
||||
Every navigation related node in the SceneTree has a function that returns the RID for this node.
|
||||
|
||||
Threading and Synchronization
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The NavigationServer does not update every change immediately but waits until
|
||||
the end of the ``physics_frame`` to synchronize all the changes together.
|
||||
|
||||
Waiting for synchronization is required to apply changes to all maps, regions and agents.
|
||||
Synchronization is done because some updates like a recalculation of the entire navigation map are very expensive and require updated data from all other objects.
|
||||
Also the NavigationServer uses a ``threadpool`` by default for some functionality like avoidance calculation between agents.
|
||||
|
||||
Waiting is not required for most ``get()`` functions that only request data from the NavigationServer without making changes.
|
||||
Note that not all data will account for changes made in the same frame.
|
||||
E.g. if an avoidance ``agent`` changed the navigation ``map`` this frame the ``agent_get_map()`` function will still return the old map before the synchronization.
|
||||
The exception to this are nodes that store their values internally before sending the update to the NavigationServer.
|
||||
When a getter on a node is used for a value that was updated in the same frame it will return the already updated value stored on the node.
|
||||
|
||||
The NavigationServer is ``thread-safe`` as it places all API calls that want to make changes in a queue to be executed in the synchronization phase.
|
||||
Synchronization for the NavigationServer happens in the middle of the physics frame after scene input from scripts and nodes are all done.
|
||||
|
||||
.. note::
|
||||
The important takeaway is that most NavigationServer changes take effect after the next physics frame and not immediately.
|
||||
This includes all changes made by navigation related nodes in the SceneTree or through scripts.
|
||||
|
||||
The following functions will be executed in the synchronization phase only:
|
||||
|
||||
- map_set_active()
|
||||
- map_set_up()
|
||||
- map_set_cell_size()
|
||||
- map_set_edge_connection_margin()
|
||||
- region_set_map()
|
||||
- region_set_transform()
|
||||
- region_set_enter_cost()
|
||||
- region_set_travel_cost()
|
||||
- region_set_navigation_layers()
|
||||
- region_set_navigation_mesh()
|
||||
- agent_set_map()
|
||||
- agent_set_neighbor_dist()
|
||||
- agent_set_max_neighbors()
|
||||
- agent_set_time_horizon()
|
||||
- agent_set_radius()
|
||||
- agent_set_max_speed()
|
||||
- agent_set_velocity()
|
||||
- agent_set_target_velocity()
|
||||
- agent_set_position()
|
||||
- agent_set_ignore_y()
|
||||
- agent_set_callback()
|
||||
- free()
|
||||
|
||||
2D and 3D NavigationServer differences
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
NavigationServer2D and NavigationServer3D are equivalent in functionality
|
||||
for their dimension and both use the same NavigationServer behind the scene.
|
||||
|
||||
Strictly technical a NavigationServer2D is a myth.
|
||||
The NavigationServer2D is a frontend to facilitate conversions of Vector2(x, y) to
|
||||
Vector3(x, 0.0, z) and back for the NavigationServer3D API. 2D uses a flat 3D mesh
|
||||
pathfinding and the NavigationServer2D facilitates the conversions.
|
||||
When a guide uses just NavigationServer without the 2D or 3D suffix it usually works for both servers
|
||||
by exchange Vector2(x, y) with Vector3(x, 0.0, z) or reverse.
|
||||
|
||||
Technically it is possible to use the tools for creating navigationmesh for the other
|
||||
dimension, e..g. baking 2D navigationmesh with the 3D NavigationMesh when using
|
||||
flat 3D source geometry or creating 3D flat navigationmesh with the
|
||||
polygon outline drawtools of NavigationRegion2D and NavigationPolygons.
|
||||
|
||||
Any RID created with the NavigationServer2D API works on the NavigationServer3D API
|
||||
as well and both 2D and 3D avoidance agents can exist on the same map.
|
||||
|
||||
.. note::
|
||||
Regions created in 2D and 3D will merge their navigationmeshes when placed on the same map and merge conditions apply.
|
||||
The NavigationServer does not discriminate between NavigationRegion2D and NavigationRegion3D nodes as both are regions on the server.
|
||||
By default those nodes register on different navigation maps so this merge can only happen when maps are changed manually e.g. with scripts.
|
||||
|
||||
Actors with avoidance enabled will avoid both 2D and 3D avoidance agents when placed on the same map.
|
||||
|
||||
.. warning::
|
||||
It is not possible to use NavigationServer2D while disabling 3D on a Godot custom build.
|
||||
|
||||
Waiting for synchronization
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
At the start of the game, a new scene or procedural navigation changes any path query to a NavigationServer will return empty or wrong.
|
||||
|
||||
The navigation map is still empty or not updated at this point.
|
||||
All nodes from the SceneTree need to first upload their navigation related data to the NavigationServer.
|
||||
Each added or changed map, region or agent need to be registered with the NavigationServer.
|
||||
Afterward the NavigationServer requires a ``physics_frame`` for synchronization to update the maps, regions and agents.
|
||||
|
||||
One workaround is to make a deferred call to a custom setup function (so all nodes are ready).
|
||||
The setup function makes all the navigation changes, e.g. adding procedural stuff.
|
||||
Afterwards the function waits for the next physics_frame before continuing with path queries.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node3D
|
||||
|
||||
func _ready():
|
||||
# use call deferred to make sure the entire SceneTree Nodes are setup
|
||||
# else await / yield on 'physics_frame' in a _ready() might get stuck
|
||||
call_deferred("custom_setup")
|
||||
|
||||
func custom_setup():
|
||||
|
||||
# create a new navigation map
|
||||
var map: RID = NavigationServer3D.map_create()
|
||||
NavigationServer3D.map_set_up(map, Vector3.UP)
|
||||
NavigationServer3D.map_set_active(map, true)
|
||||
|
||||
# create a new navigation region and add it to the map
|
||||
var region: RID = NavigationServer3D.region_create()
|
||||
NavigationServer3D.region_set_transform(region, Transform())
|
||||
NavigationServer3D.region_set_map(region, map)
|
||||
|
||||
# create a procedural navigation mesh for the region
|
||||
var new_navigation_mesh: NavigationMesh = NavigationMesh.new()
|
||||
var vertices: PackedVector3Array = PackedVector3Array([
|
||||
Vector3(0,0,0),
|
||||
Vector3(9.0,0,0),
|
||||
Vector3(0,0,9.0)
|
||||
])
|
||||
new_navigation_mesh.set_vertices(vertices)
|
||||
var polygon: PackedInt32Array = PackedInt32Array([0, 1, 2])
|
||||
new_navigation_mesh.add_polygon(polygon)
|
||||
NavigationServer3D.region_set_navigation_mesh(region, new_navigation_mesh)
|
||||
|
||||
# wait for NavigationServer sync to adapt to made changes
|
||||
await get_tree().physics_frame
|
||||
|
||||
# query the path from the navigationserver
|
||||
var start_position: Vector3 = Vector3(0.1, 0.0, 0.1)
|
||||
var target_position: Vector3 = Vector3(1.0, 0.0, 1.0)
|
||||
var optimize_path: bool = true
|
||||
|
||||
var path: PackedVector3Array = NavigationServer3D.map_get_path(
|
||||
map,
|
||||
start_position,
|
||||
target_position,
|
||||
optimize_path
|
||||
)
|
||||
|
||||
print("Found a path!")
|
||||
print(path)
|
||||
|
||||
Server Avoidance Callbacks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If RVO avoidance agents are registered for avoidance callbacks the NavigationServer dispatches
|
||||
their ``safe_velocity`` signals just before the PhysicsServer synchronization.
|
||||
|
||||
To learn more about NavigationAgents see :ref:`doc_navigation_using_navigationagents`.
|
||||
|
||||
The simplified order of execution for NavigationAgents that use avoidance:
|
||||
|
||||
- physics frame starts.
|
||||
- _physics_process(delta).
|
||||
- set_velocity() on NavigationAgent Node.
|
||||
- Agent sends velocity and position to NavigationServer.
|
||||
- NavigationServer waits for synchronization.
|
||||
- NavigationServer synchronizes and computes avoidance velocities for all registered avoidance agents.
|
||||
- NavigationServer sends safe_velocity vector with signals for each registered avoidance agents.
|
||||
- Agents receive the signal and move their parent e.g. with move_and_slide or linear_velocity.
|
||||
- PhysicsServer synchronizes.
|
||||
- physics frame ends.
|
||||
|
||||
Therefore moving a physicsbody actor in the callback function with the safe_velocity is perfectly thread- and physics-safe
|
||||
as all happens inside the same physics_frame before the PhysicsServer commits to changes and does its own calculations.
|