どうも、Reveです。最近、またUnityを使う機会が増えました。
今日は、久しぶりにUnityネタの記事を書こうかと。
モーション切替の悩み
Unityでキャラクターのアニメーションを入れていく中で、例えば武器を持ち替えた時に攻撃モーションを別物にするなど「共通する動作は多いけど、このモーションだけ替えたい」といった悩みは出てくると思います。
もちろん、Animation Controllerのステートを増やす、あるいはそれ自体を別々に用意するといった手法でもよいのですが、あまりステート数やファイルが増えてしまうと散らかってしまう上、ロード時間やメモリ容量を食うといった欠点もあります。
AnimationClipだけを動的に差し替え
そこで、一部のモーションだけを切り替えるのに「AnimatorOverrideControllerを作成して、一部のステートに別のモーションを割り当てる」という方法があります。
Animator(Animation Controller)にある各ステートのモーション(Animation Clip)は直接差し替える事はできない(?)ものの、Animation Override Controllerを元のAnimatorから生成することで動的な差替えが可能となります。大まかな手順としては以下の通りです。
- Animation Controllerと差し替え元のAnimation Clipを作成してキャラに割り当て
- 元のAnimatorからAnimatorOverrideControllerを生成し、AnimatorのruntimeAnimatorControllerに割り当てる
- AnimatorOverrideController[“クリップ名”]=(Animation Clip) でモーションを差し替え(インデクサ)
コントローラー(Animation Controller)とクリップ(Animation Clip)の用意
では、差し替え元になるコントローラーとクリップを作りましょう。ここは単にコントローラーとクリップを作成、あるいは既存のものを導入すればOKです。
コントローラーに関してはUnity公式から提供されているパッケージがあるため、それを利用するのが簡単だと思います。
一方でモーション用のクリップは意外と悩みものです。自分で打ち込むなど作成していくのも手間がかかりますが、かといって目的に合うモーションを探すのも難しいもの。
今回は棒を振る(左)のと投げる(右)モーションを切り替える想定で作りました。が、お試しで使える投げモーションが無かったので別のもので代用しました。
なお、有料版もあります。気に入ったモーションがあれば拡充してみるのもいかがでしょうか?
ちなみに、有償であれば投げるモーションもあります。
コントローラーとクリップを用意したら、キャラクターに割り当てましょう。
ちょっと詰まったのが、Animator Override Controllerのインデクサが差し替え元のクリップ名を参照する点。ステート名ではないので、ステートと実際のファイル名が異なる場合、モーションが変わりません。
今回は単一のモーションだったので、ステート名と同じ「Attack」というダミーのクリップを作成して割り当てました。projectウィンドウの右クリックメニューで作れるので、ステートと同じ名前で作成することでややこしさが軽減されます。ブレンドツリー(Blend Tree)などより複雑な事をする場合もありますが、いずれにせよ分かりやすい名称を付けるのが管理する上で重要だと思います。
C#スクリプト
大体の処理が分かったところで、C#スクリプトを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
public class ChangeMotion : MonoBehaviour{ [SerializeField] private Animator animator; [SerializeField] private string clipName = "Attack"; private AnimatorOverrideController overrideController; // Start is called before the first frame update void Start() { // animation animator ??= GetComponent<Animator>(); // (2) AnimationOverrideControllerの生成と割り当て overrideController = new AnimatorOverrideController(animator.runtimeAnimatorController); animator.runtimeAnimatorController = overrideController; } public void Change (AnimationClip clip) { // exchange motion AllocateMotion(clipName, clip); } private void AllocateMotion (string name, AnimationClip clip) { AnimatorStateInfo[] layerInfo = new AnimatorStateInfo[animator.layerCount]; for (int i = 0; i < animator.layerCount; i++) { layerInfo[i] = animator.GetCurrentAnimatorStateInfo(i); } // (3) AnimationClipを差し替えて、強制的にアップデート // ステートがリセットされる overrideController[name] = clip; animator.Update(0.0f); // ステートを戻す for (int i = 0; i < animator.layerCount; i++) { animator.Play(layerInfo[i].fullPathHash, i, layerInfo[i].normalizedTime); } } } |
重要なのはメソッド Start() と ChangeMotion(string name, AnimationClip clip)。
前者がAnimatorOverrideControllerの生成と初期化、後者がモーションの切り替えを担当します。前述のとおり、インデクサの添字に元のモーション名を入れることで差し替え対象を指定します(代入するのはAnimation Clip)。
あとは、適当な仕組み(ボタンやキー入力など)でメソッド Change(AnimationClip clip) を呼び出してからアニメーションを再生させると、同じステートに移動しても別のモーションになります。
上の画像ではちょっとわかりづらいかもしれませんが(汗)、腕の角度や降る速さなどが確かに変わってます。
良ければ動画もご覧ください。
というわけで、動的にモーションを差し替える手法を紹介しました。
参考
おまけ
ちなみに参考としたブログでは、Animatorやステートが増えすぎてとっ散らからないよう色々な手法を模索されていました。
ステートマシンの上書き(style2)かPlayable APIが良さそうと書かれていますが、単一のキャラかつ一部差し替えならAnimatorOverrideControllerを動的に生成するのもありかと考えています。
Mechanim や Playable API などアニメーション制御用のツールはなかなか複雑ですが、ここでも探求していきたいところです。