今回はAnimatorを使用している時にアニメーション再生が完了した事を検出する方法について考えてみます。
アニメーション終了検知
もしUIをアニメーションで検出したり、キャラクターの攻撃後にエフェクトを表示といった事をしたい場合、アニメーションの終了「後」のタイミングで何らかの処理を挟むといった事が必要になります。
さてAnimatorコンポーネントはAnimationコンポーネントと異なり、アニメーションを再生・管理するためにRuntimeAnimatorControllerを経由して管理しています。またアニメーション再生管理をステートマシンによって管理しているため、直接管理する事が出来ない糞仕様となっています。
AnimationコンポーネントであればシンプルにAnimation.IsPlayingでアニメーションの終了を検出できますが、Animatorではどうでしょう。とりあえず4つのパターンが考えられます。
現在のステートのnormalizedTimeを見る
まずは現在のステートのnormalizedTimeを取得するアプローチです。normalizedTimeはアニメーション開始時を0・再生後を1とするようにアニメーションの長さを正規化したものです。アニメーションの再生状態はnormalizedTimeが0からスタートし、最終的には1になる訳です。
つまり、アニメーションが再生完了している状態はnormalizedTimeが1以上になります。この部分を利用して、アニメーションの再生終了を知ります。
ステートを取得するには、「どのレイヤーのステートなのか」という情報が必要になります。殆どの場合ステート0(≒ Base Layer)が該当します。
後はanimator.GetCurrentAnimatorStateInfo (レイヤー番号).normalizedTime のような形で現在再生中のステートのnormalizedTime が1以下なら再生中…のようなコードを記述してやればOKです。
このアプローチで2点注意すべきは、アニメーションがループしている場合とステートがAnimatorのグラフにより移動する場合です。
ループはそもそもアニメーション再生完了の概念が無いので置いておくとして、ステートが移動する場合は何とかしたい所です。
ステートを比較してアニメーションの終了を知る
アニメーションが終了しステートの移動が行われる場合、2つの検出方法が考えられます。一つは再生中のステートを保持しておき、現在再生中のステートと比較する、もう一つはステートの切り替わりを検出するです。
再生中のステートを保持しておき比較するアプローチは割と概念としては簡単です。animator.GetCurrentAnimatorStateInfo (レイヤー番号).fullPathHashで再生中のアニメーションのハッシュ値*1をキャッシュしておき比較すればOKです。
ここの罠としては、再生直後に現在のステートが切り替わる訳ではない…という微妙に面倒くさい点です。たとえPlayで即時切替しても取得するステートが切り替わっているのは1フレーム後となります。
もう一つの方法は、StateMachineBehaviourを使用する事です。StateMachineBehaviourはステートの移動を検知できるので、そこに処理を挟んでおけばステートの移動を検出する事が出来ます。
ただ、StateMachineBehaviourに制御を記述すると色々と面倒くさいので、ExecuteEvents.ExecuteでMonobehaviourへメッセージを投げるようにしておくと良い感じになりそうです。
ステートが移動する…について
なおアニメーションが終了した際に移動するのは、Has Exit Timeの効果です。
ここで面倒くさいのが、Exit Timeが1でないと途中からアニメーション切り替えを始めます(初期値は0.7付近)。この設定は3Dモデルならモーションの終わりから次のモーションへ綺麗に繋いでくれるのですが、2Dスプライトアニメーションだと「終わり付近のスプライトアニメーションを再生せずスキップ」したりします。
とりあえず2Dスプライトアニメーション向けに使用する場合、Exit Timeは1でTransition Durationは0.こうしとけば多分大丈夫です。
コルーチンと組み合わせる
最後に、「アニメーション再生が終わったら云々」を行うためカスタムコルーチンと組み合わせます。
下のコードではWaitForAnimation を新しく定義し、ここでアニメーションが終了していればテキストを変更…といった感じの処理を実装しています。