diff --git a/Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs b/Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs index fabbfe89b4..16e927676f 100644 --- a/Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs +++ b/Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs @@ -2165,10 +2165,266 @@ public void TODO_PlayerInput_CanSetUpSplitScreen_AndMaintainFixedNumberOfScreens [Test] [Category("PlayerInput")] - [Ignore("TODO")] - public void TODO_PlayerInput_CanSetUpSplitScreen_AndManuallyAllocatePlayersToScreens() + public void PlayerInput_CanSetUpSplitScreen_AndManuallyAllocatePlayersToScreens() { - Assert.Fail(); + var actions = InputActionAsset.FromJson(kActions); + + var playerPrefab = new GameObject(); + playerPrefab.SetActive(false); + playerPrefab.AddComponent(); + playerPrefab.AddComponent(); + playerPrefab.GetComponent().camera = playerPrefab.GetComponent(); + playerPrefab.GetComponent().actions = actions; + + var manager = new GameObject(); + var managerComponent = manager.AddComponent(); + managerComponent.notificationBehavior = PlayerNotifications.InvokeCSharpEvents; + managerComponent.joinBehavior = PlayerJoinBehavior.JoinPlayersManually; + managerComponent.playerPrefab = playerPrefab; + managerComponent.splitScreen = true; + + var gamepad1 = InputSystem.AddDevice(); + var gamepad2 = InputSystem.AddDevice(); + var gamepad3 = InputSystem.AddDevice(); + var gamepad4 = InputSystem.AddDevice(); + + var playerIndex = 0; + + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), + Is.EquivalentTo(new[] { gamepad1, gamepad2, gamepad3, gamepad4 })); + + // Join two players manually and make sure we get two screen side-by-side. + managerComponent.JoinPlayer(playerIndex, playerIndex, "Gamepad", gamepad1); + playerIndex++; + managerComponent.JoinPlayer(playerIndex, playerIndex, "Gamepad", gamepad2); + playerIndex++; + + Assert.That(PlayerInput.all, Has.Count.EqualTo(2)); + + Assert.That(PlayerInput.all[0].devices, Is.EquivalentTo(new[] { gamepad1 })); + Assert.That(PlayerInput.all[1].devices, Is.EquivalentTo(new[] { gamepad2 })); + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), + Is.EquivalentTo(new[] { gamepad3, gamepad4 })); + + Assert.That(PlayerInput.all[0].splitScreenIndex, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].splitScreenIndex, Is.EqualTo(1)); + + // Player #1: Upper Left. + Assert.That(PlayerInput.all[0].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.height, Is.EqualTo(1)); + + // Player #2: Upper Right. + Assert.That(PlayerInput.all[1].camera.rect.x, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.height, Is.EqualTo(1)); + + // Add one more player and make sure we got a 2x2 setup. + managerComponent.JoinPlayer(playerIndex, playerIndex, "Gamepad", gamepad3); + playerIndex++; + + Assert.That(PlayerInput.all, Has.Count.EqualTo(3)); + + Assert.That(PlayerInput.all[0].devices, Is.EquivalentTo(new[] { gamepad1 })); + Assert.That(PlayerInput.all[1].devices, Is.EquivalentTo(new[] { gamepad2 })); + Assert.That(PlayerInput.all[2].devices, Is.EquivalentTo(new[] { gamepad3 })); + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), + Is.EquivalentTo(new[] { gamepad4 })); + + Assert.That(PlayerInput.all[0].splitScreenIndex, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].splitScreenIndex, Is.EqualTo(1)); + Assert.That(PlayerInput.all[2].splitScreenIndex, Is.EqualTo(2)); + + // Player #1: Upper Left. + Assert.That(PlayerInput.all[0].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #2: Upper Right. + Assert.That(PlayerInput.all[1].camera.rect.x, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #3: Lower Left. + Assert.That(PlayerInput.all[2].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[2].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Join one more player and make sure we got a fully filled 2x2 setup. + managerComponent.JoinPlayer(playerIndex, playerIndex, "Gamepad", gamepad4); + playerIndex++; + + Assert.That(PlayerInput.all, Has.Count.EqualTo(4)); + + Assert.That(PlayerInput.all[0].devices, Is.EquivalentTo(new[] { gamepad1 })); + Assert.That(PlayerInput.all[1].devices, Is.EquivalentTo(new[] { gamepad2 })); + Assert.That(PlayerInput.all[2].devices, Is.EquivalentTo(new[] { gamepad3 })); + Assert.That(PlayerInput.all[3].devices, Is.EquivalentTo(new[] { gamepad4 })); + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), Is.Empty); + + Assert.That(PlayerInput.all[0].splitScreenIndex, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].splitScreenIndex, Is.EqualTo(1)); + Assert.That(PlayerInput.all[2].splitScreenIndex, Is.EqualTo(2)); + Assert.That(PlayerInput.all[3].splitScreenIndex, Is.EqualTo(3)); + + // Player #1: Upper Left. + Assert.That(PlayerInput.all[0].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #2: Upper Right. + Assert.That(PlayerInput.all[1].camera.rect.x, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #3: Lower Left. + Assert.That(PlayerInput.all[2].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[2].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #4: Lower Right. + Assert.That(PlayerInput.all[3].camera.rect.x, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[3].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[3].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[3].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Unjoin the player in the upper right and make sure the other players stay where they are. + Object.DestroyImmediate(PlayerInput.all[1].gameObject); + + Assert.That(PlayerInput.all, Has.Count.EqualTo(3)); + + Assert.That(PlayerInput.all[0].devices, Is.EquivalentTo(new[] { gamepad1 })); + Assert.That(PlayerInput.all[1].devices, Is.EquivalentTo(new[] { gamepad3 })); + Assert.That(PlayerInput.all[2].devices, Is.EquivalentTo(new[] { gamepad4 })); + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), + Is.EquivalentTo(new[] { gamepad2 })); + + Assert.That(PlayerInput.all[0].splitScreenIndex, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].splitScreenIndex, Is.EqualTo(2)); + Assert.That(PlayerInput.all[2].splitScreenIndex, Is.EqualTo(3)); + + // Player #1: Upper Left. + Assert.That(PlayerInput.all[0].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #3: Lower Left. + Assert.That(PlayerInput.all[1].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #4: Lower Right. + Assert.That(PlayerInput.all[2].camera.rect.x, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[2].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.width, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[2].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Join a new player but manually allocate the next index. + // The split-screen setup goes to 3x2 because we are adding it to the end leaving the second empty + managerComponent.JoinPlayer(playerIndex, playerIndex, "Gamepad", gamepad2); + playerIndex++; + + Assert.That(PlayerInput.all, Has.Count.EqualTo(4)); + + // PlayerInput.all is sorted by playerIndex so the player we just joined should be in the last slot. + Assert.That(PlayerInput.all[0].devices, Is.EquivalentTo(new[] { gamepad1 })); + Assert.That(PlayerInput.all[1].devices, Is.EquivalentTo(new[] { gamepad3 })); + Assert.That(PlayerInput.all[2].devices, Is.EquivalentTo(new[] { gamepad4 })); + Assert.That(PlayerInput.all[3].devices, Is.EquivalentTo(new[] { gamepad2 })); + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), Is.Empty); + + Assert.That(PlayerInput.all[0].splitScreenIndex, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].splitScreenIndex, Is.EqualTo(2)); + Assert.That(PlayerInput.all[2].splitScreenIndex, Is.EqualTo(3)); + Assert.That(PlayerInput.all[3].splitScreenIndex, Is.EqualTo(4)); + + // Player #1: Upper Left. + Assert.That(PlayerInput.all[0].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Upper Middle will be empty + + // Player #3: Upper Right. + Assert.That(PlayerInput.all[1].camera.rect.x, Is.EqualTo(2 * (1 / 3.0)).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #4: Lower Left. + Assert.That(PlayerInput.all[2].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[2].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #2: Lower Middle. + Assert.That(PlayerInput.all[3].camera.rect.x, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[3].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[3].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[3].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Join yet another player and make sure the split-screen setup goes to 3x2. + var gamepad5 = InputSystem.AddDevice(); + managerComponent.JoinPlayer(playerIndex, playerIndex, "Gamepad", gamepad5); + playerIndex++; + + Assert.That(PlayerInput.all, Has.Count.EqualTo(5)); + + Assert.That(PlayerInput.all[0].devices, Is.EquivalentTo(new[] { gamepad1 })); + Assert.That(PlayerInput.all[1].devices, Is.EquivalentTo(new[] { gamepad3 })); + Assert.That(PlayerInput.all[2].devices, Is.EquivalentTo(new[] { gamepad4 })); + Assert.That(PlayerInput.all[3].devices, Is.EquivalentTo(new[] { gamepad2 })); + Assert.That(PlayerInput.all[4].devices, Is.EquivalentTo(new[] { gamepad5 })); + Assert.That(InputUser.GetUnpairedInputDevices().ToArray(dispose: true), Is.Empty); + + Assert.That(PlayerInput.all[0].splitScreenIndex, Is.EqualTo(0)); + Assert.That(PlayerInput.all[1].splitScreenIndex, Is.EqualTo(2)); + Assert.That(PlayerInput.all[2].splitScreenIndex, Is.EqualTo(3)); + Assert.That(PlayerInput.all[3].splitScreenIndex, Is.EqualTo(4)); + Assert.That(PlayerInput.all[4].splitScreenIndex, Is.EqualTo(5)); + + // Player #1: Upper Left. + Assert.That(PlayerInput.all[0].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[0].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[0].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Upper Middle will be empty + + // Player #3: Upper Right. + Assert.That(PlayerInput.all[1].camera.rect.x, Is.EqualTo(2 * (1 / 3.0)).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.y, Is.EqualTo(0.5).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[1].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #4: Lower Left. + Assert.That(PlayerInput.all[2].camera.rect.x, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[2].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[2].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #2: Lower Middle. + Assert.That(PlayerInput.all[3].camera.rect.x, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[3].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[3].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[3].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); + + // Player #5: Lower Right. + Assert.That(PlayerInput.all[4].camera.rect.x, Is.EqualTo(2 * (1 / 3.0)).Within(0.00001)); + Assert.That(PlayerInput.all[4].camera.rect.y, Is.EqualTo(0)); + Assert.That(PlayerInput.all[4].camera.rect.width, Is.EqualTo(1 / 3.0).Within(0.00001)); + Assert.That(PlayerInput.all[4].camera.rect.height, Is.EqualTo(0.5).Within(0.00001)); } // https://fogbugz.unity3d.com/f/cases/1260625/ diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 0a7ecaf744..a5714222ea 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -15,6 +15,7 @@ however, it has to be formatted properly to pass verification tests. - Fixed an issue causing a number of errors to be displayed when using `InputTestFixture` in playmode tests with domain reloading disabled on playmode entry. [ISXB-1446](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1446) - Fixed issue where user was not prompted to save changes when loading a second input actions asset into an already opened editor. [ISXB-1343](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1343) - Fixed an issue on macOS which didn't detect up-left DPAD presses for Xbox controllers. [ISXB-810](https://issuetracker.unity3d.com/issues/macos-d-pad-upper-left-corner-is-not-logged-with-the-xbox-controller) +- Fixed an issue when providing JoinPlayer with a specific split screen index. [ISXB-897](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-897) ## [1.14.0] - 2025-03-20 diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs index 9468230bab..00c09d7bed 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs @@ -1831,7 +1831,7 @@ private void OnEnable() // Split-screen index defaults to player index. if (s_InitSplitScreenIndex >= 0) - m_SplitScreenIndex = splitScreenIndex; + m_SplitScreenIndex = s_InitSplitScreenIndex; else m_SplitScreenIndex = playerIndex;