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:
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.