NavigationServer2D.map_get_random_point gives "Parameter "map" is null" error on a non-null map RID

Godot Version

4.4.1.stable

Question

I’m setting up navigation for an enemy in my game. As part of its “wander” state it walks from its current position to a randomly chosen point nearby on the navigation map.

The enemy has a NavigationAgent2D, and the navigation map is set in its ready function. During the wander state’s enter function, it passes the navigation map’s RID (stored in a global script) to NavigationServer2D.map_get_random_point to pick the wander target.

Problem is, Godot keeps thinking the passed RID is null.


"E 0:00:03:558   enemy_state_wander.gd:66 @ enter(): Parameter "map" is null.
  <C++ Source>  modules/navigation/3d/godot_navigation_server_3d.cpp:382 @ map_get_random_point()
  <Stack Trace> enemy_state_wander.gd:66 @ enter()
                enemy_state_machine.gd:65 @ change_state()
                enemy_state_idle.gd:67 @ _on_idle_timer_timeout()

Printing it immediately beforehand shows an RID so it definitely isn’t null.

Not sure how to verify that it’s in fact the map’s RID (I’m still quite new to this), but I don’t see why it wouldn’t be. Also, the enemy doesn’t enter a wander state until at least 2 seconds of idling first, so the scene tree has plenty of time to finish loading in and doing its thing before any of this is called.

The other thing I only just noticed while posting this is that the error refers to navigation_server_3d, but I’m definitely calling the function on NavigationServer2D.

The only other post I could find was on Reddit over a year ago, and OP said they never found a solution. The post in question: https://www.reddit.com/r/godot/comments/193oxi9/error_saying_map_parameter_is_null_when_its_not/

Is this a bug or am I missing something? Any help would be much appreciated.

Relevant code:

In overarching world scene (this is called in _ready):

@onready var navigation_region_2d: NavigationRegion2D = $Navigation/NavigationRegion2D

...

func generate_navigation_region() -> void:
	assert(navigation_region_2d.navigation_polygon, "[world.gd] Error: Navigation Polygon has not been set.")
	
	# Get array of all used tiles' coordinates.
	var used_cells_array: Array[Vector2i] = $Level/Walls.get_used_cells()
	# Sort from lowest to highest.
	used_cells_array.sort()
	
	# Get local position of the first cell, convert to global position.
	var first_tile_pos: Vector2 = $Level/Walls.to_global($Level/Walls.map_to_local(used_cells_array[0]))
	# Get local position of the last cell, convert to global position.
	var last_tile_pos: Vector2 = $Level/Walls.to_global($Level/Walls.map_to_local(used_cells_array[-1]))
	
	# Define the other two corners of the rect.
	var top_right: Vector2
	top_right.x = last_tile_pos.x
	top_right.y = first_tile_pos.y
	var bottom_left: Vector2
	bottom_left.x = first_tile_pos.x
	bottom_left.y = last_tile_pos.y
	
	# Construct an array of the above to define rectangle covering the level.
	var rect_points: Array[Vector2] = [first_tile_pos, bottom_left, last_tile_pos, top_right]
	# Pack into a packed array that the NavigationPolygon can use.
	var rect_points_packed = PackedVector2Array(rect_points)
	
	# Draw the navigation polygon's rectangle using the packed array.
	navigation_region_2d.navigation_polygon.add_outline(rect_points_packed)
	
	# Bake the polygon, using selected geometry to "cut out" walls leaving only
	# the floor areas for the region.
	navigation_region_2d.bake_navigation_polygon()

	# Store the navigation map's RID in the globals script.
	Globals.enemy_navmap_rid = navigation_region_2d.get_rid()

...
	

In enemy script:

...

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
	connect_signals()
	# Wait to be sure the world scene has initialised and done its thing.
	await get_tree().create_timer(0.2).timeout
	# Then set navigation agent's navigation map to the RID stored in 
	# globals.gd.
	navigation_agent.set_navigation_map(Globals.enemy_navmap_rid)

...

Wander state:

signal target_reached()
@export var navigation_agent: NavigationAgent2D
var wander_target: Vector2


func _ready() -> void:
	assert(navigation_agent != null, "[enemy_state_wander.gd] Error: Wander state's navigation agent has not been set.")
	navigation_agent.navigation_finished.connect(func():
		target_reached.emit()
		)


func _physics_process(delta: float) -> void:
	if !wander_target:
		return
	enemy.velocity = enemy.velocity.move_toward(wander_target * enemy.stats_component.max_speed, enemy.stats_component.acceleration * delta)


# Called in state machine's change_state function.
func enter() -> void:
	# Enable state processing.
	super()

	# Pick a random point in the nav map.
	var map_rid = navigation_agent.get_navigation_map()
	print(map_rid)   # <---- Prints "RID(5660766896128)"
	var wander_target: Vector2 = NavigationServer2D.map_get_random_point(map_rid, 1, false)

	# Smoothly turn enemy's upper body to look at target.
	var tween = get_tree().create_tween()
	tween.set_ease(Tween.EASE_OUT)
	tween.tween_property(enemy.actor_sprite_component.sprite_upper_pivot, "rotation", wander_target.angle(), 0.8)

Globals.enemy_navmap_rid = navigation_region_2d.get_rid() is an error, because you store a NavigationRegion2D resource id (RID) and later try to use that region with a function that expects a navigation map, not a region.

For NavigationServer2D.map_get_random_point() you need the RID of your navigation map, e.g. what get_world_2d().navigation_map returns.

That the error pritns the navigation_server_3d is not a bug. In Godot 4.4 there is no dedicated 2d server, it uses the 3d server behind the scene.

1 Like

Ah I see. I tried get_world_2d().navigation_map instead and the error is gone. Thanks!

My brain has been having trouble with the difference between a navigation region and a navigation map. The Godot documentation overall is amazing, but it doesn’t seem to explain that bit very well (for newbies like me at least).

Same with the thing about the 2D server using the 3D server behind the scenes - doesn’t seem to be documented anywhere. The only place it kind of alludes to it is the description for NavigationServer2D.get_maps (which sounds like another way I could have gotten the map RID funnily enough, just didn’t notice it til now).