How to detect if the mouse has stopped moving?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Sersch

I need to detect if the mouse has stopped moving in an FPS game.

I’m capturing the cursor by using Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED).

Because of that I have to use event.relative to detect relative mouse movement, like this:

if event is InputEventMouseMotion:
    var mouse_motion = event.relative

However, the mouse_motion variable will never be (0,0), because the value is no longer updated when the mouse stops moving. This is intended behaviour, but doesn’t really help.

Is there a way to detect if the mouse has stopped moving? I could use a short timer that resets every time the mouse moves, but that feels like a workaround.

:bust_in_silhouette: Reply From: jgodfrey

I assume you want to know if it’s stopped moving for some minimal amount of time. In that case, I don’t know how you can avoid some sort of time element in the solution.

Whether that’s a timer (as you mentioned), or tracking an accumulateddelta in _process, or something else, I think you have to somehow involve time.

Perhaps you could remember the most recent set of mouse coords, and, if they’re (reasonably) the same at some point in the future, the mouse hasn’t moved. How far into the future, and how you elect to track that time, is up to you I guess…

maybe the “no time” element he’s looking for is just “asap” kinda thing so on the next pass check if it’s been updated or the same (same = no movement)

Kalmier | 2020-02-14 17:38

Maybe, but making the check between two consecutive frames is likely too fast, and would not reliably detect what would typically be perceived as a “no motion” state.

jgodfrey | 2020-02-14 18:02

Thank you for your answer. The first frame without mouse movement would be desired in my case, but it doesn’t have to be that accurate, so a few more frames wil do too. I’ve settled for the Timer solution.

There’s a Timer now with a very short duration of 0.05sec that gets restarted every time the mouse is moved and automatically runs out after the mouse movement has stopped.

Sersch | 2020-02-14 18:33

:bust_in_silhouette: Reply From: worthing

Reviving a dead post just in case someone comes across this and wants a better answer.

The trick is to simply reset the mouse_motion variable (or whatever you chose to name it) when you’re done using it for that frame.

So using op’s code, once you’ve actually used the mouse_motion variable to move the camera or whatever it is you’re using it for

mouse_motion = Vector2.ZERO

That way, if on the next frame there is no mouse movement, mouse_motion will already be equal to zero.

Can you clarify what you mean by ''reset the variable when you’re done using it for that frame"?

Assuming it’s called from within _input() (as per the OP’s code), where would it fit?

I tried:
_input(event): If InputEventMouseMotion: var mouse_motion = event.relative function(mouse_motion) mouse motion = Vector2.ZERO

Thanks!

mstfacmly | 2021-03-17 14:05

I think this is the right answer, better than the time based suggestion.

InputEventMouseMotion only get fired when the mouse actually moves, so you won’t ever get one in _input() with a relative or speed of Vector2.ZERO when the mouse stops.

Instead you want to reset any mouse movement “state” you are (setting in _input()) in _process() which will get called every frame.

mattkg0 | 2022-01-31 23:33

:bust_in_silhouette: Reply From: Ermiq

What I did is created a singleton that handles mouse motion and resets the values in the next frame if there was no input.
Put it in AutoLoad and you’ll be able to get proper mouse data. The values are also become available from _process() and _physics_process() like

func _process(delta):
    print(str(MouseInput.relative))

I also added mouse wheel scrolls here to be able to get the scrolls state from _process() as well.

extends Node
class_name MouseInput

var relative : Vector2
var scrollFwd : int
var scrollBck : int

var lastFrame : float
var lastMouseMove : float
var lastScrollF : float
var lastScrollB : float

func _process(delta):
    if lastMouseMove < lastFrame:
        relative = Vector2.ZERO

    if lastScrollF < lastFrame:
        scrollFwd = 0;

    if lastScrollB < lastFrame:
        scrollBck = 0

    lastFrame += delta

func _input(event):
    if event is InputEventMouseMotion:
        lastMouseMove = lastFrame
        relative = event.relative
    elif event is InputEventMouseButton:
        if event.button_index == 4:
            lastScrollF = lastFrame
            scrollFwd = 1
        elif event.button_index == 5:
            lastScrollB = lastFrame
            scrollBck = 1