Joyconを振ってることをUnityで検知したい

# はじめに
現在体力が付けられるパックマン風ゲームを作っている時につまづいたポイントがあったので、備忘録として残しときます。やりたかったこととしては、左足にJoyconをつけ、ユーザーが走る動作をすると、Unity内のキャラクターが同時に動き出すというものです。


## JoyconLibの導入
Joyconと接続はInputSystem使う方法もありますが、JoyconLibという素晴らしいライブラリがあったので、こちらを使いました。最新のunitypackageをダウンロードし、Unity Editorに入れてください。

https://github.com/Looking-Glass/JoyconLib/releases

## 加速度に低域フィルタを適用
FilterAccelValue()でJoyconから取得した加速度にフィルターをかけます。機能には関係ないですが、パラメータの変動をEditor上で可視化できる「UnityGraphs」というライブラリがあるため、そちらを利用すると実際にフィルタがかかっているのが確認できると思います。
```c#
float JoyConUpdate() 
{
    accel = m_joyconL.GetAccel();
    Vector3 filteredAccelValue = FilterAccelValue(true, accel);
    var current_accel_value = filteredAccelValue.magnitude;
    // current_accel_valueを可視化する
    DrawGraph
        .Add( "current_accel_value", current_accel_value )
        .SetColor( Color.yellow )
        ;
    return current_accel_value;
    
}

Vector3 FilterAccelValue(bool smooth, Vector3 accVal)
{
    if (smooth)
        lowPassValue = Vector3.Lerp(lowPassValue, accVal, LowPassFilterFactor);
    else
        lowPassValue = accVal;

    return lowPassValue;
}
```

https://github.com/nukadelic/UnityGraphs

## ステップのピークを検出する
加速度の山と谷を検出し、ユーザーがどれほど早く足を振っているのかを検知します。
```c#:ピーク検出のための処理
bool DetectorPeak(float newValue, float oldValue)
{
   lastStatus = bDirectionUp;

   // wave up
   if (newValue >= oldValue)                 
   {
       bDirectionUp = true;
       continueUpCount++;
   }
   // wave down
   else {                                                           
       continueUpFormerCount = continueUpCount;
       continueUpCount = 0;
       bDirectionUp = false;
   }

   // 山
   if (!bDirectionUp && lastStatus
           && (continueUpFormerCount >= 2 && (oldValue >= minValue && oldValue < maxValue)))
   {
       peakOfWave = oldValue;
       return true;
   }
   // 谷
   else if (!lastStatus && bDirectionUp)
   {
       valleyOfWave = oldValue;
       return false;
   }

   return false;
}
```

また、ピークを検出できたので、キャラクターの速度を計算しましょう。
速度は1/(timeOfNow-timeOfLastPeak)で計算できます。加えて、どうしてもステップを踏むごとにノイズが発生するため、波の持続時間を平均化しておきましょう。

```c#:キャラクター速度を計算
void DetectNewStep(float values)
    {
        if (last_accel_value <= 0f)
        {
            last_accel_value = values;
        }
        else
        {
            if (DetectorPeak(values, last_accel_value))
            {
                timeOfLastPeak = timeOfThisPeak;
                timeOfNow = Time.time;

                if (timeOfNow - timeOfLastPeak >= 0.1f
                    && (peakOfWave - valleyOfWave >= threadThreshold))
                {
                    timeOfThisPeak = timeOfNow;
                    aveSpeed = 1f / Peak_Valley_Thread(timeOfNow - timeOfLastPeak);
                }
            }
        }
        last_accel_value = values;
    }
```
```c#:ノイズの除去
public float Peak_Valley_Thread(float value)
    {
        float tempThread = 1f;
        if (tempCount < valueNum)
        { 
            tempValue[tempCount] = value;
            tempCount++;
        }
        else
        { 
            tempThread = averageValue(tempValue, valueNum);
            for (int i = 1; i < valueNum; i++)
            {
                tempValue[i - 1] = tempValue[i];
            }
            tempValue[valueNum - 1] = value;
        }
        return tempThread;
    }

    public float averageValue(float[] value, int n)
    {
        float ave = 0;
        for (int i = 0; i < n; i++)
        {
            ave += value[i];
        }
        ave = ave / valueNum;
        return ave;
    }
```

## 結果
計算した結果を速度としてキャラクターに割り当てることで、動画のようにJoyvonを持って走るとキャラクターが動くようになりました。
<iframe width="560" height="315" src="https://www.youtube.com/embed/4jWGCq2CIzw?si=dZkB8ZgpvWF4PHYA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## 最後に
こちらの記事を参考にさせていただきました。非常にわかりやすかったです。

https://note.com/thedesignium/n/n950f9d79ba8f