Last Updated 2025.9.18
Animatorのアニメーションステートに設定したAnimationの進行度を参照する方法として、もっともポピュラーなやり方。
normalizedTimeを参照する方法についての、ちょっとした備忘録です。
そもそもnormalizedTimeって何者?
Animatorの内部には、固有の時間が流れています。
それは、秒数ではなく「アニメーションの進行度」という、少し特殊な時計です。
0.0から始まって、1.0でアニメーションが一周。
もしループしていたら、2.0、3.0……と、針は何周でも回り続けます。
この値を使えば、 「アニメーションがどこまで進んだか」 「そろそろ終わりそうか」 「今がちょうど中間か」
そんなことが、感覚ではなく数値でわかるようになります。
つまり、normalizedTimeは、 アニメーションの“今”を教えてくれる観測者 みたいなもの、というわけですね。
そもそもどういった用途で使うもの?
Animationの開始や終了、このAnimationに入ったら参照開始、あるいは実行して欲しい──といった、
フレーム単位の "厳密なタイミング調整が必要ない仕組み" を、スクリプトベースで実装する際に向いています。
▼サンプルスクリプト
using UnityEngine;
// AnimatorのnormalizedTimeを参照するサンプルコード
public class NormalizedTimeProbeSample : MonoBehaviour
{
public Animator animator; // サンプルなのでインスペクターでアタッチする
public float normalizedTime = 0;
void Update()
{
//サンプル 前進(フロントステップ)処理
if (animator.GetCurrentAnimatorStateInfo(0).IsName("FrontStep"))// ※このステート名(FrontStep)は任意です
{
normalizedTime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
if (normalizedTime >= 1)
{
// 終了のタイミングで行いたい処理を記述
Debug.Log("アニメーションが終了しました");
return;
}
if (normalizedTime > 0)
{
// 0~1の間で行いたい処理を記述。計測や開始合図など
Debug.Log("移動中");
}
}
}
}
逆にいえば、「絶対にこのタイミングで実行して欲しい」といった処理には向いていません。
僅かにブレが発生するので(コンマ以下ですが)、if構文で条件を取ろうする場合──
等号(==)だと信頼性に問題がある(処理のすり抜けが起こり得る)ので、">" や ">=" といった不等号を使用することになります。
補足:
"=" は 代入演算子(右辺の値を左辺に代入する)なので、「等号」とはちょっと違う意味になります。
if構文のイージーミスで、よくあるパターンですね。
なので、絶対にこのタイミングで実行して欲しいといった処理は、AnimationEvent等の方法で実装する方が確実です。
一回だけ実行する(例:音を鳴らす等)といった処理にも、この書き方だとひと手間必要になりますしね……
ただ、便利なのは間違いないです。
この書き方(タイミングの判定方式)は、自分もよく活用しています
normalizedTimeの扱いと変数のスコープについて
ここからが今回の本題です。
Unityの AnimatorStateInfo.normalizedTime は、Update()内で参照するのが基本です。
Animatorの状態はフレームごとに変化するので、Update()で毎回取得することで最新の状態を確実に反映できるという仕様ですね。
なので、こういう使い方:
void Update()
{
float normalizedTime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
// ここで条件分岐や処理を書く
}
が、理にかなってると思います。
インスタンスフィールドにするべきか?
もし、normalizedTime を他のメソッドでも使いたいとか、Update()以外の場所で参照したいという明確な理由があるならば、フィールドにしてもいいのですが──
それ以外の場合、Update()内でローカル変数として扱う方が安全で明快になります。 特にAnimatorの状態はフレームごとに変わるので、フィールドにしてしまうと古い値を参照してしまうリスクもあるそうです。
GetCurrentAnimatorStateInfo(0) の繰り返しが気になる場合も……
var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
という風に、一度変数に入れておくと可読性もパフォーマンスも少し良くなります。
↓取り入れると、こんな感じですね。
using UnityEngine;
// AnimatorのnormalizedTimeを参照するサンプルコード
public class NormalizedTimeProbeSample : MonoBehaviour
{
public Animator animator; // サンプルなのでインスペクターでアタッチする
void Update()
{
// サンプル 前進(フロントステップ)処理
var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (stateInfo.IsName("FrontStep"))// ※このステート名(FrontStep)は任意です
{
float normalizedTime = stateInfo.normalizedTime;
if (normalizedTime >= 1)
{
// 終了時に実行したい処理をここに
Debug.Log("アニメーションが終了しました");
return;
}
if (normalizedTime > 0)
{
// 移動中に行う処理をここに記述
Debug.Log("移動中");
}
}
}
}補足:ループアニメーションの注意点
なお、normalizedTime は ループしてるアニメーションの場合、1を超えていく仕様になっています。「0〜1の範囲で見たい」場合は、 Mathf.Repeat(normalizedTime, 1f) を使うとよい──という話もあります。
自分が現在制作中のプロジェクト(いわゆる格闘ゲームみたいなジャンル)だと……待機(Idle)状態。いわゆるニュートラルポーズぐらいしか該当はしないんですが、「1を超える」という仕様が意外な落とし穴になることもあるので、これも覚え書きとして触れておきますね。
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (stateInfo.IsName("Idle"))
{
float loopedTime = Mathf.Repeat(stateInfo.normalizedTime, 1f);
// ここで処理
}
▲loopedTimeの出力結果は、0~1の範囲内になります
-
Mathf.Repeat(normalizedTime, 1f)でnormalizedTimeを0〜1の範囲に収めることが出来る -
normalizedTimeを取得するタイミングによっては、 アニメーションが切り替わっていて意図しない値になることもあるので、IsName()でステートを確認してから使うと安心できる
おわりに
今回の焦点としては、Animetor の normalizedTime を参照するスクリプト内で、それを格納する変数を……
- インスタンスフィールドでちゃんと定義する
- Update()内のローカル変数として扱う
の、どちらがいいのか? というお話でした。
参照目的ならローカル変数で扱う方が、軽いし安全 ということですね。