mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-04 23:10:08 +01:00
Add navigation mesh chunks demos (#1099)
Adds 2D and 3D demo project for how to bake navigation meshes for large world chunk systems.
This commit is contained in:
15
2d/navigation_mesh_chunks/README.md
Normal file
15
2d/navigation_mesh_chunks/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Navigation Mesh Chunks 2D
|
||||
|
||||
Demo that shows how to bake navigation meshes for large world chunk systems.
|
||||
|
||||
A mouse cursor left click changes the start position for the debug paths.
|
||||
|
||||
Language: GDScript
|
||||
|
||||
Renderer: Compatibility
|
||||
|
||||
> Note: this demo requires Godot 4.3 or later
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
BIN
2d/navigation_mesh_chunks/icon.webp
Normal file
BIN
2d/navigation_mesh_chunks/icon.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
156
2d/navigation_mesh_chunks/navmesh_chhunks_demo_2d.gd
Normal file
156
2d/navigation_mesh_chunks/navmesh_chhunks_demo_2d.gd
Normal file
@@ -0,0 +1,156 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
static var map_cell_size: float = 1.0
|
||||
static var chunk_size: int = 256
|
||||
static var cell_size: float = 1.0
|
||||
static var agent_radius: float = 10.0
|
||||
static var chunk_id_to_region: Dictionary = {}
|
||||
|
||||
|
||||
var path_start_position: Vector2
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
NavigationServer2D.set_debug_enabled(true)
|
||||
|
||||
path_start_position = %DebugPaths.global_position
|
||||
|
||||
var map: RID = get_world_2d().navigation_map
|
||||
NavigationServer2D.map_set_cell_size(map, map_cell_size)
|
||||
|
||||
# Disable performance costly edge connection margin feature.
|
||||
# This feature is not needed to merge navigation mesh edges.
|
||||
# If edges are well aligned they will merge just fine by edge key.
|
||||
NavigationServer2D.map_set_use_edge_connections(map, false)
|
||||
|
||||
# Parse the collision shapes below our parse root node.
|
||||
var source_geometry: NavigationMeshSourceGeometryData2D = NavigationMeshSourceGeometryData2D.new()
|
||||
var parse_settings: NavigationPolygon = NavigationPolygon.new()
|
||||
parse_settings.parsed_geometry_type = NavigationPolygon.PARSED_GEOMETRY_STATIC_COLLIDERS
|
||||
NavigationServer2D.parse_source_geometry_data(parse_settings, source_geometry, %ParseRootNode)
|
||||
|
||||
# Add an outline to define the traversable surface that the parsed collision shapes can "cut" into.
|
||||
var traversable_outline: PackedVector2Array = PackedVector2Array([
|
||||
Vector2(0.0, 0.0),
|
||||
Vector2(1920.0, 0.0),
|
||||
Vector2(1920.0, 1080.0),
|
||||
Vector2(0.0, 1080.0),
|
||||
])
|
||||
source_geometry.add_traversable_outline(traversable_outline)
|
||||
|
||||
create_region_chunks(%ChunksContainer, source_geometry, chunk_size * cell_size, agent_radius)
|
||||
|
||||
|
||||
static func create_region_chunks(chunks_root_node: Node, p_source_geometry: NavigationMeshSourceGeometryData2D, p_chunk_size: float, p_agent_radius: float) -> void:
|
||||
# We need to know how many chunks are required for the input geometry.
|
||||
# So first get an axis aligned bounding box that covers all vertices.
|
||||
var input_geometry_bounds: Rect2 = calculate_source_geometry_bounds(p_source_geometry)
|
||||
|
||||
# Rasterize bounding box into chunk grid to know range of required chunks.
|
||||
var start_chunk: Vector2 = floor(
|
||||
input_geometry_bounds.position / p_chunk_size
|
||||
)
|
||||
var end_chunk: Vector2 = floor(
|
||||
(input_geometry_bounds.position + input_geometry_bounds.size)
|
||||
/ p_chunk_size
|
||||
)
|
||||
|
||||
for chunk_y in range(start_chunk.y, end_chunk.y + 1):
|
||||
for chunk_x in range(start_chunk.x, end_chunk.x + 1):
|
||||
var chunk_id: Vector2i = Vector2i(chunk_x, chunk_y)
|
||||
|
||||
var chunk_bounding_box: Rect2 = Rect2(
|
||||
Vector2(chunk_x, chunk_y) * p_chunk_size,
|
||||
Vector2(p_chunk_size, p_chunk_size),
|
||||
)
|
||||
# We grow the chunk bounding box to include geometry
|
||||
# from all the neighbor chunks so edges can align.
|
||||
# The border size is the same value as our grow amount so
|
||||
# the final navigation mesh ends up with the intended chunk size.
|
||||
var baking_bounds: Rect2 = chunk_bounding_box.grow(p_chunk_size)
|
||||
|
||||
var chunk_navmesh: NavigationPolygon = NavigationPolygon.new()
|
||||
chunk_navmesh.parsed_geometry_type = NavigationPolygon.PARSED_GEOMETRY_STATIC_COLLIDERS
|
||||
chunk_navmesh.baking_rect = baking_bounds
|
||||
chunk_navmesh.border_size = p_chunk_size
|
||||
chunk_navmesh.agent_radius = p_agent_radius
|
||||
NavigationServer2D.bake_from_source_geometry_data(chunk_navmesh, p_source_geometry)
|
||||
|
||||
# The only reason we reset the baking bounds here is to not render its debug.
|
||||
chunk_navmesh.baking_rect = Rect2()
|
||||
|
||||
# Snap vertex positions to avoid most rasterization issues with float precision.
|
||||
var navmesh_vertices: PackedVector2Array = chunk_navmesh.vertices
|
||||
for i in navmesh_vertices.size():
|
||||
var vertex: Vector2 = navmesh_vertices[i]
|
||||
navmesh_vertices[i] = vertex.snappedf(map_cell_size * 0.1)
|
||||
chunk_navmesh.vertices = navmesh_vertices
|
||||
|
||||
var chunk_region: NavigationRegion2D = NavigationRegion2D.new()
|
||||
chunk_region.navigation_polygon = chunk_navmesh
|
||||
chunks_root_node.add_child(chunk_region)
|
||||
|
||||
chunk_id_to_region[chunk_id] = chunk_region
|
||||
|
||||
|
||||
static func calculate_source_geometry_bounds(p_source_geometry: NavigationMeshSourceGeometryData2D) -> Rect2:
|
||||
if p_source_geometry.has_method("get_bounds"):
|
||||
# Godot 4.3 Patch added get_bounds() function that does the same but faster.
|
||||
return p_source_geometry.call("get_bounds")
|
||||
|
||||
var bounds: Rect2 = Rect2()
|
||||
var first_vertex: bool = true
|
||||
|
||||
for traversable_outline: PackedVector2Array in p_source_geometry.get_traversable_outlines():
|
||||
for traversable_point: Vector2 in traversable_outline:
|
||||
if first_vertex:
|
||||
first_vertex = false
|
||||
bounds.position = traversable_point
|
||||
else:
|
||||
bounds = bounds.expand(traversable_point)
|
||||
|
||||
for obstruction_outline: PackedVector2Array in p_source_geometry.get_obstruction_outlines():
|
||||
for obstruction_point: Vector2 in obstruction_outline:
|
||||
if first_vertex:
|
||||
first_vertex = false
|
||||
bounds.position = obstruction_point
|
||||
else:
|
||||
bounds = bounds.expand(obstruction_point)
|
||||
|
||||
for projected_obstruction: Dictionary in p_source_geometry.get_projected_obstructions():
|
||||
var projected_obstruction_vertices: PackedFloat32Array = projected_obstruction["vertices"]
|
||||
for i in projected_obstruction_vertices.size() / 2:
|
||||
var vertex: Vector2 = Vector2(projected_obstruction_vertices[i * 2], projected_obstruction_vertices[i * 2 + 1])
|
||||
if first_vertex:
|
||||
first_vertex = false
|
||||
bounds.position = vertex
|
||||
else:
|
||||
bounds = bounds.expand(vertex)
|
||||
|
||||
return bounds
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var mouse_cursor_position: Vector2 = get_global_mouse_position()
|
||||
|
||||
var map: RID = get_world_2d().navigation_map
|
||||
# Do not query when the map has never synchronized and is empty.
|
||||
if NavigationServer2D.map_get_iteration_id(map) == 0:
|
||||
return
|
||||
|
||||
var closest_point_on_navmesh: Vector2 = NavigationServer2D.map_get_closest_point(
|
||||
map,
|
||||
mouse_cursor_position
|
||||
)
|
||||
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
||||
path_start_position = closest_point_on_navmesh
|
||||
|
||||
%DebugPaths.global_position = path_start_position
|
||||
|
||||
%PathDebugCorridorFunnel.target_position = closest_point_on_navmesh
|
||||
%PathDebugEdgeCentered.target_position = closest_point_on_navmesh
|
||||
|
||||
%PathDebugCorridorFunnel.get_next_path_position()
|
||||
%PathDebugEdgeCentered.get_next_path_position()
|
||||
99
2d/navigation_mesh_chunks/navmesh_chhunks_demo_2d.tscn
Normal file
99
2d/navigation_mesh_chunks/navmesh_chhunks_demo_2d.tscn
Normal file
@@ -0,0 +1,99 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://svfku2i5n033"]
|
||||
|
||||
[ext_resource type="Script" path="res://navmesh_chhunks_demo_2d.gd" id="1_d68tl"]
|
||||
|
||||
[node name="NavMeshChunksDemo2D" type="Node2D"]
|
||||
script = ExtResource("1_d68tl")
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
position = Vector2(960, 540)
|
||||
zoom = Vector2(0.8, 0.8)
|
||||
|
||||
[node name="ParseRootNode" type="Node2D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="StaticBody2D" type="StaticBody2D" parent="ParseRootNode"]
|
||||
|
||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="ParseRootNode/StaticBody2D"]
|
||||
polygon = PackedVector2Array(244, 147, 636, 155, 376, 391, 460, 655, 804, 795, 756, 971, 484, 839, 308, 963, 132, 811, 128, 559)
|
||||
|
||||
[node name="CollisionPolygon2D2" type="CollisionPolygon2D" parent="ParseRootNode/StaticBody2D"]
|
||||
polygon = PackedVector2Array(1312, 207, 1596, 91, 1832, 175, 1739, 926, 1562, 796, 1508, 935, 1184, 811, 1324, 533, 1499, 599, 1684, 511, 1596, 322)
|
||||
|
||||
[node name="CollisionPolygon2D3" type="CollisionPolygon2D" parent="ParseRootNode/StaticBody2D"]
|
||||
polygon = PackedVector2Array(661, 339, 943, 397, 1112, 178, 1172, 443, 960, 639, 700, 579)
|
||||
|
||||
[node name="DebugPaths" type="Node2D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="PathDebugCorridorFunnel" type="NavigationAgent2D" parent="DebugPaths"]
|
||||
unique_name_in_owner = true
|
||||
debug_enabled = true
|
||||
debug_use_custom = true
|
||||
debug_path_custom_color = Color(1, 0, 1, 1)
|
||||
debug_path_custom_point_size = 10.0
|
||||
debug_path_custom_line_width = 4.0
|
||||
|
||||
[node name="PathDebugEdgeCentered" type="NavigationAgent2D" parent="DebugPaths"]
|
||||
unique_name_in_owner = true
|
||||
path_postprocessing = 1
|
||||
debug_enabled = true
|
||||
debug_use_custom = true
|
||||
debug_path_custom_color = Color(1, 1, 0, 1)
|
||||
debug_path_custom_point_size = 10.0
|
||||
debug_path_custom_line_width = 4.0
|
||||
|
||||
[node name="ChunksContainer" type="Node2D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="CanvasLayer"]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/PanelContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 15
|
||||
theme_override_constants/margin_top = 15
|
||||
theme_override_constants/margin_right = 15
|
||||
theme_override_constants/margin_bottom = 15
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Use cursor button to set path start position"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(128, 8)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
color = Color(1, 0, 1, 1)
|
||||
|
||||
[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Path corridor-funnel"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||
custom_minimum_size = Vector2(128, 8)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
color = Color(1, 1, 0, 1)
|
||||
|
||||
[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = "Path edge-centered"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
29
2d/navigation_mesh_chunks/project.godot
Normal file
29
2d/navigation_mesh_chunks/project.godot
Normal file
@@ -0,0 +1,29 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Navigation Mesh Chunks 2D"
|
||||
config/tags=PackedStringArray("2d", "ai", "demo", "official")
|
||||
run/main_scene="res://navmesh_chhunks_demo_2d.tscn"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
config/icon="res://icon.webp"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
window/stretch/mode="canvas_items"
|
||||
window/stretch/aspect="expand"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
0
2d/navigation_mesh_chunks/screenshots/.gdignore
Normal file
0
2d/navigation_mesh_chunks/screenshots/.gdignore
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user