InputSystemで誤認されているHIDデバイスの日本語での修正マニュアル．

#目的
UnityのInputSystemで誤認されているHIDデバイスを取得したいカテゴリに修正する．

#最初に
ソースだけクレって方は[GitHub](https://github.com/marbledNEET/HoriPad/blob/master/HORIPadSController.cs)でお取りください．
きっかけは，InputSystemで現在接続されているゲームパッドの情報を受け取ろうと

```
var pad = Gamepad.current;
Debug.Log("Gamepad ="+ pad.current);
```
と入力したところnullが出て名前が表示されなかったことです．
PCにはホリ製のホリパッド for Nintendo Switch[以下：HORIPAD_S]が刺さっていたので
名前が表示されるはずだと考えWindow>Analysis>Input Debuggerを起動
![joy0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/a56730e4-4366-119e-3e2e-b867504e0d03.png)
![joy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/e6db4318-fc51-c813-dafe-b26a685d2aa0.png)
どうも，Joystickとして認識されているようです．
これを解決するために頑張りました．

#マニュアルを読む
Inputsystemにはとても英語のマニュアルがこちらに用意されています．
[HID Support | Input System | 1.0.0](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/HID.html)
マニュアルを読んでいくとJoyStickとなぜ認識されてしまうのか？
認識がずれている物をどうすればいいのか？が解説されています．

これを元に手順を洗い出すと
1．コントローラーのボタンを押したときにBITがどのように反応するか調べる

2．1を元にC#でプログラムを記述

3． InputSystem.RegisterLayoutで機器を検出できるようにする

以上の手順で進めてみます．

#ボタンの割り当てBITの調査
今回は誤認はしていますがInput Debuggerで表示されているのでこの情報画面から調査をします．
Unityで認識もされないよ！という場合は[Gamepad Tester](https://gamepad-tester.com/)で調査してください．

UnityのWindow>Analysis>Input Debuggerを開きます．
![info0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/9fc6f9ef-60dc-3c55-f614-204b00154cb7.png)

ここでどのボタンがどれに対応しているのかを，
ボタンを押したときBITと書かれた部分の数値が動いたをボタンをメモしておきます．
すべてのボタンのメモが取り終わったら，上記画像のオレンジ色の矢印の部分をクリックしてください．
![info1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/ca19bf9d-3e51-6268-e4a3-f1f55b660fa4.png)
すると上記画像のようなウィンドウが開きます．
この画像のOffsetの隣のBITの部分がボタンに割り当てられているBITになります．
先ほどとったメモを元にボタンとBITの対応表を作ります
![info2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/b6952601-b83c-d43f-86c9-653e6eb11b32.png)
今回のHORIPAD_Sの場合はこのようになりました．

#C#にBITとボタンの対応を落とし込む．
調査が終わったのでマニュアルを読みながらC#に落とし込んでみます．

```C#:HoriPadSController.cs
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine;
using UnityEditor;


[StructLayout(LayoutKind.Explicit, Size = 20)]
public struct HoriPadSState : IInputStateTypeInfo
{
   
    public FourCC format => new FourCC('H','I','D');

    //LeftStick
    [InputControl(name = "leftStick", format = "VC2S", layout = "Stick")]
    [InputControl(name = "leftStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    [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")]
    [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")]
    [InputControl(name = "leftStick/y", offset = 1, format = "USHT", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    [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")]
    [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")]
    [FieldOffset(3)] public ushort leftStickX;
    [FieldOffset(4)] public ushort leftStickY;

    //RightStick
    [InputControl(name = "rightStick", format = "VC2S", layout = "Stick")]
    [InputControl(name = "rightStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    [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")]
    [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")]
    [InputControl(name = "rightStick/y", offset = 1, format = "USHT", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
    [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")]
    [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")]
    [FieldOffset(5)] public ushort rightStickX;
    [FieldOffset(6)] public ushort rightStickY;

    //Dpad
    [InputControl(name ="dpad", format ="BIT", layout = "Dpad", sizeInBits = 4,defaultState=8)]
    [InputControl(name ="dpad/up", format ="BIT", layout = "DiscreteButton", parameters = "minValue=7,maxValue=1,nullValue=8,wrapAtValue=7", bit = 0, sizeInBits = 4)]
    [InputControl(name ="dpad/right", format ="BIT", layout = "DiscreteButton", parameters = "minValue=1,maxValue=3", bit = 0, sizeInBits = 4)]
    [InputControl(name ="dpad/down", format ="BIT", layout = "DiscreteButton", parameters = "minValue=3,maxValue=5", bit = 0, sizeInBits = 4)]
    [InputControl(name ="dpad/left", format ="BIT", layout = "DiscreteButton", parameters = "minValue=5,maxValue=7", bit = 0, sizeInBits = 4)]
    [FieldOffset(3)] public byte dpadButton;

    //buttons1
    [InputControl(name = "buttonWest", displayName = "Y",bit = 0,usage = "SecondaryAction")]
    [InputControl(name = "buttonSouth", displayName = "B",bit = 1, usage = "Back")]
    [InputControl(name = "buttonEast", displayName = "A",bit = 2, usage = "PrimaryAction")]
    [InputControl(name = "buttonNorth", displayName = "X",bit = 3)]
    [InputControl(name = "leftShoulder", displayName = "L",bit = 4)]
    [InputControl(name = "rightShoulder", displayName = "R",bit = 5)]
    [InputControl(name = "leftTrigger", offset= 1,displayName = "ZL", format ="BIT", bit = 6)]
    [InputControl(name = "rightTrigger",offset= 1, displayName = "ZR", format ="BIT", bit = 7)]
    [FieldOffset(1)] public uint button1;

    //buttons2
    [InputControl(name = "select", displayName = "Minus",bit = 0)]
    [InputControl(name = "start", displayName = "Plus",bit = 1)]
    [InputControl(name = "leftStickPress", displayName = "Left Stick",bit = 2)]
    [InputControl(name = "rightStickPress", displayName = "Right Stick",bit = 3)]
    [FieldOffset(2)] public uint button2;
```
このようになりました．
offsetの設定はとりあえずInput Debuggerの手順１で表示されていたものを設定しました．
paramatersの設定やStickの設定は
Packages>Input System>Plugins>Switch>SwitchProControllerHID.csの設定を参考に設定しています．
他の方法として，既定のデバイスからJsonで設定を取る方法もあります．
![info3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/4db345e1-a8b8-f4c9-200a-47fe0accb1ec.png)
[Copy Layout as JSON]をクリックすると情報がコピーされます．
新しくJSONファイルを作成しペーストすると，今のボタンに設定されているパラメーターなどが見ることができます．


```json:HORIPAD_S.json
        {
            "name": "stick/x",
            "layout": "",
            "variants": "",
            "usage": "",
            "alias": "",
            "useStateFrom": "",
            "offset": 0,
            "bit": 0,
            "sizeInBits": 8,
            "format": "BIT ",
            "arraySize": 0,
            "usages": [],
            "aliases": [],
            "parameters": "normalize=true,normalizeMin=0,normalizeMax=1,normalizeZero=0.5",
            "processors": "axisDeadzone",
            "displayName": "",
            "shortDisplayName": "",
            "noisy": false,
            "synthetic": false,
            "defaultState": "127",
            "minValue": "",
            "maxValue": ""
        }

```

上記のようなパラメーターの設定や判定の基準になるButtonやDpadの設定が書かれたJSONを作成できます．
ただ，ここで設定されているParameterはかなり極端になっていますので，今回のようにサードパーティ製のコントローラーの場合は本家のコントローラーの設定をとりあえず拝借してくるのがよいと思います．

# InputSystem.RegisterLayoutで機器を検出できるようにする．
ここまでで，HORIPAD_SのLayoutを記述できました．
次はInputSystemに該当のLayoutを持つ機器を検知してもらえるようにInputSystem.RegisterLayouで登録します．

事前にデバイスのVenderIDとProductIDを調べておきましょう
![info4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/2922d5d2-99dd-2b6f-8214-da102415ef42.png)

上記画像の[HID Descriptor]をクリックすると

![info5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/2caacd0f-ed99-5fe2-ccfa-db291de94615.png)
このような画面が出てきますのでVenderIDとProductIDをメモしておきます．
Windowsのデバイスマネージャーからでも調べることができますのでUnity側で機器が認識されない場合は
デバイスマネージャー>調べたい機器のプロパティ>詳細タブ>ハードウェアID>
「～VID_XXXX&PID_XXXX～」部分がVenderIDとProductID
![Vendor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/40ac6733-b169-08a3-5495-430ff2a016c2.png)


メモし終わったら以下のようにプログラムします

```C#:HoriPadS.cs
using System.Runtime.InteropServices;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine;
using UnityEditor;

[InputControlLayout(stateType = typeof(HoriPadSState))]
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
public class HoriPadS : Gamepad
{
    static HoriPadS()
    {
        //Gamepadとして登録
        InputSystem.RegisterLayout<HoriPadS>(
            matches: new InputDeviceMatcher()
                .WithInterface("HID")
                .WithDeviceClass("Gamepad")
                 //企業名と製品名でも判定できます．
                 //名前は3個上の矢印が書いてある画像のProductとManufactuerです．
                .WithManufacturer("HORI CO.,LTD.")
                .WithProduct("HORIPAD S")
                 //こちらが先ほどメモしたvendorIdとproductIdで登録する方法
                .WithCapability("vendorId", 0xF0D)
                .WithCapability("productId", 0xC1) 
        );
    }

    [RuntimeInitializeOnLoadMethod]
    static void Init() { 
         UnityEngine.Debug.Log("initController");
    }
}
```
このように記述することで，Input Debuggerの下の方に
![Layout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/fd416de1-674c-bce5-817f-da1b41f11ac2.png)
このような感じにclass名で命名したGamePadが登録されているはずです．
Unity側で認識されていない機器であればこの時点で接続してやれば，おそらく動くはずです．

しかし，HORIPAD_Sのように既に別の機器として認識されている物はこのままでは動きません．

```C#:HoriPadS.cs
using System.Runtime.InteropServices;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine;
using UnityEditor;

[InputControlLayout(stateType = typeof(HoriPadSState))]
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
public class HoriPadS : Gamepad
{
    static HoriPadS()
    {
            //こちらに変更
            InputSystem.RegisterLayoutMatcher<HoriPadS>(
            new InputDeviceMatcher()
                .WithInterface("HID")
                .WithCapability("vendorId", 0xF0D)
                .WithCapability("productId", 0xC1) 
        );
    }

    [RuntimeInitializeOnLoadMethod]
    static void Init() { 
         UnityEngine.Debug.Log("initController");
    }
}
```

上記のプログラムのようにInputSystem.RegisterLayoutMatcher<>で既に登録されている構成に上書きしないと動かないです．
英語のマニュアルでは新しく構成を登録して終わりとなっているので，注意してください．

上手くいくと
![tori00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369570/58f059da-6641-b301-b850-a7767468b07c.png)
上記のDevices直下のアイコンが目的のものに変化しているはずです．
この後は正常に動作しているかの確認作業になりますので，InputSystemをDLしたPackageManagerから
Visualizersなどを追加DL・使用して入力が想定通りに動いているかなどをチェックしてください．
スティック部分のデータ構造は変更前のものはoffset以外あまり当てにならないので既定のSwitchのProコンなどの設定を参考に
データ形式を変えたりoffsetを変更して調整してください．


#最後に
今回は新しいInputSystemで誤認されているHIDデバイスの修正方法を，英語のマニュアルを元に実践しました．
実際に私が行ってみてわかりづらかった部分などは補足したつもりですが，InputSystemの理解の一助になれば幸いです．

※修正
HoriPadSController.csのbit表記がenumでグループ分けしたものだったのを
通常の数字に修正．以前の表記方式はGitHubに上がってます……2020/11/19