Dynamically update NavigationRegion3D/NavigationMesh

Godot Version

4.2.2

Question

I am moving a CharacterBody3D with a NavigationAgend3D. If i block off the path completely (i.e. with a big enough StaticBody3D) my CharacterBody3D walks up to that obstacle and stops.


Now i want the StaticBody3D to resume it’s way to the target location once that obstacle has been removed. However, if i remove the StaticBody3D and its parent (MeshInstance3D) and then call bake_navigation_mesh the NavigationMesh does not seem to be updated.

I checked and the nodes get deleted from the SceneTree. If I do this manually in the Editor the NavigationMesh gets updated correctly.

This is my scene:

And this is the script

extends CharacterBody3D

var movement_speed: float = 2.0
var movement_target_position: Vector3 = Vector3(-10.0,0.0,10.0)

@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D
@onready var navigation_region_3d = $"../NavigationRegion3D"

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()

	velocity = current_agent_position.direction_to(next_path_position) * movement_speed
	move_and_slide()
	for i in get_slide_collision_count():
		var collision = get_slide_collision(i)
		print("I collided with ", collision.get_collider().name)
		var obstacle = collision.get_collider()
		print(obstacle)
		var mesh = obstacle.get_parent()
		for n in mesh.get_children():
			n.remove_child(n)
			n.queue_free()
		mesh.queue_free()

		navigation_region_3d.bake_navigation_mesh(false)
		print("Baked navigation mesh")
		set_movement_target(movement_target_position)

And this is the scene without obstacle (removed and baked again in the editor):

What am i doing wrong? Or is there a better way to do this?

Did you change the NavigationMesh parse properties to only parse collision shapes? Else it is likely that your mesh gets parsed because you only remove_child the nodes below it while your mesh instance lingers.

If you have the parse properties set to collision the simplest way to exclude objects is to remove their collision layer bit that matches with the NavigationMesh parsed bitmask.

If you change the navmesh it will take until the next physics sync to update the navigation map so that immediate target change on the agent still queries a path on the old navigation map. That said when the navigation map changes the agent updates its path automatically if not already finished.

I set Parsed Geometry Type to both now on the NavigationMesh and also tried to move StaticBody3D that is attached to the MeshInstance to another collision Layer but still with the same effect. The NavigationMesh stays unchanged and the agent doesn’t move along…

EDIT:
I figured it out. If i queue the removal in the same frame as i bake the navigation it will not work (i.e. Calling both function in the same run of _physics_process). The bake_navigation_mesh needs to be called after the removal is completed. I rewrote my example with a Timer that triggers bake_navigation_mesh and it works. Now, this is obviously not a good solution but at least it helped me identify the issue.