NavigationServer2D get_closest_point inaccuracy?

Godot Version

4.3

Question

I’m making a 2D point-and-click game and using the NavigationServer2D and the NavAgent2D to handle the navigation. For some reason, there always seems to be an offset between where I click and where my target goes.

To better illustrate:


The red circle is where I approximately clicked, and instead of my target moving towards the end of the navigation mesh (although keeping itself on the middle would be the ideal), it performed the trajectory shown.

Here is an outline of how my player scene looks:
image

And here is my movement script, placed on my player scene:

# player.gd

extends CharacterBody2D

@export var movement_speed: float = 200.0

@onready var nav_agent: NavigationAgent2D = $NavigationAgent

func _ready() -> void:
	nav_agent.path_desired_distance = 2.0
	nav_agent.target_desired_distance = 2.0

func _physics_process(_delta: float) -> void:
	if nav_agent.is_navigation_finished():
		return
		
	var next_path_position: Vector2 = nav_agent.get_next_path_position()
	var direction: Vector2 = global_position.direction_to(next_path_position)
	velocity =  direction * movement_speed
	
	move_and_slide()
		
func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.is_action_pressed("left_click"):
		var map: RID = get_world_2d().navigation_map
		var pos: Vector2 = NavigationServer2D.map_get_closest_point(map, event.position)
		nav_agent.target_position = pos


Any ideas?

You can try to reduce the target_desired_distance, but try to set the nav_agent.target_position directly:

nav_agent.target_position = get_global_mouse_position()

The target_desired_distance is rather low however already.
Using the get_global_mouse_position worked, but the docs imply that it is for use with canvas layers (which I have none). Even though I’m happy that it worked, I’m still rather curious as to why my method didn’t work.

1 Like

The navigation system always works with and returns global positions.

There is nothing particular wrong in that code, it is more likely that the problem is input and canvas related. E.g. the input is not in global space but relativ to some canvas item or streched viewport. That it works with get_global_mouse_position() more or less confirms this because that function applies the entire transform stack from all the canvas parents and viewports to the position.

You need to make extra sure that your positions are transform remapped to global positions for both input and output when using the navigation system.

1 Like

Ok, so I think I figured out what the problem is, but not how it is fixed.
Basically, I have a zoom in-out camera that also moves around with the WASD keys. I made it so that whenever I click on a point, the mouse event position (event.global_position) is being printed.

As you can see from the recording below event.global_position seems to return corrdinates relative to where my camera is looking, instead of absolute values on the game world. Is that expected? Is there a best practice for this sort of problem (using get_global_mouse_position)?

Animation

I don’t have too much experirnce with 2D but I think the simplest solution would be to add the camera’s global_position.x to the event’s position.x.

1 Like

A 2D world global_position and an event global_position are not necessarily the same.

A position or global_position is always relative to something which is important to look up when using.

E.g. if you look at the documentation of the InputEventMouse you will learn that the global_position of a mouse event is

When received in Node._input() or Node._unhandled_input(), returns the mouse’s position in the root Viewport using the coordinate system of the root Viewport.

When received in Control._gui_input(), returns the mouse’s position in the CanvasLayer that the Control is in using the coordinate system of the CanvasLayer.

So what that global_position truely is depends already on your context with your nodes or canvas / viewport / camera settings. In case of using a Camera2D your Viewport coordinate system is affected and does not align with your 2D world.

So you either need to use helper functions like CanvasItem.get_global_mouse_position(), or your need to manually apply all the transform changes that comes with changes to canvas, viewport and camera settings to remap the viewport mouse position back to a global world position.

What functions like get_global_mouse_position() do behind the scene is to multiply the inverse canvas transform with the viewport mouse position, basically resetting all the canvas transform so the position is back to a “world position” that a viewport would have with no transform.

Vector2 CanvasItem::get_global_mouse_position() const {
	return get_canvas_transform().affine_inverse().xform(get_viewport()->get_mouse_position());
}
3 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.