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)