Aiming with mouse in 3D splitscreen game

Godot Version

4.3

Question

I’m making a 3D game with splitscreen. I have two characters, one controlled by keyboard and mouse, and the other by a gamepad. Before implementing splitscreen the controls worked fine, but after that I can’t use my mouse to aim with player1 anymore. I believe the control nodes used to implement the splitscreen are eating up my mouse movements somehow. All my control nodes have mouse_filter set to pass.

This is my scene setup:
image

And this is my player code, in case the problem is in there. (little explanation on the important parts below)

extends CharacterBody3D

@export var id := 1

@onready var camera_pivot: Node3D = $camera_pivot
@onready var spring_arm: SpringArm3D = $camera_pivot/SpringArm3D
@onready var camera: Camera3D = $camera_pivot/SpringArm3D/Camera3D
@onready var animation_player: AnimationPlayer = $visuals/mixamo_base/AnimationPlayer
@onready var armature: Node3D = $visuals/mixamo_base/Armature
@onready var dash_cooldown: Timer = $DashCooldown

const SPEED := 20.0
const JUMP_VELOCITY : = 12.0
const ACCEL := 12.0
const ACCEL_AIR := 8
const CAMERA_SENSITIVITY := 0.001
const CAMERA_SENSITIVITY_JOY := 0.1
const AIR_JUMPS := 1
const DASH_VELOCITY := 200
const CROUCH_VELOCITY := 150

@onready var accel := ACCEL
@onready var current_air_jumps := 0
@onready var froze = false


func _ready() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event: InputEvent) -> void:
	if Input.is_action_just_pressed("quit"):
		get_tree().quit()
		
	if event is InputEventMouseMotion and id == 1:
		camera_pivot.rotate_y(-event.relative.x * CAMERA_SENSITIVITY)
		spring_arm.rotate_x(-event.relative.y * CAMERA_SENSITIVITY)
		spring_arm.rotation.x = clamp(spring_arm.rotation.x, -PI/2, PI/2)

func _physics_process(delta: float) -> void:
	if id == 2:
		camera_pivot.rotate_y(- Input.get_axis("look_left", "look_right") * CAMERA_SENSITIVITY_JOY)
		spring_arm.rotate_x(- Input.get_axis("look_up", "look_down") * CAMERA_SENSITIVITY_JOY)
		spring_arm.rotation.x = clamp(spring_arm.rotation.x, -PI/2, PI/2)
		
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta
	else:
		if current_air_jumps != 0:
			current_air_jumps = 0

	# Handle jump.
	if Input.is_action_just_pressed("jump_%s" % [id]):
		if is_on_floor():
			velocity.y = JUMP_VELOCITY
		elif is_on_wall():
			velocity += get_wall_normal() * JUMP_VELOCITY * 5
			if velocity.y < 0:
				velocity.y = JUMP_VELOCITY / 2
			else:
				velocity.y += JUMP_VELOCITY / 2
		elif current_air_jumps < AIR_JUMPS:
			velocity.y = JUMP_VELOCITY
			current_air_jumps += 1
	
	if Input.is_action_just_pressed("dash_%s" % [id]) and dash_cooldown.is_stopped():
		var dir = - camera.global_transform.basis.z
		if dir.y > 0:
			dir.y = 0
		velocity += dir * DASH_VELOCITY
		dash_cooldown.start()
	
	if Input.is_action_pressed("crouch_%s" % [id]):
		velocity.y -= CROUCH_VELOCITY * delta
	
	# Get the input direction and handle the movement/deceleration.
	var input_dir := Input.get_vector("left_%s" % [id], "right_%s" % [id], "forward_%s" % [id], "back_%s" % [id])
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	direction = direction.rotated(Vector3.UP, camera_pivot.rotation.y)
	if direction:
		if is_on_floor():
			accel = ACCEL
		else:
			accel = ACCEL_AIR
		velocity.x = lerp(velocity.x, direction.x * SPEED, accel * delta)
		velocity.z = lerp(velocity.z, direction.z * SPEED, accel * delta)
		armature.rotation.y = lerp_angle(armature.rotation.y, atan2(velocity.x, velocity.z), 0.2)
	else:
		velocity.x = lerp(velocity.x, 0.0, accel * delta)
		velocity.z = lerp(velocity.z, 0.0, accel * delta)
	
	if not froze:
		move_and_slide()

I use an id variable and formatted strings to differentiate between the actions in the input map, so for example jump_1 is mapped to spacebar and jump_2 to A on the gamepad. However I didn’t know how to do that for the aiming part, so I kept the old mouse aiming code in func _input:

	if event is InputEventMouseMotion:
		camera_pivot.rotate_y(-event.relative.x * CAMERA_SENSITIVITY)
		spring_arm.rotate_x(-event.relative.y * CAMERA_SENSITIVITY)
		spring_arm.rotation.x = clamp(spring_arm.rotation.x, -PI/2, PI/2)

and added code to move the camera with the joystick in func _physics_process

		camera_pivot.rotate_y(- Input.get_axis("look_left", "look_right") * CAMERA_SENSITIVITY_JOY)
		spring_arm.rotate_x(- Input.get_axis("look_up", "look_down") * CAMERA_SENSITIVITY_JOY)
		spring_arm.rotation.x = clamp(spring_arm.rotation.x, -PI/2, PI/2)

Then I added an if clause to check the player id, as you can see in the full player code above.

Looking around I found this post on reddit who seemed to be about my issue, and proposed to attach this script to the SubViewportContainer to forward all input events to the viewport:

extends ViewportContainer

func _ready():
	set_process_unhandled_input(true)

func _input(event):
	# fix by ArdaE https://github.com/godotengine/godot/issues/17326#issuecomment-431186323
	for child in get_children():
		if event is InputEventMouse:
			var mouseEvent = event.duplicate()
			mouseEvent.position = get_global_transform_with_canvas().affine_inverse() * event.position
			child.unhandled_input(mouseEvent)
		else:
			child.unhandled_input(event)

I changed ViewportContainer to SubViewportContainer but I get the following error: Invalid call. Nonexistent function 'unhandled_input' in base 'SubViewport'. Changing unhandled_input to _unhandled_input also doesn’t work.
I would gladly appreciate some help.

1 Like

child.unhandled_input(mouseEvent) was a syntax, that was valid for Godot 3 and has since been replaced by Viewport.push_input in order to send the input event to the SubViewport.

2 Likes

Thank you!!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.