【Unity】複雑なアニメーション遷移を制御するAnimatorの作り方

 

昔こんな記事を書きました。

 

yutakaseda3216.hatenablog.com

 

さらに、最近Gotanda.unity - connpass という集まりでもAnimatorについてのLTを行いました。

 

ここらへんで、皆一度は悩むであろうAnimatorのそれなりにマシな作り方、設計の仕方をまとめていこうと思った次第です。

 

Animatorの矢印が多くて困っている、どういう指標で作ればわからない

という方への手助けとなれば幸いです

 

既知の手法でAnimatorを作った際の問題点

 

まず、既知の手法でAnimatorを作るとこういった形になると思います

 

https://docs.unity3d.com/jp/540/uploads/Main/MecanimAnimatorControllerWindow.png

 

1つ1つAnimatorのステートを作り、それらをトランジションで繋ぐ作り方です

 

トランジションの制御にはそれぞれboolやTriggerといったパラメータを用意し、スクリプトでAnimator.SetBool()などを呼び出す...

 

 

ありがちですね

 

UIなどに使用するシンプルなアニメーションであれば、上記手法でも良いかと思います

 

しかし、キャラクターの制御などにこれをそのまま当てはめてしまうとこのようになっていくと思います

 

https://forum.unity.com/attachments/all-animations-small-jpg.67588/

 

これは極端な例ですが、矢印とパラメータが量産され視認性が悪くなっていきます

 

視認性の悪さを改善できる、サブステートマシンという機能が存在します

 

https://gametukurikata.com/wp-content/uploads/img/substate2.png

 

サブステートマシンとは、複数のステートをまとめておくことのできる機能です

 

詳細な説明はこちらの方が詳しく載っていますので割愛します

tsubakit1.hateblo.jp

 

サブステートマシンは良いグルーピングの手法ではありますが、この手法ではパラメータが量産されることを防止できません

 

また、グルーピングされた結果サブステートマシンの中の特定のステートから、他のサブステートマシンの特定のステートへ直接遷移する

などの手法はトランジションでは取りづらくなります

 

つまるところ、根本的な解決にはなっていません

 

 

必要な要件について

 

現在開発している3Dアクションゲーム、SLEEPWALKERでは

  • 大量にあるステートの全てが自由に遷移を組むことができること
  • ステートの追加、組み替えが容易であること
  • 時間に応じたモーションブレンディング設定
  • 時間に応じたエフェクト設定、etc

が必要でした

いろいろと試行錯誤し、このような非常にスムースな遷移を実現できました

 

youtu.be

 

 

具体的な設計、実装について

Animator部分

まず、Animator自体はこのようになっています

 

f:id:yutakaseda3216:20171219123349p:plain

 

トランジション、パラメータは一切ありません

デフォルトのステートはIdle(待機)モーションになるように設定してあり、AnyStateやExitなどの機能も使用していません

 

遷移は全てAnimator.CrossFadeInFixedTime()を使用しています

これについてはこちらをご覧ください

 

 

yutakaseda3216.hatenablog.com

 

 

遷移の方法について

遷移はStateMachineBehaviourを継承したスクリプトを全てのステートに付け、制御を行っています

 

docs.unity3d.com

 

StateMachineBehaviourを継承することで、現在のステートのNormalizedTime(アニメーション経過時間)を取ることができるため

 

経過時間に合わせた処理を行うことができるようになります

 

まず基底クラスを作成し、NormalizedTimeなどを取得しやすくしました

かなりレガシーコードです、整理もしてません。Rxを使用しているのと、TODOとか書いてあるので、設計自体はあまり参考にしないでください

 

gist.github.com

 

ResetTime()メソッドは、NormalizedTimeはステートにいる限り加算され続ける仕様のため、ループ設定をした際にNormalizedTimeが0.0f~1.0fで見れるようにtimeを初期化する処理です

 

気をつけないとループするアニメーションの際に設定したエフェクトなどが再生されなくなるので気をつけましょう

 

 

この基底を使いこのような設定を行うことのできるスクリプトができました

 

トランジション設定スクリプト

f:id:yutakaseda3216:20171219124202p:plain

 

遷移が可能なステートのenumを定義し、遷移優先度、遷移可能時間などを設定しています

 

ソースコードもそのまま乗せておきます、こちらもかなりレガシーなコードなのであまり参考にはせず、ニュアンスだけの理解にとどめてもらえると嬉しいです

 

gist.github.com

 

今見てもシングルトン経由したりしてやべえコードだ・・・

 

上記のようにAnimatorでアニメーション時間に関連する処理を書いていますが

AnimatorのStateと連動したStateMachineを作成し、Animatorの外側でゲームに関連する処理を書いています

 

gist.github.com

 

アニメーション時間に応じたその他の処理

 

攻撃判定発生スクリプト

f:id:yutakaseda3216:20171219124243p:plain

 

こちらも時間軸で動作を定義しています

 

SE再生スクリプト

f:id:yutakaseda3216:20171219124314p:plain

 

ループ設定などもできるようにしてあります 

 

 

まとめ

というわけで、トランジション設定をStateMachineBehaviourのスクリプトに寄せ、アニメーション時間が必要な処理もまとめてぶちこんでいます

 

 

トランジション設定を作っていくのに比べて

ので視認性、拡張性に優れます

 

また、Unity2017で追加されたPlayableを使用した完全スクリプトベースの手法も存在しますが

  • Animatorでステートの制御をぽちぽち作れること
  • ステートをビジュアル化しておけること
  • コード量の少なさ

で優位です

 

もちろん適材適所ですが、1つの指標になれば幸いです

 

 

宣伝

現在開発中のゲーム、SLEEPWALKERの体験版がweb公開されました!

よろしければぜひプレイしてみてください

 

https://www.studiophantomisland.com/