How to fix global mouse position (0;0) different from main node transform (0;0)?

Godot Version

4.2.2.stable.mono

Question

For some time now I’ve been trying to figure out an issue with my mouse positioning. In my game I create a grid and position the tiles using x and y coordinates. The tiles are made using 16 pixel wide, pixel art. So when I position them I multiply the x and the y coordinates by 16 but that’s slightly beyond the point. The issue is when I print the global mouse position and position my cursor at the center of the top left grid tile, instead of printing (0;0) as it should, it prints something around (-2.5;-2.5) but not exactly. Why is this the case? I need to be able to use the mouse position as I then round it to the closest tile position and then update code for that tile but currently there is an offset which get’s worse as the player zooms out.

What I’ve tried?

I’ve looked at all my node transforms and they all have (0;0) and my grid node doesn’t move so it shouldn’t affect anything. But I’m still getting a difference between the (0;0) for my grid and the (0;0) for the mouse.

GridManager.gd Script - Creates the Grid (if needed)

extends Node2D

var tileScene = preload("res://Scenes/Tile.tscn")
var gridSize: int = 20
var tileSize: int = 16
var noise = FastNoiseLite.new()
var noiseScale = 3
var seed = randi() % 1000

func _ready():
	noise.seed = seed
	noise.fractal_octaves = 2
	noise.fractal_gain = 0.4
	noise.fractal_lacunarity = 4.0
	noise.frequency = 0.01
	noise.noise_type = 1
	print(seed)
	
	generateGrid()

# Generate grid
func generateGrid():
	for x in range(gridSize):
		for y in range(gridSize):
			var heightValue = noise.get_noise_2d(x * noiseScale, y * noiseScale)
			var tileType = getTileType(heightValue)
			var tileInstance = tileScene.instantiate()
			tileInstance.position = Vector2(x * tileSize, y * tileSize)
			tileInstance.set_name("Tile[" + str(x) + "_" + str(y) + "]")
			add_child(tileInstance)
			tileInstance.initialize(x, y, getTileType(noise.get_noise_2d(x * noiseScale, y * noiseScale)))
	
	# Initialize tile textures after all have been added
	for x in range(gridSize):
		for y in range(gridSize):
			var surroundingTileTextures = getSurroundingTextures(x, y)

# Get surrounding textures with tile coordinates
func getSurroundingTextures(x, y):
	var surroundingTilePaths = [
		"Tile[" + str(x) + "_" + str(y + 1) + "]", # Bottom
		"Tile[" + str(x) + "_" + str(y - 1) + "]", # Top
		"Tile[" + str(x + 1) + "_" + str(y) + "]", # Right
		"Tile[" + str(x - 1) + "_" + str(y) + "]" # Left
	]

	var surroundingTileTextures = []
	surroundingTileTextures.append(str(x) + "_" + str(y))
	var centerTile = getNodeWithPath("Tile[" + str(x) + "_" + str(y) + "]")
	var centerTexture = centerTile.texture
	surroundingTileTextures.append(centerTexture)
	for path in surroundingTilePaths:
		var tileManager = getNodeWithPath(path)
		if tileManager != null:
			surroundingTileTextures.append(tileManager.texture)
		else:
			surroundingTileTextures.append(null)
	return surroundingTileTextures

# Find node with path
func getNodeWithPath(path):
	if has_node(path):
		return get_node(path)
	return null

# Determine the tile type based on the height value
func getTileType(height_value):
	if height_value < -0.2:
		return "water"
	elif height_value < 0.1:
		return "sand"
	else:
		return "grass"

Hierarchy

Main (node 2d)
→ GridManager (node 2d)

Image to Show Issue


At the bottom left are the coordinates when placing my cursor at the center of the black dot (a node 2d which draws a circle at coordinates (0;0)

You need to understand that there are many different coordinate systems at play.

The mouse will provide a position in screen space, the tiles will be in canvas space the circle will, depending on how you check its position, will
either be in global canvas space or local space of the canvas item.

It would be helpful if you only provide the code related to the issue and how you query the mouse position. None of the issue is relevant to how you construct your grid.

1 Like

Thanks for the answer! I am new to Godot so the fact that there are different coordinate systems is very helpful. One thing, the canvas space… how does that work? Is it an automatically created node? If yes, is it hidden? Or does it just represent the space where nodes appear? And in that case why is the mouse position different in terms of coordinates?

So for the code? I’ll do my best to provide what is need but am not sure where the issue stems from, which is why i’m asking so feel free to ask if you need anything.

This is my script attached to a node 2d (Game Manager), located as a child to Main which calculate the grid position :

extends Node2D

const tileSize = 16

func _process(delta):
	updateHighlight()
	var mousePos = get_global_mouse_position()
	var mouseOffsetPos = Vector2(int(mousePos.x / tileSize), int(mousePos.y / tileSize))
	print(mouseOffsetPos)

func updateHighlight():
	# Calculate mouse position and offset
	var mousePos = get_global_mouse_position()
	var mouseOffsetPos = Vector2(int(mousePos.x / tileSize), int(mousePos.y / tileSize))
	# Calculate tile position
	var snapped_pos = Vector2(mouseOffsetPos.x * tileSize, mouseOffsetPos.y * tileSize)
	set_global_position(snapped_pos)

There are two ways to get mouse position.
One is a pixel location within the viewport. The other is a point location on the viewports canvas. This is the get global position that you use.

A node2d inherits from canvasitem. It will have a global position on the canvas layer, and it will have a local space for itself and children. When you use position, or transform, you use local space. If you want a position on the canvas layer you use global position, or the global transform.

I see you use position for local space in your code. This will be okay if your camera is at the origin of the canvas layer, but if your camera moves away from the origin, using local position may, or may not, be what you expect depending on the circumstances.

There is an implicit canvas layer for the viewport. It is hidden in the sense that it’s built into the default viewport but can be retrieved an modified.

Ok thanks for the resources, that makes a lot of sense. So from what I understood, there are different coordinate systems in play but when checking the mouse position I use the canvas coordinate system. Additionally, while nodes are created on the canvas and inherit from it, their transforms and positions are local and based in part on the camera view (including its zoom and position relative to the canvas), meaning that the local positions of the nodes could be (0;0) but relative to the current position of the camera and not of the canvas.

And so this leads me to my question which is how could I set the node positions correctly so that they are located at the coordinate position (0;0) relative to the canvas?

Once again thank you so much for your help! And let me know if you need more details.

I guess going back to your problem you asked why is the global mouse position the way it is? I think you understand better so I will say that when you call global mouse position you get the the translation of the mouse from viewport space into canvas space.

Your other problem I think you said is that it gets worse when you zoom out. Which zooming out just shows more canvas area, making the mouse pixel to canvas translation more course. (Less accurate).

So for the code you have shown now I think looks fine. (I have mental blocks to try and read it all, but searching for position related aspects it’s fine)

Just be aware that global_position (which is also the real canvas position) of each item will generally be what you want to interact with unless you have other reasons to interact with local space positions, which there can be many.

I guess do you have a more specific question?

Ok, that makes sense but now in terms of trying to solve my issues, the issue you pointed out was the use of position instead of global_position but if the parent positions are all (0;0), then the global_position and position are the same. Nevertheless, I tried changing the code to implement global_position and still notice the same issues :

func generateGrid():
		for x in range(gridSize):
			for y in range(gridSize):
				var heightValue = noise.get_noise_2d(x * noiseScale, y * noiseScale)
				var tileType = getTileType(heightValue)
				var tileInstance = tileScene.instantiate()
				tileInstance.global_position = Vector2(x * tileSize, y * tileSize)
				print(tileInstance.global_position)
				tileInstance.set_name("Tile[" + str(x) + "_" + str(y) + "]")
				add_child(tileInstance)
				tileInstance.initialize(x, y, getTileType(noise.get_noise_2d(x * noiseScale, y * noiseScale)))

It seems that either global_mouse_position is working on a different coordinate system than expected or the canvas coordinate system used for the nodes is working on a different one… Do you know what specific changes I should be making to use the same coordinate system for the both the mouse position and the node positions?

I’m not sure, I usually just bend to the way things are.

From what you have said so far tile one is 16x16
And there is a graphical selection with a black circle. When you place your mouse on it and get global position it returns ~(2.5, 2.5).

If we assume this is the first tile and the viewport/camera is at the default position my expectation would be that the mouse at center circle would be (8,8)pixels/on canvas

But I guess your viewport may not be 1 to 1 with your screen pixel scale. So that position could be smaller if your viewport size is somehow smaller then your screens pixel scale. (Basically your CanvasItems are some how scaled below desired pixel ratio) (( I’m failing to find a term not related directly to resolution per say, but related to scaling. ))

I guess do you scale canvas items somewhere, or have a subviewport that is smaller then native pixel scale?

Hey, thank you so much for your help!
You made me look through my project setting with that question and realized I had changed the cursor but not the offset and so after looking into it, I believe the issue spurred from that!
It would make a lot of sense, since everything was on scale (1;1) and position (0;0)…
Nevertheless, thanks for teaching me about viewports and transforms, it’s extremely helpful, especially when starting out with godot.

1 Like