【Unity(C#)】Animationとパラメータを連動させる方法

##デモ
タイトルの内容について書きますが、百聞は一見に如かずということでご覧ください。

![Gif_Squat.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/848658a5-b093-db4f-4191-4736e7b34960.gif)

膝の角度に合わせて数値及び、青い角度計が変化しています。

これは**膝の角度を読み取っているわけではない**です。


**アニメーション(スクワット)の再生時間に応じてパラメータを変化**させています。

---
2019/06/25　追記
アニメーションはこちらから拾ってきました→[Mixamo](https://www.mixamo.com/#/)

##GetCurrentAnimatorStateInfo
Animatorを作ると複数のステートを作成できます。

ステートはアニメーションを遷移させるときに便利です。

今回は再生中のアニメーションを調べる必要があるので、
`GetCurrentAnimatorStateInfo(0)`を使って調べます。

```C#

   [SerializeField]
   Animator squatAnimeController;

   void Update()
    {
       AnimatorStateInfo animeStateInfo = squatAnimeController.GetCurrentAnimatorStateInfo(0);
    }
```

`GetCurrentAnimatorStateInfo(0)`を使うことで、再生中のアニメーションを取得できます。
引数はアニメーションのLayerですが、複雑なアニメーションを作成してLayerを使用した場合を除いて、
基本的にデフォルトの`0`で大丈夫です。

## AnimatorStateInfo.normalizedTime
`normalizedTime`を使用すれば**アニメーションの再生時間を0～1の値に正規化**することができます。

**正規化された時間を利用して他のパラメータの値を操作**すれば
デモのようにアニメーションに合わせて数値を変化させることができます。


##Mathf.Lerp
先程の正規化された時間と`Mathf.Lerp`を利用して数値を連動して変化させます。
`Mathf.Lerp`の引数は3つ用意されており、`Lerp(float a, float b, float t)`となっています。

それぞれの引数は
`a：開始値`
`b：終了値`
`t：補完値(0～1)`
となっています。`aの値からbの値に向けてtで補完`します。
と言われてもいまいちわからないと思います。(私は使ってみるまでさっぱりでした)

なので一例を示します。

まずはコードです。

```C#

   [SerializeField]
   Animator squatAnimeController;

   [SerializeField]
   Text degreesText;

   void Update()
    {
       AnimatorStateInfo animeStateInfo = squatAnimeController.GetCurrentAnimatorStateInfo(0);

       float degrees = Mathf.Lerp(180, 0, animeStateInfo.normalizedTime); 
       degreesText.text = ((int)degrees).ToString() + "°";
    }
```

このように、アニメーションの再生時間(正規化されているので0～1)に合わせて
角度が180～0°で変化しているのがわかるかと思います。(GIFが汚いのはご容赦ください)
![Gif_Squat_Explanation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/b854f788-d390-4d0e-b74e-922b357a79d4.gif)

これが先程の`aの値からbの値に向けてtで補完する`という意味です。

a：開始値(180)
b：終了値(0)
t：補完値(正規化したアニメーションの再生時間0～1)

##Mathf.Repeat
先程のGIF画像ではステートを二つ用意して、同じアニメーションを交互に遷移させていました。
その理由としてはアニメーションのループ設定がうまくいかなかったからです。

ループ設定してしまうと、アニメーションの正規化がうまくできないようです。
0～1収まるはずの正規化した値が1を超えて無限に増えていきます。(ループに終了時間という概念が存在しないから？)

もし、なんらかの事情でアニメーションのループ設定を外せない場合は、
`Mathf.Repeat`で正規化した値が1を超えないようにします。

`Mathf.Repeat(float t, float length)`は
**指定値(length)を超えると0に戻り、再度 指定値まで循環**します。

```C#
   [SerializeField]
   Animator squatAnimeController;

   [SerializeField]
   Text degreesText;

   void Update()
    {
       AnimatorStateInfo animeStateInfo = squatAnimeController.GetCurrentAnimatorStateInfo(0);

       float degrees = Mathf.Lerp(180, 0, Mathf.Repeat(animeStateInfo.normalizedTime, 1)); 
       degreesText.text = ((int)degrees).ToString() + "°";
    }

```

これでループ設定を外せないとしても安心です。
![Gif_Squat_Explanation_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/f95b8d01-5438-fc12-dd2c-7b309c01e209.gif)


##fillAmount

角度計のアニメーションは[こちらのサイト](https://clrmemory.com/unity/circle-gauge-meter-p1/)を参考にしました。


アニメーションに応じて、FillAmountの値を変化させています。
![ImageFillAmount.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/473fa11a-5dd1-34af-8047-c9c5751ac9ac.png)


##最終的なコード

当方、マジックナンバーおじさんですが、ご容赦ください。

```C#:作成した画像(色付き)にアタッチ
using UnityEngine.UI;
using UnityEngine;

public class AngleVisualController : MonoBehaviour
{
    [SerializeField]
    Animator squatAnimeController;

    AnimatorStateInfo animeStateInfo;

    [SerializeField]
    Text degreesText;

    Image circle_Image;

    void Start()
    {
        circle_Image = this.gameObject.GetComponent<Image>();
    }

    void Update()
    {
        animeStateInfo = squatAnimeController.GetCurrentAnimatorStateInfo(0);

        float squatVertical = 25 / 60.0f;
        float degrees = 0;

        if (animeStateInfo.normalizedTime < squatVertical)
        {
            circle_Image.fillAmount = Mathf.Lerp(0.48f, 0.25f, animeStateInfo.normalizedTime * (60.0f / 25));

            degrees = Mathf.Lerp(170, 90, animeStateInfo.normalizedTime * (60.0f / 25));
            degreesText.text = ((int)degrees).ToString() + "°";
        }
        else
        {
            circle_Image.fillAmount = Mathf.Lerp(0.25f, 0.48f, (animeStateInfo.normalizedTime-(25.0f/60)) * (60.0f / 25));

            degrees = Mathf.Lerp(90, 170, (animeStateInfo.normalizedTime - (25.0f / 60)) * (60.0f / 25));
            degreesText.text = ((int)degrees).ToString() + "°";
        }
    }
}
```

なぜこんなわけのわからない計算をLerpの中に仕込ませているかというと、
・スクワットのアニメーションの折り返し地点(正規化した値でいうと0.5)がしゃがんだ状態ではない
・角度の値が、直立(170)→しゃがむ(90)→直立(170)を取る
・アニメーションの再生フレームを取得できない
からです。

なので、もっとシンプルに使うには
**・しゃがむ、立ち上がる　の二つのアニメーションを用意する**
なのかな～と思います。そうすれば、変な計算しなくても正規化した値をそのまま使えるかと思います。

本当は、アニメーションの再生フレームを取得して
0～25フレームはこの処理をして、26～60フレームは別の処理を...
みたいなことができれば便利なのですが、見つかりませんでした。(ありましたら教えてください)