Search Unity

Question Custom Gamepad for Unofficial Nintendo Switch Pro Controller (PDP)

Discussion in 'Input System' started by mariepierkame, Aug 19, 2021.

  1. mariepierkame

    mariepierkame

    Joined:
    Aug 1, 2021
    Posts:
    4
    Hi everyone,

    I have a "Performance Designed Products" (PDP) nintendo switch pro controller that I use without any issue with steam and other software. It also worked normally with the old input system.

    On the new input system, however, it is recognized as a joystick (as I've understood, as are all non-standard controllers). So I tried to create my own PDPSwitchProControllerHID Gamepad class, with its associated struct for the InputReport. Once I associate the class with the vendor and product id, unity recognizes it as a gamepad and gives the following debug information:

    upload_2021-8-19_8-55-32.png

    However, I'm not even sure if that's actually displaying the HID information of my controller, or Unity's default gamepad HID info... Once I add my input report to the controller, something seems to be working as the north/east/west/south buttons get their display name changed to ABXY:

    upload_2021-8-19_9-0-19.png

    Here's the code I have so far, I don't really understand what's not working as this all seems a little black-box-y. (For example, where should I find information about what parameters to pass to each direction of a stick?)

    Code (CSharp):
    1. [InputControlLayout(stateType = typeof(PDPSwitchProControllerInputReport))]
    2. #if UNITY_EDITOR
    3. [InitializeOnLoad]
    4. #endif
    5. public class PDPSwitchProControllerHID : Gamepad
    6. {
    7.     static PDPSwitchProControllerHID() { // this part seems to be working correctly
    8.         InputSystem.RegisterLayout<PDPSwitchProControllerHID>(
    9.             "Nintendo Switch Pro Controller (PDP)",
    10.             new InputDeviceMatcher()
    11.                 .WithInterface("HID")
    12.                 .WithCapability("vendorId", 0xE6F)
    13.                 .WithCapability("productId", 0x185));
    14.     }
    15.  
    16.     [RuntimeInitializeOnLoadMethod]
    17.     static void Init() { }
    18. }
    19.  
    20. //I'm guessing the issue is with my InputReport, but I don't know how...
    21. [StructLayout(LayoutKind.Explicit, Size = 224)]
    22. struct PDPSwitchProControllerInputReport : IInputStateTypeInfo {
    23.  
    24.     public FourCC format => new FourCC('H', 'I', 'D');
    25.  
    26.     [InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4)]
    27.     [InputControl(name = "dpad/down", format = "BIT", bit = 1, sizeInBits = 1)]
    28.     [InputControl(name = "dpad/left", format = "BIT", bit = 2, sizeInBits = 1)]
    29.     [InputControl(name = "dpad/right", format = "BIT", bit = 3, sizeInBits = 1)]
    30.     [InputControl(name = "dpad/up", format = "BIT", bit = 4, sizeInBits = 1)]
    31.     [InputControl(name = "dpad/x", format = "BIT", layout = "DpadAxis", bit = 0, sizeInBits = 4)]
    32.     [InputControl(name = "dpad/y", format = "BIT", layout = "DpadAxis", bit = 0, sizeInBits = 4)]
    33.     [InputControl(name = "buttonNorth", layout = "Button", displayName = "Y", bit = 4)]
    34.     [InputControl(name = "buttonEast", layout = "Button", displayName = "X", bit = 5)]
    35.     [InputControl(name = "buttonSouth", layout = "Button", displayName = "A", bit = 6)]
    36.     [InputControl(name = "buttonWest", layout = "Button", displayName = "B", bit = 7)]
    37.     [InputControl(name = "leftStickPress", layout = "Button", bit = 8)]
    38.     [InputControl(name = "rightStickPress", layout = "Button", bit = 9)]
    39.     [InputControl(name = "leftShoulder", layout = "Button", bit = 10)]
    40.     [InputControl(name = "rightShoulder", layout = "Button", bit = 11)]
    41.     [InputControl(name = "start", layout = "Button", bit = 12)]
    42.     [InputControl(name = "select", layout = "Button", bit = 13)]
    43.     [FieldOffset(0)] public ushort buttons;
    44.  
    45.     // tried one stick with all directions explicitely defined
    46.  
    47.     [InputControl(name = "leftStick", layout = "Stick")]
    48.     [InputControl(name = "leftStick/x", format = "FLT", offset = 0, parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    49.     [InputControl(name = "leftStick/y", format = "FLT", offset = 4, parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    50.     [InputControl(name = "leftStick/up", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=0,clampMax=1")]
    51.     [InputControl(name = "leftStick/down", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=-1,clampMax=0,invert")]
    52.     [InputControl(name = "leftStick/left", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=-1,clampMax=0,invert")]
    53.     [InputControl(name = "leftStick/right", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=0,clampMax=1")]
    54.     [FieldOffset(4)] public Vector2 leftStick;
    55.  
    56.     // and the other one just like that
    57.  
    58.     [InputControl(name = "rightStick", layout = "Stick")]
    59.     [FieldOffset(12)] public Vector2 rightStick;
    60.  
    61.     [InputControl(name = "leftTrigger", layout = "Button")]
    62.     [FieldOffset(20)] public float leftTrigger;
    63.  
    64.     [InputControl(name = "rightTrigger", layout = "Button")]
    65.     [FieldOffset(24)] public float rightTrigger;
    66. }
    Let me know if you have an idea what I did wrong, or where I can find more information about how to setup the InputReport properly / find the actual data representing my controller.

    Thanks,

    Marie
     

    Attached Files:

  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    If that controller has the same report format as the standard Switch Pro Controller, you should be able to simply do

    Code (CSharp):
    1.  
    2.             InputSystem.RegisterLayoutMatcher<SwitchProControllerHID>(
    3.                 new InputDeviceMatcher()
    4.                     .WithInterface("HID")
    5.                     .WithCapability("vendorId", 0xE6F)
    6.                     .WithCapability("productId", 0x185));
    7.  
     
  3. mariepierkame

    mariepierkame

    Joined:
    Aug 1, 2021
    Posts:
    4
    Just tried this, and the input works as good as it's been, but the buttons are not mapped correctly (X is A, etc.) and the sticks X/Y are messed up too. To move the left stick left and right, I need to move it up and down, and to move it up and down, it's actually the right stick I need to map.

    I'm guessing if I had access to the SwitchProControllerHIDInputState struct, I could copy it and change the settings to fit my controller. Probably?
     
  4. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    The code for the struct is public. Copying the existing SwitchProControlllerHID along with the state struct and then modifying them and registering them as a layout is certainly one way to go. Might be the easiest one, too.

    An alternative is to take the layout as is and derive a new one from it and fix specific setup problems in there.

    Code (CSharp):
    1.  
    2. InputSystem.RegisterLayout(@"
    3.    {
    4.        ""name"" : ""PDPSwitchPro"",
    5.        ""extend"" : ""SwitchProControllerHID"",
    6.        ""controls"" : {
    7.            { ""name"" : ""leftStick/x"", ... }
    8.    }
    9. ",
    10. matches: new InputDeviceMatcher()
    11.     .WithInterface("HID")
    12.     .WithCapability("vendorId", 0xE6F)
    13.     .WithCapability("productId", 0x185));
    14.  
     
  5. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    For HIDs, another way is to just hack up the auto-generated format. With the device connected (and no custom layout registered), open the input debugger and browse to the auto-generated layout (should show up somewhere under Layouts >> Specific Devices). Right-click and use "Copy Layout as JSON".

    The JSON string can be edited to fix any problem in the auto-generated layout and the resulting string can be registered as a layout.

    Anyway, just to mention that. Copypasting SwitchProControllerHID and adapting it is probably going to be a quicker way to get a working setup.
     
  6. mariepierkame

    mariepierkame

    Joined:
    Aug 1, 2021
    Posts:
    4
    Thank you for your help! I will try modifying the InputState and will update this thread with my result; hopefully now I should be able to make this work!
     
  7. mariepierkame

    mariepierkame

    Joined:
    Aug 1, 2021
    Posts:
    4
    EDIT: This leftStick y value starting at -1 is bugging me more than I thought, and I can't find a way to fix it... If anybody has any idea on why that would happen, I'd be delighted to know!

    I got it all working now!

    The only weird thing it that, on startup, the leftStick starts with -1 on the y axis, and resets back to 0 as soon as I press on any button. If someone can figure that one out! But for now, I'll do with it; it's not such a big deal for my testing purposes.

    Here's the code, if anyone else needs to use a PDP Switch Pro Controller on a project:

    Code (CSharp):
    1. [InputControlLayout(stateType = typeof(PDPSwitchProControllerInputState))]
    2. #if UNITY_EDITOR
    3. [InitializeOnLoad]
    4. #endif
    5. public class PDPSwitchProControllerHID : Gamepad
    6. {
    7.     static PDPSwitchProControllerHID() {
    8.         InputSystem.RegisterLayout<PDPSwitchProControllerHID>(
    9.             "Nintendo Switch Pro Controller (PDP)",
    10.             new InputDeviceMatcher()
    11.                 .WithInterface("HID")
    12.                 .WithCapability("vendorId", 0xE6F)
    13.                 .WithCapability("productId", 0x185));
    14.     }
    15.  
    16.     [RuntimeInitializeOnLoadMethod]
    17.     static void Init() { }
    18. }
    19.  
    20.  
    21. [StructLayout(LayoutKind.Explicit, Size = 20)]
    22. struct PDPSwitchProControllerInputState : IInputStateTypeInfo {
    23.  
    24.     public FourCC format => new FourCC('H', 'I', 'D');
    25.  
    26.     [InputControl(name = "dpad", format = "BIT", layout = "Dpad", bit = 24, sizeInBits = 4, defaultState = 8)]
    27.     [InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=7,maxValue=1,nullValue=8,wrapAtValue=7", bit = 24, sizeInBits = 4)]
    28.     [InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=1,maxValue=3", bit = 24, sizeInBits = 4)]
    29.     [InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=3,maxValue=5", bit = 24, sizeInBits = 4)]
    30.     [InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=5, maxValue=7", bit = 24, sizeInBits = 4)]
    31.     [InputControl(name = "buttonEast", displayName = "X", shortDisplayName = "X", bit = (uint)PDPSwitchProControllerButton.East)]
    32.     [InputControl(name = "buttonNorth", displayName = "Y", shortDisplayName = "Y", bit = (uint)PDPSwitchProControllerButton.North, usage = "SecondaryAction")]
    33.     [InputControl(name = "buttonSouth", displayName = "A", shortDisplayName = "A", bit = (uint)PDPSwitchProControllerButton.South, usage = "PrimaryAction")]
    34.     [InputControl(name = "buttonWest", displayName = "B", shortDisplayName = "B", bit = (uint)PDPSwitchProControllerButton.West, usage = "Back")]
    35.     [InputControl(name = "leftStickPress", displayName = "Left Stick", bit = (uint)PDPSwitchProControllerButton.StickL)]
    36.     [InputControl(name = "rightStickPress", displayName = "Right Stick", bit = (uint)PDPSwitchProControllerButton.StickR)]
    37.     [InputControl(name = "leftShoulder", displayName = "L", shortDisplayName = "L", bit = (uint)PDPSwitchProControllerButton.L)]
    38.     [InputControl(name = "rightShoulder", displayName = "R", shortDisplayName = "R", bit = (uint)PDPSwitchProControllerButton.R)]
    39.     [InputControl(name = "leftTrigger", displayName = "ZL", shortDisplayName = "ZL", format = "BIT", bit = (uint)PDPSwitchProControllerButton.ZL)]
    40.     [InputControl(name = "rightTrigger", displayName = "ZR", shortDisplayName = "ZR", format = "BIT", bit = (uint)PDPSwitchProControllerButton.ZR)]
    41.     [InputControl(name = "start", displayName = "Plus", bit = (uint)PDPSwitchProControllerButton.Plus, usage = "Menu")]
    42.     [InputControl(name = "select", displayName = "Minus", bit = (uint)PDPSwitchProControllerButton.Minus)]
    43.     [FieldOffset(0)]
    44.     public uint buttons;
    45.  
    46.     [InputControl(name = "leftStick", format = "VC2S", layout = "Stick")]
    47.     [InputControl(name = "leftStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    48.     [InputControl(name = "leftStick/left", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")]
    49.     [InputControl(name = "leftStick/right", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85")]
    50.     [InputControl(name = "leftStick/y", offset = 1, format = "USHT", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    51.     [InputControl(name = "leftStick/up", offset = 1, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")]
    52.     [InputControl(name = "leftStick/down", offset = 1, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85,invert=false")]
    53.     [FieldOffset(3)] public ushort leftStickX;
    54.     [FieldOffset(4)] public ushort leftStickY;
    55.  
    56.     [InputControl(name = "rightStick", format = "VC2S", layout = "Stick")]
    57.     [InputControl(name = "rightStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    58.     [InputControl(name = "rightStick/left", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")]
    59.     [InputControl(name = "rightStick/right", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")]
    60.     [InputControl(name = "rightStick/y", offset = 1, format = "USHT", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    61.     [InputControl(name = "rightStick/up", offset = 1, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")]
    62.     [InputControl(name = "rightStick/down", offset = 1, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85,invert=false")]
    63.     [FieldOffset(5)] public ushort rightStickX;
    64.     [FieldOffset(6)] public ushort rightStickY;
    65.  
    66.     public float leftTrigger => ((buttons & (1U << (int)PDPSwitchProControllerButton.ZL)) != 0) ? 1f : 0f;
    67.  
    68.     public float rightTrigger => ((buttons & (1U << (int)PDPSwitchProControllerButton.ZR)) != 0) ? 1f : 0f;
    69.  
    70.     public enum PDPSwitchProControllerButton {
    71.         North = 8,
    72.         South = 10,
    73.         West = 11,
    74.         East = 9,
    75.  
    76.         StickL = 18,
    77.         StickR = 19,
    78.         L = 12,
    79.         R = 13,
    80.  
    81.         ZL = 14,
    82.         ZR = 15,
    83.         Plus = 17,
    84.         Minus = 16,
    85.  
    86.         X = North,
    87.         B = East,
    88.         Y = South,
    89.         A = West,
    90.     }
    91. }
     
    Last edited: Aug 19, 2021
  8. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Means the default state is off. Default state for any control is 0. For [-1..1] controls that are stored in signed integer form, that will result in a -1 default value. There's a
    defaultState
    property on
    InputControlAttribute
    to initialize that to the right value.
     
  9. xxpackmaniasxx

    xxpackmaniasxx

    Joined:
    Apr 5, 2023
    Posts:
    2
    Hello, I'm experiencing the same issue of switch pro controller not working properly

    How can I add this patched file to the control layout ?
     
  10. xxpackmaniasxx

    xxpackmaniasxx

    Joined:
    Apr 5, 2023
    Posts:
    2
    Something that make me wonder is that the input debugger register properly all the input of the controller. Am I missing something ?
     

    Attached Files: