godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.13k stars 20.2k forks source link

Mouse enter & exit signals trigger falsely when viewport is scaled (canvas_items mode) and mouse input is manually triggered #96808

Open greatOmouse opened 1 week ago

greatOmouse commented 1 week ago

Tested versions

System information

Windows 11 - Godot Engine v4.3.stable.mono.official

Issue description

This seems to have been fixed, but it may only have been for 2D? For a 3D project I had to use the manual position update when I move the camera to get mouse enter and exit to trigger: https://github.com/godotengine/godot/issues/69708

doing this I seem to get the same additional bug with 2D or 3D resulting from a few steps. So, when calling something like this to manually update the mouse position:

var mouse_event = InputEventMouseMotion.new()
mouse_event.position = get_viewport().get_mouse_position()
Input.parse_input_event(mouse_event)

Mouse Enter & Mouse Exit Signals will fire on an object at a different location to the mouse position if the viewport has been scaled and the stretch mode is set to canvas_items.

i.e. get_viewport().get_mouse_position() seems to be accurately produced, and you can draw things to the screen or make things follow to that mouse location and it works and produces the correct location and output, but somewhere in the processing of handling the simulated mouse input above, it will seemingly use a different or incorrect position value (maybe due to scale).

This causes other objects with mouse enter and exit signals to trigger when the mouse is not over them, or flickering to occur on the object you are mousing over.

I'm not sure if I'm doing something wrong, please let me know. Or in any case if there is a good workaround, please let me know.

Steps to reproduce

  1. Create a 2D scene
  2. set Project Settings -> Display -> Window -> Stretch -> Mode to canvas_items
  3. create a 2D node and give it these functions
    
    extends Node2D

var mousePos = Vector2.ZERO;

Called every frame. 'delta' is the elapsed time since the previous frame.

func _process(delta: float) -> void: var mouse_event = InputEventMouseMotion.new() mouse_event.position = get_viewport().get_mouse_position() Input.parse_input_event(mouse_event)

mousePos = mouse_event.position
print(mousePos)
queue_redraw()
pass

func _draw(): draw_circle(mousePos,20.0,Color.ANTIQUE_WHITE) pass

4. Create a staticbody2D, with a sprite and a collision shape
5. give the static body a script with these functions
```gdscript
extends StaticBody2D

@onready var sprite = $Sprite2D

func _on_mouse_entered() -> void:
    print("hit")
    sprite.modulate = Color.BROWN
    pass # Replace with function body.

func _on_mouse_exited() -> void:
    sprite.modulate = Color.WHITE
    pass # Replace with function body.
  1. Run the game
  2. Observe the proper behavior (do not scale the viewport yet)
  3. Scale the viewport any way you'd like
  4. Observe flickering and false positives.

https://github.com/user-attachments/assets/14fccba0-d70b-475d-9e46-5899931a7f77

Minimal reproduction project (MRP)

ScalingMouseEnter_MRP.zip

kleonc commented 1 week ago

I'm not sure if I'm doing something wrong, please let me know.

You're mixing many different coordinate spaces. See Viewport and canvas transforms. Or, linked in there, a more detailed example in 2D coordinate systems and 2D transforms. Also Custom drawing in 2D (as CanvasItem.draw_x methods work in the local coordinate system of the given CanvasItem).

Specifically: M97EsM5Yln

More or less fixed MouseInput.gd from the MRP:

extends Node2D

var mouse_position_root_window = Vector2.ZERO;

func _process(delta: float) -> void:
    # Both should yield same-ish result:
    mouse_position_root_window = DisplayServer.mouse_get_position() - get_tree().root.position
    #mouse_position_root_window = get_viewport().get_screen_transform() * get_viewport().get_mouse_position()

    var mouse_event = InputEventMouseMotion.new()
    mouse_event.position = mouse_position_root_window
    Input.parse_input_event(mouse_event)

    queue_redraw()

func _draw():
    var local_to_root_window: Transform2D = get_viewport().get_screen_transform() * get_global_transform_with_canvas()
    var root_window_to_local: Transform2D = local_to_root_window.affine_inverse()
    draw_circle(root_window_to_local * mouse_position_root_window, 20.0, Color.ANTIQUE_WHITE)

So I don't see any bug in here. But not closing the issue as seems like some docs can be improved further, e.g. Input.parse_input_event.

https://github.com/godotengine/godot/blob/97ef3c837263099faf02d8ebafd6c77c94d2aaba/doc/classes/Input.xml#L285-L305

greatOmouse commented 1 week ago

Ah I understand now, thank you for the clarification.