Hi all,

New to posting here, but have been playing around with Godot for a while and have been using the knowledge and help here and elsewhere with a lot of gratitude. This time, I ran into something I can't seem to find help for. Not sure if I'm doing something wrong, if there's a bug I stumbled upon or if I'm just missing some basic stuff. Any help is appreciated.

I'm working on a top down 2D game with hoverable/clickable KinematicsBody2D's. The mouse_entered() signal works as expected, but on a moving sprite the mouse_exited() signal only fires as soon as the mouse is moved. It makes sense to some degree, but it is not what I'd logically expect to happen in a game. Say I'm hovering over a character, not moving my mouse, and the character wanders off... it's still 'hovered' in my code, and can still be selected by a mouse click.

I feel I am missing a fundamental step in the process, because I can't be the first running into this. I made a simple test scene and the behaviour is the same. KinematicsBody2D is set to pickable, belongs to a layer and there are no controls blocking mouse events. It responds to mouse signals... just not upon moving away from under the mouse cursor.

Only workaround I can think of myself is to trigger a mouse move signal myself (I saw Input.parse_input_event, that might do the trick), but somethig tells me there should be a better way (like no workaround at all).

Any ideas?

Easily reproduced by a single scene with a KinematicsBody2D as root, Sprite and CollisionShape2D as children:

extends KinematicBody2D


var path = [Vector2(100, 100), Vector2(200, 100)]
var step = 0
var velocity := Vector2.ZERO


func _ready():
	pass
	
	
func _physics_process(delta):
	var dist = position.distance_to(path[step])
	if dist > 1:
		velocity = path[step] - position
		move_and_slide(velocity)
	else:
		step = (step + 1) % 2


func _on_KinematicsBody2D_mouse_entered():
	print('mouse entered')


func _on_KinematicsBody2D_mouse_exited():
	print('mouse exited')

Welcome to the forums @Jeroen!

I think it may be a bug, as it seems similar to this closed issue but in 2D instead of 3D. I did some looking on the Godot Github repository and couldn't find any similar bug reports, new or closed, so I think it's probably best to make a bug report. I think as a workaround generating and sending the input event might work though.

Yes, I can see the same thing on my system. I'm not sure it's a bug, but it's definitely unexpected behavior. Since, from what I understand, the input system responds to events (key presses, mouse movement, etc.). So without any input, those events don't fire and thus if you are not moving the mouse the signal will not happen. This is obviously strange, but it does make some sort of sense.

In any case, you can simply check for mouse overlap in code. It's probably not as optimized as the signal system, but it works fine. I tried generating mouse events, but this does not work either. So it seems the best thing is just to check manually. Here is my example:

extends KinematicBody2D

var path = [Vector2(100, 100), Vector2(300, 100)]
var step = 0
var velocity := Vector2.ZERO
var mouse_over = false
onready var shape = get_node("Shape")
onready var extents = shape.shape.extents
onready var bounds = Rect2(-extents, extents * 2)
	
func _physics_process(_delta):
	var dist = position.distance_to(path[step])
	if dist > 1:
		velocity = path[step] - position
		velocity = move_and_slide(velocity)
	else:
		step = (step + 1) % 2
	check_mouse()
	
func check_mouse():
	var mouse_pos = get_local_mouse_position()
	if bounds.has_point(mouse_pos):
		if not mouse_over:
			print("ENTERED SHAPE")
		mouse_over = true
	else:
		if mouse_over:
			print("EXITED SHAPE")
		mouse_over = false

Thank you both! I get that input events only happen upon actual input, so not moving the mouse doesn't trigger anything. I will look into checking for overlaps myself, perhaps keeping it down to a minimum (using mouse_entered event, and then checking bounds myself to simulate mouse_exited).

I don't think this classifies as a bug. Events need triggering, and even though it's not optimal for my situation, the behaviour is to be expected.

I think I found a Godotesque way of solving this particular problem: I hide the OS mouse cursor and use an Area2D scene with a sprite and a tiny CollisionShape2D at the tip of the arrow image to detect collisions with bodies and areas beneath the cursor. Cursor is simply moved by a 'position = get_global_mouse_position()' in the _input() method.

This way, I get exited signals when either of the two (the cursor or the hovered object) mouse away from the other. It also lets me filter the front object when there's more than one node beneath the cursor and calls (un)hover() and (de)select() methods in the hovered/selected object from the cursor's script. Using layers/masks I can also make sure entered and exited signals are only fired on shapes I need.

I thought about @cybereality's workaround, but that wasn't it for me. I have several bodies/areas that can be hovered/selected and I would have to fit a custom check to all of them. Would work with a single node to check, but not so much for a lot of them.

That's a good idea. It's not a hack if it works!

a year later