From ea1676c433becb9fea7118e3f81690bd9a93e0a0 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Tue, 29 Jul 2025 15:53:46 +0100 Subject: [PATCH 01/11] Add new SwitchProControlleriOS device for iOS, which has swapped face buttons Also includes new state, for any devices which use the Nintendo-y button bindings --- .../Plugins/iOS/IOSGameController.cs | 97 +++++++++++++++++++ .../InputSystem/Plugins/iOS/iOSSupport.cs | 6 ++ 2 files changed, 103 insertions(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs index 5e65219e78..98062d04e5 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs @@ -1,5 +1,6 @@ #if UNITY_EDITOR || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS || PACKAGE_DOCS_GENERATION using System.Runtime.InteropServices; +using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.DualShock; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; @@ -95,6 +96,66 @@ public iOSGameControllerState WithAxis(iOSAxis axis, float value) return this; } } + + /// + /// State for iOS Gamepads using a layout where B button is south, A is east, X is north, and Y is west + /// This layout is typically seen on Nintendo gamepads, such as the Switch Pro Controller. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct iOSGameControllerStateSwappedFaceButtons : IInputStateTypeInfo + { + public static FourCC kFormat = new FourCC('I', 'G', 'C', ' '); + public const int MaxButtons = (int)iOSButton.Select + 1; + public const int MaxAxis = (int)iOSAxis.RightStickY + 1; + + [InputControl(name = "dpad")] + [InputControl(name = "dpad/up", bit = (uint)iOSButton.DpadUp)] + [InputControl(name = "dpad/right", bit = (uint)iOSButton.DpadRight)] + [InputControl(name = "dpad/down", bit = (uint)iOSButton.DpadDown)] + [InputControl(name = "dpad/left", bit = (uint)iOSButton.DpadLeft)] + [InputControl(name = "buttonSouth", bit = (uint)iOSButton.B)] + [InputControl(name = "buttonWest", bit = (uint)iOSButton.Y)] + [InputControl(name = "buttonNorth", bit = (uint)iOSButton.X)] + [InputControl(name = "buttonEast", bit = (uint)iOSButton.A)] + [InputControl(name = "leftStickPress", bit = (uint)iOSButton.LeftStick)] + [InputControl(name = "rightStickPress", bit = (uint)iOSButton.RightStick)] + [InputControl(name = "leftShoulder", bit = (uint)iOSButton.LeftShoulder)] + [InputControl(name = "rightShoulder", bit = (uint)iOSButton.RightShoulder)] + [InputControl(name = "start", bit = (uint)iOSButton.Start)] + [InputControl(name = "select", bit = (uint)iOSButton.Select)] + public uint buttons; + + [InputControl(name = "leftTrigger", offset = sizeof(uint) + sizeof(float) * (uint)iOSButton.LeftTrigger)] + [InputControl(name = "rightTrigger", offset = sizeof(uint) + sizeof(float) * (uint)iOSButton.RightTrigger)] + public fixed float buttonValues[MaxButtons]; + + private const uint kAxisOffset = sizeof(uint) + sizeof(float) * MaxButtons; + [InputControl(name = "leftStick", offset = (uint)iOSAxis.LeftStickX * sizeof(float) + kAxisOffset)] + [InputControl(name = "rightStick", offset = (uint)iOSAxis.RightStickX * sizeof(float) + kAxisOffset)] + public fixed float axisValues[MaxAxis]; + + public FourCC format => kFormat; + + public iOSGameControllerStateSwappedFaceButtons WithButton(iOSButton button, bool value = true, float rawValue = 1.0f) + { + buttonValues[(int)button] = rawValue; + + Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask"); + var bit = 1U << (int)button; + if (value) + buttons |= bit; + else + buttons &= ~bit; + + return this; + } + + public iOSGameControllerStateSwappedFaceButtons WithAxis(iOSAxis axis, float value) + { + axisValues[(int)axis] = value; + return this; + } + } } namespace UnityEngine.InputSystem.iOS @@ -134,5 +195,41 @@ public class DualShock4GampadiOS : DualShockGamepad public class DualSenseGampadiOS : DualShockGamepad { } + + /// + /// A Switch Pro Controller connected to an iOS device. + /// If you use InputSystem.GetDevice, you must query for this class rather than Gamepad in order for aButton, bButton, yButton and xButton to be correct + /// + [InputControlLayout(stateType = typeof(iOSGameControllerStateSwappedFaceButtons), displayName = "iOS Switch Pro Controller Gamepad")] + public class SwitchProControlleriOS : Gamepad + { + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonSouth, whereas this class returns the state of buttonEast + /// + public new ButtonControl aButton => buttonEast; + + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonEast, whereas this class returns the state of buttonSouth + /// + public new ButtonControl bButton => buttonSouth; + + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonNorth, whereas this class returns the state of buttonWest + /// + public new ButtonControl yButton => buttonWest; + + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonWest, whereas this class returns the state of buttonNorth + /// + public new ButtonControl xButton => buttonNorth; + } } #endif // UNITY_EDITOR || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs index 981bac4fad..cb7fc3b019 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/iOSSupport.cs @@ -37,6 +37,12 @@ public static void Initialize() .WithDeviceClass("iOSGameController") .WithProduct("DualSense Wireless Controller")); + InputSystem.RegisterLayout("SwitchProGamepadiOS", + matches: new InputDeviceMatcher() + .WithInterface("iOS") + .WithDeviceClass("iOSGameController") + .WithProduct("Pro Controller")); + InputSystem.RegisterLayoutMatcher("GravitySensor", new InputDeviceMatcher() .WithInterface("iOS") From 9ab29ab610f9e416faa29ba4ed1d68e6ff11640a Mon Sep 17 00:00:00 2001 From: Val Knight Date: Tue, 29 Jul 2025 15:54:11 +0100 Subject: [PATCH 02/11] Correct Switch Pro Controller button bindings for HID desktop --- .../Plugins/Switch/SwitchProControllerHID.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs index 6a619301da..8321deb128 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs @@ -155,6 +155,34 @@ public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEve [InputControl(name = "home", displayName = "Home")] public ButtonControl homeButton { get; protected set; } + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonSouth, whereas this class returns the state of buttonEast + /// + public new ButtonControl aButton => buttonEast; + + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonEast, whereas this class returns the state of buttonSouth + /// + public new ButtonControl bButton => buttonSouth; + + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonNorth, whereas this class returns the state of buttonWest + /// + public new ButtonControl yButton => buttonWest; + + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonWest, whereas this class returns the state of buttonNorth + /// + public new ButtonControl xButton => buttonNorth; + protected override void OnAdded() { base.OnAdded(); From 2908eeeb39587d8e52f317c07e1412dbdbd00664 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Tue, 29 Jul 2025 15:55:39 +0100 Subject: [PATCH 03/11] Add tests for Switch face buttons (hid + ios) --- Assets/Tests/InputSystem/Plugins/iOSTests.cs | 47 ++++++++++++++++++++ Assets/Tests/InputSystem/SwitchTests.cs | 4 ++ 2 files changed, 51 insertions(+) diff --git a/Assets/Tests/InputSystem/Plugins/iOSTests.cs b/Assets/Tests/InputSystem/Plugins/iOSTests.cs index 941e7072ed..84564b1733 100644 --- a/Assets/Tests/InputSystem/Plugins/iOSTests.cs +++ b/Assets/Tests/InputSystem/Plugins/iOSTests.cs @@ -56,13 +56,60 @@ public void Devices_SupportsiOSGamePad(string product, Type deviceType, Type par Assert.That(gamepad.rightTrigger.ReadValue(), Is.EqualTo(0.456).Within(0.000001)); AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.A), gamepad.buttonSouth); + AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.A), gamepad.aButton); AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.X), gamepad.buttonWest); + AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.X), gamepad.xButton); AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.Y), gamepad.buttonNorth); + AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.Y), gamepad.yButton); AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.B), gamepad.buttonEast); + AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.B), gamepad.bButton); AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.LeftShoulder), gamepad.leftShoulder); AssertButtonPress(gamepad, new iOSGameControllerState().WithButton(iOSButton.RightShoulder), gamepad.rightShoulder); } + [Test] + [Category("Devices")] + // this is a new test, as we need to assert the Nintendo layout (e.g. buttonSouth == B button) + public void Devices_SupportsProControlleriOS() + { + var device = InputSystem.AddDevice( + new InputDeviceDescription + { + interfaceName = "iOS", + deviceClass = "iOSGameController", + product = "Pro Controller" + }); + Assert.That(device, Is.TypeOf(typeof(SwitchProControlleriOS))); + Assert.That(device, Is.InstanceOf(typeof(Gamepad))); + + var gamepad = (SwitchProControlleriOS)device; + + InputSystem.QueueStateEvent(gamepad, + new iOSGameControllerStateSwappedFaceButtons() + .WithButton(iOSButton.LeftTrigger, true, 0.123f) + .WithButton(iOSButton.RightTrigger, true, 0.456f) + .WithAxis(iOSAxis.LeftStickX, 0.789f) + .WithAxis(iOSAxis.LeftStickY, 0.987f) + .WithAxis(iOSAxis.RightStickX, 0.654f) + .WithAxis(iOSAxis.RightStickY, 0.321f)); + InputSystem.Update(); + + var leftStickDeadzone = gamepad.leftStick.TryGetProcessor(); + var rightStickDeadzone = gamepad.leftStick.TryGetProcessor(); + + Assert.That(gamepad.leftStick.ReadValue(), Is.EqualTo(leftStickDeadzone.Process(new Vector2(0.789f, 0.987f)))); + Assert.That(gamepad.rightStick.ReadValue(), Is.EqualTo(rightStickDeadzone.Process(new Vector2(0.654f, 0.321f)))); + Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.123).Within(0.000001)); + Assert.That(gamepad.rightTrigger.ReadValue(), Is.EqualTo(0.456).Within(0.000001)); + // testing for Pro Controller layout... + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.A), gamepad.buttonEast); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.X), gamepad.buttonNorth); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.Y), gamepad.buttonWest); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.B), gamepad.buttonSouth); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.LeftShoulder), gamepad.leftShoulder); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.RightShoulder), gamepad.rightShoulder); + } + [Test] [Category("Devices")] [TestCase("Gravity", typeof(GravitySensor))] diff --git a/Assets/Tests/InputSystem/SwitchTests.cs b/Assets/Tests/InputSystem/SwitchTests.cs index 5828480b08..b82287a81e 100644 --- a/Assets/Tests/InputSystem/SwitchTests.cs +++ b/Assets/Tests/InputSystem/SwitchTests.cs @@ -56,9 +56,13 @@ public void Devices_SupportsHIDNpad() Assert.That(currentRight, Is.EqualTo(expectedRight).Using(new Vector2EqualityComparer(0.01f))); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.A), controller.buttonEast); + AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.A), controller.aButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.B), controller.buttonSouth); + AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.B), controller.bButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.X), controller.buttonNorth); + AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.X), controller.xButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.Y), controller.buttonWest); + AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.Y), controller.yButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.StickL), controller.leftStickButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.StickR), controller.rightStickButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.L), controller.leftShoulder); From 50d38242b26273d4745bb8af3480c42988ec95c7 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Tue, 29 Jul 2025 16:29:06 +0100 Subject: [PATCH 04/11] Fix formatting --- .../InputSystem/Plugins/Switch/SwitchProControllerHID.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs index 8321deb128..8a0bf09c80 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs @@ -161,21 +161,21 @@ public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEve /// The gamepad class will return the state of buttonSouth, whereas this class returns the state of buttonEast /// public new ButtonControl aButton => buttonEast; - + /// /// A Button for a Nintendo Switch Pro Controller. /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. /// The gamepad class will return the state of buttonEast, whereas this class returns the state of buttonSouth /// public new ButtonControl bButton => buttonSouth; - + /// /// A Button for a Nintendo Switch Pro Controller. /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. /// The gamepad class will return the state of buttonNorth, whereas this class returns the state of buttonWest /// public new ButtonControl yButton => buttonWest; - + /// /// A Button for a Nintendo Switch Pro Controller. /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. From 95df53da84fa557863385b6ad091601b04bc447b Mon Sep 17 00:00:00 2001 From: Val Knight Date: Thu, 31 Jul 2025 12:33:16 +0100 Subject: [PATCH 05/11] Update changelog --- Packages/com.unity.inputsystem/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 5d522c7ffd..aadcbca1f5 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -13,6 +13,9 @@ however, it has to be formatted properly to pass verification tests. ### Added - Exposed MediaPlayPause, MediaRewind, MediaForward keys on Keyboard. +### Fixed +- Fixed `buttonSouth` returning the state of the east button (and so on for all the buttons) when using a Nintendo Switch Pro Controller on iOS [ISXB-1632](issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1632) + ## [1.14.2] - 2025-08-05 ### Fixed From 9bee64719fe49e7790b3c219c6c6818936563fd0 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Thu, 31 Jul 2025 12:44:09 +0100 Subject: [PATCH 06/11] Add test coverage for aButton & friends on iOS --- Assets/Tests/InputSystem/Plugins/iOSTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Tests/InputSystem/Plugins/iOSTests.cs b/Assets/Tests/InputSystem/Plugins/iOSTests.cs index 84564b1733..50ff4c11de 100644 --- a/Assets/Tests/InputSystem/Plugins/iOSTests.cs +++ b/Assets/Tests/InputSystem/Plugins/iOSTests.cs @@ -103,9 +103,13 @@ public void Devices_SupportsProControlleriOS() Assert.That(gamepad.rightTrigger.ReadValue(), Is.EqualTo(0.456).Within(0.000001)); // testing for Pro Controller layout... AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.A), gamepad.buttonEast); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.A), gamepad.aButton); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.X), gamepad.buttonNorth); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.X), gamepad.xButton); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.Y), gamepad.buttonWest); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.Y), gamepad.yButton); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.B), gamepad.buttonSouth); + AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.B), gamepad.bButton); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.LeftShoulder), gamepad.leftShoulder); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.RightShoulder), gamepad.rightShoulder); } From f8f6aa42f1bd8e8d263f60a646b64fe03d503db8 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Fri, 1 Aug 2025 17:50:39 +0100 Subject: [PATCH 07/11] Rename test method for Switch Pro Controller support --- Assets/Tests/InputSystem/Plugins/iOSTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Tests/InputSystem/Plugins/iOSTests.cs b/Assets/Tests/InputSystem/Plugins/iOSTests.cs index 50ff4c11de..44328347cb 100644 --- a/Assets/Tests/InputSystem/Plugins/iOSTests.cs +++ b/Assets/Tests/InputSystem/Plugins/iOSTests.cs @@ -70,7 +70,7 @@ public void Devices_SupportsiOSGamePad(string product, Type deviceType, Type par [Test] [Category("Devices")] // this is a new test, as we need to assert the Nintendo layout (e.g. buttonSouth == B button) - public void Devices_SupportsProControlleriOS() + public void Devices_SupportsSwitchProControlleriOS() { var device = InputSystem.AddDevice( new InputDeviceDescription From 0f0bf61af775153ec7480bc5207aaf840f98edb5 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Mon, 4 Aug 2025 12:03:30 +0100 Subject: [PATCH 08/11] Refactor to have new SwitchProController Gamepad with remapped ABXY buttons --- Assets/Tests/InputSystem/Plugins/iOSTests.cs | 1 + Assets/Tests/InputSystem/SwitchTests.cs | 1 + .../InputSystem/Devices/Gamepad.cs | 35 +++++++++++++++++++ .../Plugins/Switch/SwitchProControllerHID.cs | 30 +--------------- .../Plugins/iOS/IOSGameController.cs | 31 ++-------------- 5 files changed, 40 insertions(+), 58 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/iOSTests.cs b/Assets/Tests/InputSystem/Plugins/iOSTests.cs index 44328347cb..eef11eb36a 100644 --- a/Assets/Tests/InputSystem/Plugins/iOSTests.cs +++ b/Assets/Tests/InputSystem/Plugins/iOSTests.cs @@ -80,6 +80,7 @@ public void Devices_SupportsSwitchProControlleriOS() product = "Pro Controller" }); Assert.That(device, Is.TypeOf(typeof(SwitchProControlleriOS))); + Assert.That(device, Is.InstanceOf(typeof(SwitchProController))); Assert.That(device, Is.InstanceOf(typeof(Gamepad))); var gamepad = (SwitchProControlleriOS)device; diff --git a/Assets/Tests/InputSystem/SwitchTests.cs b/Assets/Tests/InputSystem/SwitchTests.cs index b82287a81e..4d122458ec 100644 --- a/Assets/Tests/InputSystem/SwitchTests.cs +++ b/Assets/Tests/InputSystem/SwitchTests.cs @@ -172,6 +172,7 @@ public void Devices_SupportsSwitchLikeControllers(int vendorId, int productId) }); Assert.That(device, Is.TypeOf()); + Assert.That(device, Is.InstanceOf(typeof(SwitchProController))); } #endif diff --git a/Packages/com.unity.inputsystem/InputSystem/Devices/Gamepad.cs b/Packages/com.unity.inputsystem/InputSystem/Devices/Gamepad.cs index 3841c4d723..df285ca9dc 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/Gamepad.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/Gamepad.cs @@ -806,4 +806,39 @@ public virtual void SetMotorSpeeds(float lowFrequency, float highFrequency) private static int s_GamepadCount; private static Gamepad[] s_Gamepads; } + + /// + /// Base class for Nintendo Switch Pro Controllers that provides the correct button mappings for Nintendo's face button layout where A is east, B is south, X is north, and Y is west. + /// If you use InputSystem.GetDevice and the ABXY properties to represent the labels on the device, you must query for this class + /// + public abstract class SwitchProController : Gamepad + { + /// + /// A Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to a Switch Pro Controller class, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonSouth, whereas this class returns the state of buttonEast + /// + public new ButtonControl aButton => buttonEast; + + /// + /// B Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to a Switch Pro Controller class, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonEast, whereas this class returns the state of buttonSouth + /// + public new ButtonControl bButton => buttonSouth; + + /// + /// Y Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to a Switch Pro Controller class, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonNorth, whereas this class returns the state of buttonWest + /// + public new ButtonControl yButton => buttonWest; + + /// + /// X Button for a Nintendo Switch Pro Controller. + /// If querying via script, ensure you cast the device to a Switch Pro Controller class, rather than using the Gamepad class. + /// The gamepad class will return the state of buttonWest, whereas this class returns the state of buttonNorth + /// + public new ButtonControl xButton => buttonNorth; + } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs index 8a0bf09c80..ec43b1195c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/Switch/SwitchProControllerHID.cs @@ -147,7 +147,7 @@ namespace UnityEngine.InputSystem.Switch /// A Nintendo Switch Pro controller connected to a desktop mac/windows PC using the HID interface. /// [InputControlLayout(stateType = typeof(SwitchProControllerHIDInputState), displayName = "Switch Pro Controller")] - public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEventPreProcessor + public class SwitchProControllerHID : SwitchProController, IInputStateCallbackReceiver, IEventPreProcessor { [InputControl(name = "capture", displayName = "Capture")] public ButtonControl captureButton { get; protected set; } @@ -155,34 +155,6 @@ public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEve [InputControl(name = "home", displayName = "Home")] public ButtonControl homeButton { get; protected set; } - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonSouth, whereas this class returns the state of buttonEast - /// - public new ButtonControl aButton => buttonEast; - - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonEast, whereas this class returns the state of buttonSouth - /// - public new ButtonControl bButton => buttonSouth; - - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonNorth, whereas this class returns the state of buttonWest - /// - public new ButtonControl yButton => buttonWest; - - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControllerHID, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonWest, whereas this class returns the state of buttonNorth - /// - public new ButtonControl xButton => buttonNorth; - protected override void OnAdded() { base.OnAdded(); diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs index 98062d04e5..4d34274386 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs @@ -198,38 +198,11 @@ public class DualSenseGampadiOS : DualShockGamepad /// /// A Switch Pro Controller connected to an iOS device. - /// If you use InputSystem.GetDevice, you must query for this class rather than Gamepad in order for aButton, bButton, yButton and xButton to be correct + /// If you use InputSystem.GetDevice, you must query for SwitchProControlleriOS rather than Gamepad in order for aButton, bButton, yButton and xButton to be correct /// [InputControlLayout(stateType = typeof(iOSGameControllerStateSwappedFaceButtons), displayName = "iOS Switch Pro Controller Gamepad")] - public class SwitchProControlleriOS : Gamepad + public class SwitchProControlleriOS : SwitchProController { - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonSouth, whereas this class returns the state of buttonEast - /// - public new ButtonControl aButton => buttonEast; - - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonEast, whereas this class returns the state of buttonSouth - /// - public new ButtonControl bButton => buttonSouth; - - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonNorth, whereas this class returns the state of buttonWest - /// - public new ButtonControl yButton => buttonWest; - - /// - /// A Button for a Nintendo Switch Pro Controller. - /// If querying via script, ensure you cast the device to SwitchProControlleriOS, rather than using the Gamepad class. - /// The gamepad class will return the state of buttonWest, whereas this class returns the state of buttonNorth - /// - public new ButtonControl xButton => buttonNorth; } } #endif // UNITY_EDITOR || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS From c5d6f72bf4567ba7355141c4efe3eef1df951dcf Mon Sep 17 00:00:00 2001 From: Val Knight Date: Mon, 4 Aug 2025 12:10:35 +0100 Subject: [PATCH 09/11] [Rebase on develop] Updated changelog to address reviewer feedback --- Packages/com.unity.inputsystem/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index aadcbca1f5..a33561c194 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -14,7 +14,8 @@ however, it has to be formatted properly to pass verification tests. - Exposed MediaPlayPause, MediaRewind, MediaForward keys on Keyboard. ### Fixed -- Fixed `buttonSouth` returning the state of the east button (and so on for all the buttons) when using a Nintendo Switch Pro Controller on iOS [ISXB-1632](issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1632) +- Fixed `buttonSouth` returning the state of the east button (and so on for all the compass named buttons) when using a Nintendo Switch Pro Controller on iOS [ISXB-1632](issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1632) +- Fixed `aButton` returning the state of the east button (and so on for all the letter named buttons) when using a Nintendo Switch Pro Controller on Standalone & iOS [ISXB-1632](issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1632) ## [1.14.2] - 2025-08-05 From ef8941053c2654beb6fb5e1d54c95a50ce47e927 Mon Sep 17 00:00:00 2001 From: Val Knight Date: Mon, 27 Oct 2025 15:46:41 +0000 Subject: [PATCH 10/11] correct display names & usages for iOS controller --- Assets/Tests/InputSystem/Plugins/iOSTests.cs | 8 ++++++++ Assets/Tests/InputSystem/SwitchTests.cs | 9 +++++++++ .../InputSystem/Plugins/iOS/IOSGameController.cs | 8 ++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/iOSTests.cs b/Assets/Tests/InputSystem/Plugins/iOSTests.cs index eef11eb36a..9fad4c5201 100644 --- a/Assets/Tests/InputSystem/Plugins/iOSTests.cs +++ b/Assets/Tests/InputSystem/Plugins/iOSTests.cs @@ -113,6 +113,14 @@ public void Devices_SupportsSwitchProControlleriOS() AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.B), gamepad.bButton); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.LeftShoulder), gamepad.leftShoulder); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.RightShoulder), gamepad.rightShoulder); + Assert.Contains("Submit", gamepad.buttonEast.usages.m_Array); + Assert.Contains("Submit", gamepad.aButton.usages.m_Array); + Assert.Contains("PrimaryAction", gamepad.aButton.usages.m_Array); + Assert.Contains("PrimaryAction", gamepad.aButton.usages.m_Array); + Assert.Contains("Back", gamepad.bButton.usages.m_Array); + Assert.Contains("Back", gamepad.bButton.usages.m_Array); + Assert.Contains("Cancel", gamepad.bButton.usages.m_Array); + Assert.Contains("Cancel", gamepad.bButton.usages.m_Array); } [Test] diff --git a/Assets/Tests/InputSystem/SwitchTests.cs b/Assets/Tests/InputSystem/SwitchTests.cs index 4d122458ec..a7f323c35c 100644 --- a/Assets/Tests/InputSystem/SwitchTests.cs +++ b/Assets/Tests/InputSystem/SwitchTests.cs @@ -71,6 +71,15 @@ public void Devices_SupportsHIDNpad() AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.ZR), controller.rightTrigger); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.Plus), controller.startButton); AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.Minus), controller.selectButton); + + Assert.Contains("Submit", controller.buttonEast.usages.m_Array); + Assert.Contains("Submit", controller.aButton.usages.m_Array); + Assert.Contains("PrimaryAction", controller.aButton.usages.m_Array); + Assert.Contains("PrimaryAction", controller.aButton.usages.m_Array); + Assert.Contains("Back", controller.bButton.usages.m_Array); + Assert.Contains("Back", controller.bButton.usages.m_Array); + Assert.Contains("Cancel", controller.bButton.usages.m_Array); + Assert.Contains("Cancel", controller.bButton.usages.m_Array); } private static SwitchProControllerHIDInputState StateWithButton(SwitchProControllerHIDInputState.Button button) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs index 4d34274386..1b0c7a1575 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/IOSGameController.cs @@ -113,10 +113,10 @@ internal unsafe struct iOSGameControllerStateSwappedFaceButtons : IInputStateTyp [InputControl(name = "dpad/right", bit = (uint)iOSButton.DpadRight)] [InputControl(name = "dpad/down", bit = (uint)iOSButton.DpadDown)] [InputControl(name = "dpad/left", bit = (uint)iOSButton.DpadLeft)] - [InputControl(name = "buttonSouth", bit = (uint)iOSButton.B)] - [InputControl(name = "buttonWest", bit = (uint)iOSButton.Y)] - [InputControl(name = "buttonNorth", bit = (uint)iOSButton.X)] - [InputControl(name = "buttonEast", bit = (uint)iOSButton.A)] + [InputControl(name = "buttonSouth", bit = (uint)iOSButton.B, displayName = "B", shortDisplayName = "B", usages = new[] { "Back", "Cancel" })] + [InputControl(name = "buttonWest", bit = (uint)iOSButton.Y, displayName = "Y", shortDisplayName = "Y", usage = "SecondaryAction")] + [InputControl(name = "buttonNorth", bit = (uint)iOSButton.X, displayName = "X", shortDisplayName = "X")] + [InputControl(name = "buttonEast", bit = (uint)iOSButton.A, displayName = "A", shortDisplayName = "A", usages = new[] { "PrimaryAction", "Submit" })] [InputControl(name = "leftStickPress", bit = (uint)iOSButton.LeftStick)] [InputControl(name = "rightStickPress", bit = (uint)iOSButton.RightStick)] [InputControl(name = "leftShoulder", bit = (uint)iOSButton.LeftShoulder)] From a281958642f325eddcf12426669ba94b3137bc3d Mon Sep 17 00:00:00 2001 From: Val Knight Date: Mon, 27 Oct 2025 15:57:29 +0000 Subject: [PATCH 11/11] Fix issues flagged up by PR bot --- Assets/Tests/InputSystem/Plugins/iOSTests.cs | 5 +---- Assets/Tests/InputSystem/SwitchTests.cs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/iOSTests.cs b/Assets/Tests/InputSystem/Plugins/iOSTests.cs index 9fad4c5201..1aa833849d 100644 --- a/Assets/Tests/InputSystem/Plugins/iOSTests.cs +++ b/Assets/Tests/InputSystem/Plugins/iOSTests.cs @@ -114,13 +114,10 @@ public void Devices_SupportsSwitchProControlleriOS() AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.LeftShoulder), gamepad.leftShoulder); AssertButtonPress(gamepad, new iOSGameControllerStateSwappedFaceButtons().WithButton(iOSButton.RightShoulder), gamepad.rightShoulder); Assert.Contains("Submit", gamepad.buttonEast.usages.m_Array); - Assert.Contains("Submit", gamepad.aButton.usages.m_Array); Assert.Contains("PrimaryAction", gamepad.aButton.usages.m_Array); - Assert.Contains("PrimaryAction", gamepad.aButton.usages.m_Array); - Assert.Contains("Back", gamepad.bButton.usages.m_Array); Assert.Contains("Back", gamepad.bButton.usages.m_Array); Assert.Contains("Cancel", gamepad.bButton.usages.m_Array); - Assert.Contains("Cancel", gamepad.bButton.usages.m_Array); + Assert.Contains("SecondaryAction", gamepad.yButton.usages.m_Array); } [Test] diff --git a/Assets/Tests/InputSystem/SwitchTests.cs b/Assets/Tests/InputSystem/SwitchTests.cs index a7f323c35c..9e68c92c93 100644 --- a/Assets/Tests/InputSystem/SwitchTests.cs +++ b/Assets/Tests/InputSystem/SwitchTests.cs @@ -73,13 +73,10 @@ public void Devices_SupportsHIDNpad() AssertButtonPress(controller, StateWithButton(SwitchProControllerHIDInputState.Button.Minus), controller.selectButton); Assert.Contains("Submit", controller.buttonEast.usages.m_Array); - Assert.Contains("Submit", controller.aButton.usages.m_Array); Assert.Contains("PrimaryAction", controller.aButton.usages.m_Array); - Assert.Contains("PrimaryAction", controller.aButton.usages.m_Array); - Assert.Contains("Back", controller.bButton.usages.m_Array); Assert.Contains("Back", controller.bButton.usages.m_Array); Assert.Contains("Cancel", controller.bButton.usages.m_Array); - Assert.Contains("Cancel", controller.bButton.usages.m_Array); + Assert.Contains("SecondaryAction", controller.yButton.usages.m_Array); } private static SwitchProControllerHIDInputState StateWithButton(SwitchProControllerHIDInputState.Button button)