How to get InputEventMouseMotion through multiple SubViewports?

Godot Version

4.5.1

Question

I’m making this game and I’m making a split-screen multiplayer. I want first player to play with the mouse and the other player with the gamepad. I need the mouse to be captured, of course.

Unfortunately, in this configuration (with more than one SubViewports) I cant get InputEventMouseMotion in any of subviewports.

In single player, with only one subviewport everything work as it should.

I understand that the problem lies in the fact that the two subviewports split the screen and each uses the mouse position.

For now, I have patched the mouse movement by updating a variable in GameSplitScreenDual that I read in the player. But the solution is a patchwork and shows its limitations if I want to read an Input.is_action_just_released, for example.

Is there any way to read the entire input from the mouse directly after the subviewport?

It should work as expected. How do you catch events in viewports?

Well, in master node I have this:

var mouse_event_relative: Vector2

func _ready() → void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event):
	if event is InputEventMouseMotion:
	mouse_event_relative = event.relative
	await get_tree().create_timer(0.1).timeout
	mouse_event_relative = Vector2.ZERO

As I said, it is not the perfect solution: without that last line the player will turn forever.

In player script I have this:

func use_mouse(_delta) -> void:
	rotate_from_vector(get_owner().mouse_event_relative * some_mouse_acceleration)

func rotate_from_vector(v: Vector2) -> void:
	if v.x == 0.0: return
	rotation.y -= v.x

func _input(event) -> void:
	if Input.is_action_just_released(controls.action_1):
		is_action_pressed = false

I’m having trouble with Input.is_action_just_released() witch does work in single screen, but does not ALWAYS work in split screen. This action triggered by Left Mouse Button.

Master node script should call the rotation change on the player immediately when it receives the event. What’s the point of storing it like this? If you insist on storing it then store it in the player script and let the player reset it to zero after using it. Get rid of that await.

I solved the problem by forwarding the input received from the mouse from SubViewport. I found the solution here:
https://www.reddit.com/r/godot/comments/tx9x2b/a_guide_to_mouse_events_in_subviewports/

What’s the point of forwarding the event in this case? The viewport context is not really relevant for the relative motion of the captured mouse.

You’re right. That’s not the solution. Still digging.

Just capture the event outside of viewports like you’re already doing and call any updating functions in the player that relies on the mouse change. Or, from the same handler, simply accumulate the mouse delta in some variable in the player.

main:

func _input(event):
	if event is InputEventMouseMotion:
		player.mouse_moved(event.relative)

player:

var mouse_total_delta := Vector2.ZERO

func mouse_moved(delta: Vector2):
	mouse_total_delta += delta

func _process(dt: float):
	rotation.y += mouse_total_delta * dt # or whatever
	mouse_total_delta = Vector2.ZERO

2 Likes

Where you able to resolve this? Because I have a similar issue where I forward GUI events to a SubViewport, but unfortunately the only way to receive MouseMotion events is when I hold down a mouse button.

Subviewport in a container should catch the motion event if the pointer is inside it.

I solved the problem: in the main node (owner) I have a script that intercepts all input and redirects it to a function of a child node (player):

func _ready():
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	set_process_unhandled_input(true)


func _input(event):
	if event is InputEventMouse:
		var mouseEvent = event.duplicate()
		player.unhandled_input(mouseEvent)
	else:
		player.unhandled_input(event)

In the player I have the function that manages the input received from the owner:

func unhandled_input(event) -> void:
	if event is InputEventMouseMotion:
		rotate_from_vector(event.relative * mouse_acceleration)
	if event.is_action(controls.action):
		do_action()
1 Like