Flickering Between Mouse and Joystick Input

Godot Version

3.6

Question

I am working on implementing mouse cursor hiding when the user switches to controller input. The current implementation I have does not hide the cursor, but causes it to flicker. This is the code I have that is responsible for the swap.

func _input(event: InputEvent):
	if event is InputEventMouse and not _mouse_active:
		_mouse_active = true
		_swap_to_mouse()
	elif not event is InputEventMouse and _mouse_active:
		_mouse_active = false
		_swap_to_joystick()


# Reveals the mouse cursor at the last recorded position.
func _swap_to_mouse() -> void:
	print("mouse swap")
	warp_mouse(_mouse_position)
	Input.mouse_mode = Input.MOUSE_MODE_CONFINED


# Hides the mouse cursor.
func _swap_to_joystick() -> void:
	print("joystick swap")
	Input.mouse_mode = Input.MOUSE_MODE_CONFINED_HIDDEN

When running this code, the output is a constant swap between mouse and joystick. What I’ve found is that when I comment out the line that sets the mouse mode to MOUSE_MODE_CONFINED, the instant swapping stops, only printing when one or the other input is used. This code is part of an autoload.

Try to debug print the event that is causing the swap - I’m sure it’ll get you some clues on how to fix the issue.
I’m guessing there is some more guarding required in your if statement.

I concur with @wchc – you could have all sorts of things coming into _input() that are neither mouse nor joystick, depending on your setup.

Something else to consider is that a lot of gamepads have noisy sticks; even if left untouched, they can generate values that wander a bit, which Godot may well treat as valid input. If you’ve got anything with an accelerometer, it’ll be even worse.

1 Like

Using debugging print I found that I can further isolate the input to only look for joypad motion.

func _input(event: InputEvent):
	if event is InputEventMouse and not _mouse_active:
		_mouse_active = true
		_swap_to_mouse()
	elif event is InputEventJoypadMotion and _mouse_active:
		_mouse_active = false
		_swap_to_joystick()

This does not resolve the flicker issue though. I’ve found that the mouse is not considered initially active when the scene starts. What I’ve also found is that the flicker arises as a result of both functions updating the mouse mode.

Specifically, when _swap_to_joystick sets mouse mode to anything other than MOUSE_MODE_CONTAINED while _swap_to_mouse also sets the mouse mode, flickering occurs whenever joystick motion is given. Using MOUSE_MODE_CONTAINED in _swap_to_joystick does not cause a flicker, but it does prevent the mouse position from getting set using warp_mouse. I want to be able to set the mouse position to when switching from joystick to mouse.

Are you getting InputEventJoypadMotion every frame?

No, I only get InputEventJoypadMotion whenever joypad motion is detected. You are right about the noisy stick. There are times where Godot reads InputEventJoypadMotion when I am holding the controller without pressing anything. I’ve added some logic to leverage dead zones by creating custom input maps for the left joystick, as that is what I am currently testing with.

func _input(event: InputEvent):
	var dir_vec: Vector2 = Input.get_vector(
			"left_joystick_l",
			"left_joystick_r",
			"left_joystick_u",
			"left_joystick_d"
	)
	if event is InputEventMouse and not _mouse_active:
		_mouse_active = true
		_swap_to_mouse()
	elif (
		event is InputEventJoypadMotion
		and dir_vec != Vector2.ZERO
		and _mouse_active
	):
		_mouse_active = false
		_swap_to_joystick()

The flicker issue still persists. It’s not mouse mode getting interrupted by joystick, but joystick getting interrupted by mouse, if that helps clarify.

For the time being I am going to have _swap_to_joystick set mouse mode to MOUSE_MODE_CAPTURED when called. This resolves the flickering issue. This is the current code for reference.

enum InputSource {
	MOUSE,
	JOYSTICK,
	NONE
}

func _input(event: InputEvent):
	var left_dir_vec: Vector2 = Input.get_vector(
			"left_joystick_l",
			"left_joystick_r",
			"left_joystick_u",
			"left_joystick_d"
	)
	if event is InputEventMouse and not _input_source == InputSource.MOUSE:
		_input_source = InputSource.MOUSE
		_swap_to_mouse()
	elif (
		event is InputEventJoypadMotion
		and left_dir_vec != Vector2.ZERO
		and not _input_source == InputSource.JOYSTICK
	):
		_input_source = InputSource.JOYSTICK
		_swap_to_joystick()


# Reveals the mouse cursor at the last recorded position.
func _swap_to_mouse() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CONFINED
    warp_mouse(_mouse_position)


# Hides the mouse cursor.
func _swap_to_joystick() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

Just to be clear, in your code you’re using MOUSE_MODE_CAPTURED, which is what you want to use to hide the mouse. The CONTAINED version doesn’t exist.


I have a different solution to recommend. Separate out the mouse and gamepad processing to two different files that just handle that and then update your inputs so that they get the same raw data from either input, and you can query which input device it came from (if you care). Then you don’t need to turn one or the other off and on, and you don’t drop inputs. It also allows you to change things like your UI that tells you what button to press. If you move your mouse, those inputs pop on screen - if you move your gamepad joystick, those inputs replace them immediately.

For example, here’s how I handle controls for looking in first person and third person 3D games:

mouse.gd excerpt
Handles mouse movement and funnels it to Controller class.

func _unhandled_input(event: InputEvent) -> void:
	if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
		if event is InputEventMouseMotion:
			Controller.look = -event.relative * sensitivity

gamepad.gd excerpt
Handles gamepad joystick movement and funnels it to Controller class.

func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventJoypadMotion:
		if event.axis == JOY_AXIS_RIGHT_X:
			Controller.look = Vector2(-event.axis_value * horizontal_look_sensitivity, Controller.look.y)
		if event.axis == JOY_AXIS_RIGHT_Y:
			Controller.look = Vector2(Controller.look.x, -event.axis_value * vertical_look_sensitivity)

controller.gd excerpt
Has a look variable the Player can call every frame without having to do the work itself. Also monitors input being used and lets anything interested know if inputs change. Allows a call for the last input type used and a function that can get the icon related to any action mapped. (Which delegate back to the Gamepad, Mouse and Keyboard classes to get the icons.)

## Allows anything listening (like UI) to know when the input method being
## used changed for something new. Primarily for changing what interaction
## hints and control icons are shown on screen.
signal input_method_changed(last_input_type: LastInput)

## Enumerates the kinds of inputs that are possible.
enum LastInput {
	KEYBOARD_AND_MOUSE,
	GAMEPAD
}

## Stores the amount of movement in the x/y direction that the player is trying
## to look in a 3D game.
var look := Vector2.ZERO
## Stores the last input used by the player for UI interaction hint updates.
## Private so that it cannot be modified extrenally. This is a read-only value
## outside this singleton. (Or would be if GDScript enforced that.)
var _last_input_type := LastInput.KEYBOARD_AND_MOUSE


## Updates the last input type for use throughout the game.
func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton or event is InputEventMouseMotion or event is InputEventKey:
		if _last_input_type != LastInput.KEYBOARD_AND_MOUSE:
			input_method_changed.emit(LastInput.KEYBOARD_AND_MOUSE)
		_last_input_type = LastInput.KEYBOARD_AND_MOUSE
	elif event is InputEventJoypadButton or event is InputEventJoypadMotion:
		if _last_input_type != LastInput.GAMEPAD:
			input_method_changed.emit(LastInput.GAMEPAD)
		_last_input_type = LastInput.GAMEPAD


## Returns the last input type used by the player.
func get_last_input_type() -> LastInput:
	return _last_input_type


## Returns the correct Texture2D representation of the action passed based on
## the last input type used by the player - which can be specified. (Useful when
## the type changes and you want to update the UI immediately - resovles race conditions.)
func get_action_icon(action_name: String, input_type: LastInput = _last_input_type) -> Texture2D:
	var events = InputMap.action_get_events(action_name)
	for event in events:
		if input_type == LastInput.KEYBOARD_AND_MOUSE:
			if event is InputEventKey:
				return Keyboard.get_key_icon(event)
			if event is InputEventMouse:
				return Mouse.get_mouse_icon(event)
		else:
			if event is InputEventJoypadButton or event is InputEventJoypadMotion:
				return Gamepad.get_gamepad_icon(event)
	return null

Anyway, just a thought. If you want to see all the code, I created a plugin that I use for my game jam games. I just finished a jam and will be making a number of updates soon from what I learned. Dragonforge Controller 0.10.1 I made it for myself, but figured it might help others in the community at some point. I actually just used it for a 2D game most recently. Was great for the UI.

1 Like

Thanks for the catch, I’ve edited my post to account for the error. Also thanks for the recommendation about splitting the input processes.

1 Like