##デモ
タイトルの内容について書きますが、百聞は一見に如かずということでご覧ください。
膝の角度に合わせて数値及び、青い角度計が変化しています。
これは膝の角度を読み取っているわけではないです。
アニメーション(スクワット)の再生時間に応じてパラメータを変化させています。
2019/06/25 追記
アニメーションはこちらから拾ってきました→Mixamo
##GetCurrentAnimatorStateInfo
Animatorを作ると複数のステートを作成できます。
ステートはアニメーションを遷移させるときに便利です。
今回は再生中のアニメーションを調べる必要があるので、
GetCurrentAnimatorStateInfo(0)
を使って調べます。
[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で補完
します。
と言われてもいまいちわからないと思います。(私は使ってみるまでさっぱりでした)
なので一例を示します。
まずはコードです。
[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が汚いのはご容赦ください)
これが先程の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に戻り、再度 指定値まで循環します。
[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() + "°";
}
##fillAmount
角度計のアニメーションはこちらのサイトを参考にしました。
アニメーションに応じて、FillAmountの値を変化させています。
##最終的なコード
当方、マジックナンバーおじさんですが、ご容赦ください。
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フレームは別の処理を...
みたいなことができれば便利なのですが、見つかりませんでした。(ありましたら教えてください)