godot: NavigationServer map query failed from _physics_process

Godot version

4.1.3

System information

Macos

Issue description

getting error/warning on first frame

E 0:00:03:0742   abr.gd:41 @ _physics_process(): NavigationServer map query failed because it was made before first map synchronization.
  <C++ Error>    Condition "map_update_id == 0" is true. Returning: Vector<Vector3>()
  <C++ Source>   modules/navigation/nav_map.cpp:119 @ get_path()
  <Stack Trace>  abr.gd:41 @ _physics_process()

on this code

extends CharacterBody3D
@onready var nav_agent = $NavigationAgent3D

func _physics_process(_delta: float) -> void:
	var next_position = nav_agent.get_next_path_position()
	var new_velocity = (next_position - global_position).normalized()
	nav_agent.velocity = new_velocity

what the right way to implement Navigation path finding?

Steps to reproduce

call get_next_path_position from _physics_process

Minimal reproduction project

TestNav.zip

About this issue

  • Original URL
  • State: closed
  • Created 8 months ago
  • Reactions: 5
  • Comments: 19 (16 by maintainers)

Commits related to this issue

Most upvoted comments

added

func _ready():
	set_physics_process(false)
	call_deferred("enemy_setup")

func enemy_setup():
	# Wait for the first physics frame so the NavigationServer can sync.
	await get_tree().physics_frame
	set_physics_process(multiplayer.is_server())

but still getting error

Screenshot 2023-12-06 175821

trying the above solution in v4.2.stable.official 46dc27791, I don’t have this error any more

func _ready():
	set_physics_process(false)
	call_deferred('setup')

func setup():
	await get_tree().physics_frame
	set_physics_process(true)
	$NavigationAgent2D.target_position = Globals.player_pos #my code here

I’m using Godot v4.2.stable.official 46dc27791 and I’m seeing this issue on a Mac (M1).

I used the workaround suggested by @smix8 here: https://github.com/godotengine/godot/issues/84677#issuecomment-1841014124

Instead of relying on the physics_frame, I wait for a map_changed NavigationServer3D event:

func _ready():
    set_physics_process(false)
    call_deferred("actor_setup")

func actor_setup():
    NavigationServer3D.map_changed.connect(Callable(map_ready))

func map_ready(rid): 
	set_physics_process(true)
	NavigationServer3D.map_changed.disconnect(Callable(map_ready))

Not sure if this is ideal but the mobs load without issue.

The navigation has not really changed in that regard. The server sync still happens at the same time as before. So your options are to wait for the physics sync or listen to the NavigationServer map_changed signal.

Just want to share my solution in case somebody else have the same issue, my actual code a bit more structured, so I have a class BasicMap where processing players locations and class Enemy who chasing players: BasicMap.gd

extends Node3D
class_name BasicMap

@onready var players_node = $Players

func _ready():
	set_physics_process(false)
	call_deferred("enemies_setup")

func enemies_setup():
	# Wait for the first physics frame so the NavigationServer can sync.
	await get_tree().physics_frame
	set_physics_process(true)

func _physics_process(_delta):
	for player in players_node.get_children():
		get_tree().call_group("Enemies", "update_target_position", player.global_transform.origin)

Enemy.gd

@tool
extends CharacterBody3D
class_name Enemy
@onready var nav_agent = $NavigationAgent3D

func update_target_position(target_position: Vector3):
	nav_agent.target_position = target_position

This is multiplayer game and physics management should be controlled from the server, but this is next steps.

What Scony mentioned. You can either wait for the physics_frame tick or you can listen to the NavigationServer map_changed signal to know when a navigation map sync has happened.

That errors is because the navigation map has never synchronized at least once. The NavigationServer sync happens on the physics_frame tick.

Using await in _ready() does not work sometimes so it is better to call_deferred() and then await.

Example from the documentation:

func _ready():
	# 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.
	navigation_agent.set_target_position(movement_target_position)

@theromis you very likely need to wait for the navigation to synchronize. For that, you can e.g. wait 1 frame before doing any queries: await get_tree().physics_frame - the problem with that is, you shouldn’t do it in the _physics_process. Therefore I’d recommend blocking the processing for 1 frame like:

func _ready():
    set_physics_process(false)
    await get_tree().physics_frame
    set_physics_process(true)