趣味プロダクトのDebug用途でUnity Input Systemを使ってみた（GamePad入力）

# Unity Input Systemとは
>ゲームパッド・キーボード・マウス・センサー等の各種入力デバイスからの入力を汎用的・統合的に取り扱うために導入されたUnityの新しい入力システムです

[公式の引用](https://forpro.unity3d.jp/unity_pro_tips/2021/05/20/1957/#Input_System:~:text=%E3%82%B2%E3%83%BC%E3%83%A0%E3%83%91%E3%83%83%E3%83%89%E3%83%BB%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E3%83%BB%E3%83%9E%E3%82%A6%E3%82%B9%E3%83%BB%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E7%AD%89%E3%81%AE%E5%90%84%E7%A8%AE%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%8B%E3%82%89%E3%81%AE%E5%85%A5%E5%8A%9B%E3%82%92%E6%B1%8E%E7%94%A8%E7%9A%84%E3%83%BB%E7%B5%B1%E5%90%88%E7%9A%84%E3%81%AB%E5%8F%96%E3%82%8A%E6%89%B1%E3%81%86%E3%81%9F%E3%82%81%E3%81%AB%E5%B0%8E%E5%85%A5%E3%81%95%E3%82%8C%E3%81%9FUnity%E3%81%AE%E6%96%B0%E3%81%97%E3%81%84%E5%85%A5%E5%8A%9B%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%A7%E3%81%99)
以前までは、[Input Manager](https://docs.unity3d.com/ja/2021.3/Manual/class-InputManager.html)を利用して入力を受け取っていましたが、Input Systemの勉強も兼ねて試してみました。なぜInput Managerではなく、Input Systemを利用するのかについては[こちらのドキュメント](https://forpro.unity3d.jp/unity_pro_tips/2021/05/20/1957/#Input_System:~:text=%E3%82%88%E3%81%84%E3%81%A7%E3%81%97%E3%82%87%E3%81%86%E3%80%82-,%E3%81%93%E3%82%8C%E3%81%BE%E3%81%A7%E3%81%AE%E5%85%A5%E5%8A%9B%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%9C%E9%A7%84%E7%9B%AE%E3%81%AA%E3%81%AE%E3%81%8B,-Unity%E3%82%92%E4%BD%BF%E7%94%A8)がわかりやすいかと思います。

# 環境
Unity 2021.3.15f1

# 導入
UnityのプロジェクトにInput Systemを導入する方法はPackageManagerからインストールを行います。こちらの[Install guide](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.8/manual/Installation.html)がわかりやすいかと思います。


# 実際に行ったこと
趣味で作成しているVRコンテンツにおいてXR Interaction Toolkitを試している最中にDebug用途として、GamePadを用いても動かせるようにしたいと思いGamePadでの入力周りの実装を行いました。

## 実装
左スティックでの移動

```GamePadLeftController.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class GamePadLeftController : MonoBehaviour
{
    [SerializeField] private GameObject XROrigin;
    [SerializeField] private GameObject mainCamera;
    private float speed = 0.05f;

    private InputAction movement;
    private void OnEnable()
    {
        movement = new InputAction(binding: "<Gamepad>/leftStick");
        movement.Enable();
    }

    private void OnDisable()
    {
        movement.Disable();
    }

    // Update is called once per frame
    void Update()
    
        Vector2 movementInput = movement.ReadValue<Vector2>();
        
        switch (movementInput)
        {
            case Vector2 _ when movementInput.y > 0.3f:
                XROrigin.transform.position +=  XZPlaneMoveVector3(mainCamera.transform.forward) * speed;
                break;
            
            case Vector2 _ when movementInput.y < -0.3f:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.forward) * speed * -1;
                break;
            
            case Vector2 _ when movementInput.x > 0.3f:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.right) * speed;
                break;
            
            case Vector2 _ when movementInput.x < -0.3f:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.right) * speed * -1;
                break;
        }
    }

    //xz平面での移動をさせるためにz座標を0にする関数
    public Vector3 XZPlaneMoveVector3(Vector3 a)
    {
        return new Vector3(a.x * 1, a.y * 0, a.z * 1);
    }
    
    // 入力されている情報を確認するために表示
    private void OnGUI()
    {
        if (Gamepad.current == null) return;
        
        GUILayout.Label($"leftStick: {Gamepad.current.leftStick.ReadValue()}");
        GUILayout.Label($"buttonNorth: {Gamepad.current.buttonNorth.isPressed}");
        GUILayout.Label($"buttonSouth: {Gamepad.current.buttonSouth.isPressed}");
        GUILayout.Label($"buttonEast: {Gamepad.current.buttonEast.isPressed}");
        GUILayout.Label($"buttonWest: {Gamepad.current.buttonWest.isPressed}");
        GUILayout.Label($"leftShoulder: {Gamepad.current.leftShoulder.ReadValue()}");
        GUILayout.Label($"leftTrigger: {Gamepad.current.leftTrigger.ReadValue()}");
        GUILayout.Label($"rightShoulder: {Gamepad.current.rightShoulder.ReadValue()}");
        GUILayout.Label($"rightTrigger: {Gamepad.current.rightTrigger.ReadValue()}");
        
    }
}

```

ボタンの処理

``` GamePadButton.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class GamePadButton : MonoBehaviour
{
    private Dictionary<CustomButton, InputAction> buttonActions = new Dictionary<CustomButton, InputAction>();
    private float speed = 1;
    [SerializeField] private GameObject XROrigin;
    [SerializeField] private GameObject mainCamera;
    
    private void OnEnable()
    {
        foreach (CustomButton button in Enum.GetValues(typeof(CustomButton)))
        {
            var buttonAction = new InputAction(binding: $"<Gamepad>/button{GetButtonIndex(button)}");
            buttonAction.Enable();
            
            buttonActions.Add(button, buttonAction);

            buttonAction.started += ctx => HandleButtonPress(button);
        }
    }

    private void OnDisable()
    {
        foreach (var buttonAction in buttonActions.Values)
        {
            buttonAction.Disable();
        }
    }

    private void HandleButtonPress(CustomButton button)
    {
        switch (button)
        {
            case CustomButton.North:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.forward) * speed;
                break;
            case CustomButton.South:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.forward) * speed * -1;
                break;
            case CustomButton.East:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.right) * speed;
                break;
            case CustomButton.West:
                XROrigin.transform.position += XZPlaneMoveVector3(mainCamera.transform.right) * speed * -1;
                break;
            default:
                return ;
        }
    }

    private String GetButtonIndex(CustomButton button)
    {
        switch (button)
        {
            case CustomButton.North:
                return "North";
            case CustomButton.South:
                return "South";
            case CustomButton.East:
                return "East";
            case CustomButton.West:
                return "West";
            default:
                return "North";
        }
    }
    
    //xz平面での移動をさせるためにy座標を0にする関数
    public Vector3 XZPlaneMoveVector3(Vector3 a)
    {
        return new Vector3(a.x * 1, a.y * 0, a.z * 1);
    }
}

public enum CustomButton
{
    North,
    South,
    East,
    West
}




```
今後別のボタンを追加したい際に同じ実装をする必要がないように、enumを利用してButtonの種類を定義するようにしました。しかし、[ボタンの名称がbutton以外から始まる場合](https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Gamepad.html#:~:text=%E7%A4%BA%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82-,%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB,-%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%82%B2%E3%83%BC%E3%83%A0)もあるため、その部分は修正が必要になるかと思います。

```.cs
var buttonAction = new InputAction(binding: $"<Gamepad>/button{GetButtonIndex(button)}");
```
上記の実装の"button"部分含めて名称を取得する必要が出てくると思います。そして名称すべてを含めるようにして、enumで定義をしておく必要があります。それに伴ってGetButtonIndexとHandleButtonPressの中でCustomButtonを利用している箇所も修正が必要になります。

右スティックでの視点変更
```GamePadRotation.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class GamePadRotation : MonoBehaviour
{
    private float rotationSpeed = 10f;

    private InputAction horizontalInput;
    private InputAction verticalInput;

    [SerializeField] private GameObject XROrigin;
    [SerializeField] private GameObject mainCamera;
    private void OnEnable()
    {
        horizontalInput = new InputAction(binding: "<Gamepad>/rightStick/x");
        horizontalInput.Enable();
        verticalInput = new InputAction(binding: "<Gamepad>/rightStick/y");
        verticalInput.Enable();
    }

    private void OnDisable()
    {
        horizontalInput.Disable();
        verticalInput.Disable();
    }

    private void Update()
    {
        RotateCamera(horizontalInput.ReadValue<float>(), verticalInput.ReadValue<float>());
    }

    private void RotateCamera(float horizontalRotation, float verticalRotation)
    {
        XROrigin.transform.Rotate(mainCamera.transform.up, horizontalRotation * rotationSpeed * Time.deltaTime);
        XROrigin.transform.Rotate(mainCamera.transform.right, verticalRotation * rotationSpeed * Time.deltaTime * -1);
    }
}


```

# 回転部分の実装について
今回は簡易的な視点の回転を実装しました。おそらくHMDを付けて動くとおかしな方向に回転してしまう可能性もあるかもしれないです。今回はDebugで複雑な動きをしない想定で実装しました。

# まとめ
今回Input Systemを利用してDebug用にGamepadの入力を制御する実装をしました。今回の実装では確認のためにわかりやすくXROriginの移動にしていますが、Unityの中でテストしたい関数などをボタン一つで実行できるようにすると便利かと思っています。Debug用の隠しコマンドのような物も作成しようと思えば出来るかと思うので、今後は試していきたいと思います。また今回[Action](https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Actions.html)を利用出来ていないので、今後はActionを利用した方法も試していきたいです。

# 参考資料

https://docs.unity3d.com/ja/2021.3/Manual/class-InputManager.html

https://forpro.unity3d.jp/unity_pro_tips/2021/05/20/1957/#Input_System:~:text=%E3%82%88%E3%81%84%E3%81%A7%E3%81%97%E3%82%87%E3%81%86%E3%80%82-,%E3%81%93%E3%82%8C%E3%81%BE%E3%81%A7%E3%81%AE%E5%85%A5%E5%8A%9B%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%9C%E9%A7%84%E7%9B%AE%E3%81%AA%E3%81%AE%E3%81%8B,-Unity%E3%82%92%E4%BD%BF%E7%94%A8

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.4/manual/Installation.html

https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Gamepad.html#:~:text=%E7%A4%BA%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82-,%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB,-%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%82%B2%E3%83%BC%E3%83%A0

https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/Actions.html