UnityのInputSystemでさらに入力情報を取得するクラスを作ってみた

# 概要
UnityのInputSystemはキーボード、マウス、ゲームパッドなどの入力を一括管理できる代物です。詳しくは[マニュアル](https://docs.unity3d.com/ja/Packages/com.unity.inputsystem@1.4/manual/index.html)をご覧ください。

InputSystemは基本的に入力開始時と入力終了時、トリガーやスティックだったら`float`や`vector2`で押し具合を受け取れるものです。
ただアクションゲーム前提だともっと詳しく入力情報ほしいので、さらに入力回数、入力時間や入力開始、入力中、入力終了フレームを計算するクラスを作りました。

# 環境
Unity 6000.0.23f1

# 実装
まずは、InputActionAssetを作成してラッパークラスを生成します。
このクラスはActionで関数を登録することで入力時、入力終了時にコールバックを受け取れ、引数で入力値のクラスを受け取ることができます。

### InputData
このクラスでは先ほどのコールバック時に`Input`を呼び、引数のboolに入力時にはtrue、終了時にはfalseを渡します。

このコールバック時とは別にUpdateで毎フレーム、計算用の`PostInput`を呼んで入力回数や入力時間などを計算します。

```C#
/// <summary>
/// インプット状態の列挙
/// </summary>
public enum InputState
{
    None = 0, // 入力なし
    Start = 1, // 入力開始時
    Middle = 2, // 入力中
    End = 3 // 入力終了時
}

/// <summary>
/// インプット情報を格納する構造体
/// </summary>
public struct InputInfo
{
    public bool isInput; // 入力判定
    public int inputCount; // 入力回数
    public float inputTime; // 入力時間
    public InputState inputState; //入力の状態
}

/// <summary>
/// インプット情報計算の抽象クラス
/// </summary>
public abstract class InputData
{
    protected bool _isInput; 
    protected bool _isInputed;
    protected InputInfo _inputInfo;
    protected bool _isPool;
    protected bool _poolInput;
    
    /// <summary>
    /// 入力計算のためUpdateで毎フレーム呼ぶ
    /// </summary>
    public void PostInput()
    {
        if (!(_isPool & _inputInfo.inputState == InputState.Start))
        {
            if (_isInput)
            {
                _inputInfo.inputTime += Time.deltaTime;
                _inputInfo.inputState = InputState.Middle;
            }
            else
            {
                _inputInfo.inputTime = 0;
                _inputInfo.inputState = InputState.None;
            }
        }
        _isInputed = _isInput;
        _inputInfo.isInput = _isInput | _isInputed;
    }

    /// <summary>
    /// 先行入力用
    /// </summary>
    /// <param name="isPool"></param>
    public void InputPool(bool isPool)
    {
        _isPool = isPool;
        if (!isPool)
            _poolInput = false;
    }

    public InputInfo inputInfo {get => _inputInfo;}
}


/// <summary>
/// スティック、トリガーなど入力値がある場合
/// </summary>
/// <typeparam name="T"><see cref="UnityEngine.InputSystem.InputAction.CallbackContext.ReadValue{TValue}()"/>の型に合わせる</typeparam>
public class InputValueData<T> : InputData where T : struct
{
    private T _value; // 入力値

    public void Input(bool isInput, T value)
    {
        if (isInput)
            _value = value;
        if (!_isInputed & isInput)
        {
            ++ _inputInfo.inputCount;
            _inputInfo.inputState = InputState.Start;
        }
        if (_isInputed & !isInput)
            _inputInfo.inputState = InputState.End;

        _isInput = isInput;
        _inputInfo.isInput = _isInput | _isInputed;
    }

    public T value {get => _inputInfo.isInput ? _value : default;}
}

/// <summary>
/// ボタン用
/// </summary>
public class InputButtonData : InputData
{
    public void Input(bool isInput)
    {
        if (!_isInputed & isInput)
        {
            ++ _inputInfo.inputCount;
            _inputInfo.inputState = InputState.Start;
        }
        if (_isInputed & !isInput)
            _inputInfo.inputState = InputState.End;
            
        _isInput = isInput;
        _inputInfo.isInput = _isInput | _isInputed;
    }
}
```

使い方は以下のようになります。

```C#
public class PlayableCharacterInputManager : SingletonMonoBehaviour<PlayableCharacterInputManager>
{
    private PlayableCharacterInput _inputs;
    private List<InputData> _inputDataList;

    private InputValueData<Vector2> _move;
    private InputValueData<Vector2> _moveCamera;
    private InputButtonData _jump;
    private InputButtonData _guard;
    private InputButtonData _step;
    private InputButtonData _attack;
    private InputValueData<float> _shoot;

    protected override void OnAwakeProcess()
    {
        //InputActionAssetのラッパークラスをインスタンス化
        _inputs = new PlayableCharacterInput();

        //それぞれの入力用のInputDataを作成
        _move = new InputValueData<Vector2>();
        _moveCamera = new InputValueData<Vector2>();
        _jump = new InputButtonData();
        _guard = new InputButtonData();
        _step = new InputButtonData();
        _attack = new InputButtonData();
        _shoot = new InputValueData<float>();
        _inputDataList = new List<InputData>()
        {
            _move,
            _moveCamera,
            _jump,
            _guard,
            _step,
            _attack,
            _shoot
        };

        //CallBackを登録

        _inputs.Enable();
        _inputs.Player.Move.performed += Move;
        _inputs.Player.Move.canceled += Move;


        _inputs.Player.MoveCamera.performed += MoveCamera;
        _inputs.Player.MoveCamera.canceled += MoveCamera;

        _inputs.Player.Jump.performed += Jump;
        _inputs.Player.Jump.canceled += Jump;

        _inputs.Player.Guard.performed += Guard;
        _inputs.Player.Guard.canceled += Guard;

        _inputs.Player.Step.performed += Step;
        _inputs.Player.Step.canceled += Step;

        _inputs.Player.Attack.performed += Attack;
        _inputs.Player.Attack.canceled += Attack;
        
        _inputs.Player.Shoot.performed += Shoot;
        _inputs.Player.Shoot.canceled += Shoot;
    }

    protected override void OnDestroyProcess()
    {
        if (_inputs == null) return;
        _inputs.Disable();
        _inputs.Dispose();

        _inputs.Player.Move.performed -= Move;
        _inputs.Player.Move.canceled -= Move;

        _inputs.Player.MoveCamera.performed -= MoveCamera;
        _inputs.Player.MoveCamera.canceled -= MoveCamera;

        _inputs.Player.Jump.performed -= Jump;
        _inputs.Player.Jump.canceled -= Jump;

        _inputs.Player.Guard.performed -= Guard;
        _inputs.Player.Guard.canceled -= Guard;

        _inputs.Player.Step.performed -= Step;
        _inputs.Player.Step.canceled -= Step;
        
        _inputs.Player.Attack.performed -= Attack;
        _inputs.Player.Attack.canceled -= Attack;

        _inputs.Player.Shoot.performed -= Shoot;
        _inputs.Player.Shoot.canceled -= Shoot;
    }

    //それぞれのコールバックでInputを呼ぶ

    private void Move(InputAction.CallbackContext context)
    {
        if (context.performed)
            _move.Input(true, context.ReadValue<Vector2>());
        if (context.canceled)
            _move.Input(false, context.ReadValue<Vector2>());
    }

    private void MoveCamera(InputAction.CallbackContext context)
    {
        if (context.performed)
            _moveCamera.Input(true, context.ReadValue<Vector2>());
        if (context.canceled)
            _moveCamera.Input(false, context.ReadValue<Vector2>());
    }

    private void Jump(InputAction.CallbackContext context)
    {
        if (context.performed)
            _jump.Input(true);
        if (context.canceled)
            _jump.Input(false);
    }

    private void Guard(InputAction.CallbackContext context)
    {
        if (context.performed)
            _guard.Input(true);
        if (context.canceled)
            _guard.Input(false);
    }
    
    private void Step(InputAction.CallbackContext context)
    {
        if (context.performed)
            _step.Input(true);
        if (context.canceled)
            _step.Input(false);
    }
    
    private void Attack(InputAction.CallbackContext context)
    {
        if (context.performed)
            _attack.Input(true);
        if (context.canceled)
            _attack.Input(false);
    }

    private void Shoot(InputAction.CallbackContext context)
    {
        if (context.performed)
            _shoot.Input(true, context.ReadValue<float>());
        if (context.canceled)
            _shoot.Input(false, context.ReadValue<float>());
    }
            
    // Updateで毎フレーム呼ばれるとこでPostInputを呼ぶ
    public void PlayableCharacterInputPostTick()
    {
        foreach (var inputData in _inputDataList)
        {
            inputData.PostInput();
        }
    }

    public void PlayableCharacterInputPools(bool isInputPool)
    {
        foreach (var inputData in _inputDataList)
        {
            inputData.InputPool(isInputPool);
        }
    }

    public InputValueData<Vector2> move {get => _move;}
    public InputValueData<Vector2> moveCamera {get => _moveCamera;}
    public InputButtonData jump {get => _jump;}
    public InputButtonData guard {get => _guard;}
    public InputButtonData step {get => _step;}
    public InputButtonData attack {get => _attack;}
    public InputValueData<float> shoot {get => _shoot;}
}
```