Unityアニメーションシステムの今と未来

山村達彦氏(以下、山村):よろしくお願いします。先ほどご紹介に預かりました、Unity Technologies Japanの山村と申します。今回は「Unityアニメーションシステムの今と未来の話」ということで話していこうと思います。このUnityのアニメーションシステムは、2017以前から、いろいろな機能が追加されてきました。しかし、「なんとなく使用している場合が多いかな」という認識があります。

今回はこの場を借りまして、そのUnityのアニメーションシステムが本来どういうものだったのかということと、今後どんな機能が追加されていくのかについて、お話ししていこうと思います。

では、まず「Unityにおけるアニメーションとは何か?」という話をしていこうと思います。その後に、Animationというすばらしい機能を紹介して、Mecanimという新しく追加された機能を紹介します。そして、Playable、Timeline、Animation C# Jobs、Kinematicaといったことを紹介していこうと思います。

最初はAnimationです。「Unityにおいてアニメーションとは何か?」と言われると、要するに時間で変化するシステムのことです。

0フレームの時は0フレームの挙動があるし、15フレームだったら15フレームのなんらかの挙動があります。30だったら30。「15フレーム目」と指定したら、15フレーム目の挙動がちゃんと返ってくるというシステムです。しっかり時間と連動して、中間がきちんと保たれるシステムのことを指しています。

基本的にアニメーションは、「時間のカーブを取得して、そのカーブのなかを評価する」ものを指していると思っています。このカーブのなかで変化する値を評価するということです。

カーブからカーブまでを、どんなカーブを選んで、その中間のこのフレームだったら何を持ってくるのか、そのフレームだったら何を持ってくるのか、といったものを取っていくシステムです。時間によっては、1.0から、1.3から、0.2からのように、いろいろ変わるかもしれません。

変化するものは、Unityのシステムですと、Inspectorで表示される値は概ね変更することができます。例えばposition、rotation、color、UVや、Animatorからは、SpriteやMaterialなどのオペレーションの差し込みも可能です。

カーブは基本的に、AnimationClipから取得します。

FBXやeditorからも作ることができます。そういったものからAnimationClipを生成して、そのなかにはカーブ情報など、いろいろ入っています。

Animation(Legacy)の概要

アニメーションといえば、変形することもよくありますが、こちらはスキニングという技術で、アニメーションとは微妙に違う動きになります。基本的にはGameObject……スケルトンやボーン構造を使って、位置、回転などを変形することによって実現しています。

かつてUnityは、GPU Skinningという機能を持っていました。これは、GPUを使ってスキニングをする技術です。昔はStream Outputというものを使っていたんですが、これがちょっとイケてないというか、あまり使っている人がいませんでした。環境によってはあまり速くなかったため、現在はcompute shaderに切り替え中です。

アニメーションしないもので、スキニングを使う場合もあります。 ということで、最終的にはAnimationClipを持ってきて、回転や座標などをアニメーションで評価して、スキニングでアニメーションを実現するという流れが、Unityのアニメーションシステムの一番基本的なところです。

これを実現するための機能が、このすばらしいAnimation(Legacy)です。

Unityに昔からあった機能で、今でも大人気の機能です。Legacyという名前が付いていますが、今でも大人気。Animationを使うなら、これは大事です。

Animationで行っていることは、AnimationClipからカーブ情報を持ってきて、その情報をもとにGameObjectにバインドして、その対象を変形させたりといったことです。

あとは、2つのClip同士でカーブをブレンドしてあげたり、メソッドを呼び出すといったことが可能になっています。

基本的にはこの流れです。GameObjectの変形といったものは、基本的には名前ベースで取ってきています。つまり、評価する瞬間に名前が一致したら、それが反映されます。これは、GameObjectをダイナミックに作ってもいいということです。

ただし、AnimationClipのカーブだけでは表現できない機能も多いです。例えば、髪のDynamic Boneなどの揺れものです。また、胸や髪、アクセサリーといったものは、普通にAnimationClipに全部突っ込むというわけにはいかないこともあるため、そうしたものは事後処理で、追加でエフェクトを付けることは、よくあります。

これは、LateUpdateと言われるタイミングで行っておくと、アニメーションの処理から再生までの間に、ちょっとした処理を付け加えることができます。

今のAnimationの機能

ここまでは過去の話です。この程度であれば、理解している人が多いので退屈な話だったかもしれません。

とはいえ、次に紹介する新しい機能は、過去の機能を理解していないと、理解しにくいため紹介させていただきました。

ちなみにAnimationには、「Legacy」という名前が付いていますが、なくなる気配はまったくありません。根強い人気というか、いくつかの大きなところで普通に使われているため、おそらく将来的にもなくならないんじゃないかと思います。

さて、アニメーションの過去の話をしましたので、ここからは未来の話をしようと思います。未来というか、今の話ですね。つまり、今の使える機能の話です。

今、使える機能にどんなものがあるかというと、Animation(Legacy)と、Mecanimという機能があります。これはAnimationと同じような使われ方をしていることが多いです。今回紹介するメインの話です。あとはTimelineですね。

Animationは先ほど紹介しましたので、今度はMecanimとTimelineの2つを紹介しようと思います。これが本当はどんな機能なのか。マニュアルでは使い方しか紹介していないので、それが本来どういう機能だったのかを紹介していこうと思います。

Mecanimはどのように動くか

では、Mecanimがどういう機能かをご説明します。その前に、Mecanimがどのように動くのかを話していきましょう。まず、Mecanimには今までどおり、AnimationClipがあります。

あとは、最終的なスキミングの対象でオブジェクトがあります。その間に、2つのコンポーネント、Animator Controllerというアセットと、Avatarという謎のシステムがあります。その3つを使って、Mecanimはいろんなことを実現しています。

このMecanimを一言で紹介すると、実は若干面倒くさいシステムの1つです。なぜかというと、複数の機能を持っているからです。

この4つの機能が入り混じって書かれているせいで紹介しにくいというか……普通に紹介しようと思った時に、State Machineがあるよという、その程度しか紹介できないんですが、実際はこんな機能が入り混じった、若干面倒くさいシステムです。

例えば、GameObjectを経由せず、直接マトリクスに書き込むことができたり、リターゲットの機能だったり、あとはキャラクターの移動、座標の移動みたいなものを、Animation側で制御できたりします。再生するアニメーションを自分で判断する機能もあります。

では、これはどんな機能なのかを1つずつ紹介していこうと思います。まずはリターゲットの話です。

異なるボーン構造で同じアニメーションを使う

異なるボーン構造であったり異なる体型……例えば身長が高い人、低い人、太っている人、女の子、男の子、あとはボーンが全く違うタイプのものだったりとか。

そういった異なるボーン構造、異なる体型であっても、ヒト型であればアニメーションを共通して使うことができます。特別なヒューマノイドリグを用意して、それを経由してアニメーションさせることで、互換性を持たせるという機能です。

では、それ以前にリグとはどういうものかというと、これを作る手順のなかに入っています。

まずはモデリングで頂点とポリゴンを作って、スキニングでボーンを追加したり、ウェイトを付けたり、ボーンが動いた時にどういうふうに変形するのかです。

ボーンに対して、どのようにコントロールさせるのかリギングします。腰を回すであったり、(肘を曲げながら)こう押したらこう動くであったり。それをもとにアニメーションを作っていきます。

アニメーションを再生する場合は逆ですね。AnimationClipのなかにカーブ情報が入っていて、それをリターゲットして、スキニングし、モデルが表示される。このリターゲット部分も、Mecanimはサポートしています。

これによって、他のヒューマノイドとぜんぜん違うボーン構造を持っているモデルでも、同じようにアニメーションさせることができます。この情報はAvatarに格納しています。これを使うことで互換性を持たせています。

どういうことかというと、例えばAnimationが「右腕を動かしたい」という場合でも、ボーンの名称や配置、レイヤー、構造が違った場合、アクセスすることができませんでした。なぜかというと、名前をパスで検索していたからです。

このように、Animationで動かそうとする場合、他のボーン構造ではアクセスできなかったのです。しかし、ここにAvatarを使ってリターゲットしてやることによって、Avatarのなかに「右腕=〇〇だよ」という情報を持たせることができ、きちんと動かせるようになります。一応、これができるのはヒューマノイドという特殊なリグだけなんですが、そういうことができます。

マトリクスに直接書き込み

Mecanimの機能の2つ目ですが、直接マトリクスに書き込むことができます。

ややこしいですが、今までのUnityのアニメーションシステムは、基本的にGameObjectを経由してスキニングを行っていました。

よって、そのGameObjectのボーン構造に対して、このGameObjectを動かすと、腕が変形する、もしくは、その頂点が動くということだったんですが、そうしたことを無視して、直接ボーン構造というデータに対してAnimatorはアクセスすることができます。

これはパフォーマンス的にいいことです。GameObjectの構造をあまり制御せずに、それに対して直接アクセスできるので、1回クッションが消えますし、GameObjectの数も減らせます。

ちなみにこれは任意のため、GameObjectに対して相変わらず書き込むことができます。Dynamic Boneなど、揺れもののように追加でエフェクトを付ける場合には、GameObjectを経由してアクセスする必要があります。

もう1つ、Animationベースで移動させることができます。

例えば、転がるという動作に移動距離があった場合、それをスクリプトでやらせると面倒くさいことが多いんですが、それをAnimation側でやらせることができます。

なぜAnimation側で行うのかというと、足すべりを回避するのに便利だからです。

Animationの場合、DCCツールなどのいろいろなサポートが使えるので、普通にアニメーションさせた場合には、Animationベースで動かしたほうが、きれいで見やすくなります。

足すべりの場合は、スクリプトベースでやるのが悪いということではなく、見やすくなるという話です。いくつかのゲームでは、足すべりがすごいことがあるので(笑)。ダッシュしているのにすごく移動速度が遅かったり、歩いているのにすごく(速く)移動したりといったケースは減るかもしれません。

ただ、この機能を使うか使わないかはゲームの内容によります。

移動は、かなりゲーム性に直結してしまうので、本当にこれが必要なければ、使わないという選択肢もあるでしょう。とくに回転や振り向きなどは、これで動かすとあまりきれいに動かないことが多いです。

再生するべきClipを自動で判断

ややこしいことの1つですが、再生するClipを判断できます。

Animator Controllerと呼ばれている機能ですが、これを使って、再生すべきアニメーションを判断します。これはアセットとして管理されており、多くのキャラクターでこの判断するシステムを使い回すことができます。そのためにアセットになっています。

どういうことかというと、やりたいことや周辺の状況を表す時に、そのアニメーションが現在行うべきものを自分で選択して再生できる、ということを期待した機能です。

例えば、「移動速度がこのぐらい速いのか?」「向きたい方向はどうなっているのか?」という情報だったり、「そのキャラクターが今、地面に立っているのか?」「落下中なのか?」「落下速度は?」といったことや、「曲がる動きに対しての角度はどのぐらいだったのか?」という情報をもとに、行うべきアニメーションを自分で判断させることができます。

例えばこんな感じです。

基本的に、向きと角度と高さみたいなものしか取っていませんが、そうした情報を与えてやることによって、現在自分が行うべきアニメーションを、自分で判断して動いています。

ダッシュの角度や、ギュッと曲がる時に足を踏ん張って、そこから止まる動作などといったことをやらせています。

Blend Treeの機能

これを実現するための機能の1つ目がBlend Treeです。

このアニメーションを再生させるうえで、判断するのに便利なのがBlend Treeです。パラメーターを渡した時に、一番近いアニメーションを再生できます。

基本的には1軸もしくは2軸、つまりxかx/yか……実を言うと、n軸作ることができます。その軸の数だけパラメーターを動かせて、調節できます。

アニメーションが無条件に切り替わる、つまり、アニメーションの終了待ちをしないで、常に簡単に切り替わることができます。ダッシュと歩行とアイドルといったものを行う時には、Blend Treeが一番手っ取り早いというか、よいです。

これと、先ほどのAnimationの、自分自身で判定するという機能を両方使った場合で、ちょっとおもしろい設定を実行していきます。

設定の流れです。まず、キャラクターのAnimationClipをどんどん登録してあげます。軸のパラメーターの用途を、速度とアングルで渡します。そして、「速度と角速度」を設定すると、レイヤーが自動的にセットされて、パラメーターを渡すだけで、期待する速度で曲がってくれたり、移動するようになります。

これはEditorの画面です。

実際の動かし方としては、パラメーターを調整すると期待したとおりにアニメーションしてくれます。

パラメーターの設定方法

先ほど「パラメーターを調整する」と言いましたが、パラメーターをどう設定すればいいのかという話になります。このパラメーターは、当然と言えば当然なんですが、スクリプトで設定します。

残念ながら、ノードベースの何かはありません。基本的には、Animator.SetFloat(“key”, Value)という情報を渡すと、それを使って判断できるようになります。

MecanimのState Machineなどの機能を使って、パラメーターをその瞬間だけ渡すことが多いと思います。しかし実際には「常に全情報を入れておけ」というのが、Mecanimの発想の1つです。

このパラメーターを合わせるところは、StateMachineBehaviourを使うと、ある程度、渡す情報を簡略化できます。

StateMachineBehaviourが何かというと、とてもマイナーな機能です。

アニメーションの動きに関わる機能は、基本的にGameObjectからコンポーネントに渡すんですが、そうではなくて、Animator Controller自体にステートを渡す機能です。

例えば、Animator Controllerの全体に対してや、特定のステートに対してなど、そういったものに処理を加えることができます。それがステートが動いてるタイミングであったり、ステートがIN/OUTするタイミングであったり。これを使って設定してみると、こんな感じになります。 

例えば、これはAnimator Controllerにパラメーターを設定させるタイプのコードです。

座標をもらって、その座標から角度などを抽出して、それをパラメーターに渡すというコードを作りました。これはStateに入っている限り、毎フレーム行われます。

逆に、これはGameObject側で調整している場合のコードです。

どちらも座標を常に自分自身で更新し続けるだけです。毎フレーム、Updateで自分の情報を渡してやるという感じですね。ここでState Machine Behaviourの何がいいかというと、State Machineに関わってきます。

パラメーターのアニメーションにとって、そのパラメーターを更新する必要があるのかどうかは、けっこうややこしい問題です。例えば、ジャンプ中に地面との距離を見ておくのか。また、velocityが上がっているか下がっているかといった情報を更新する必要の有無は、ケースバイケースです。その状態によって更新が必要かどうかは変わります。

例えばジャンプ中だったら、落下中の速度によって、(両手を上げて)こういうふうに動くか、(両手を下げて)こういうふうにジャンプするかが切り替わると思います。これは地面を歩いているタイミングだったり、しゃがんで動いている時にはあまり必要のないことが多いです。

そんな時に、このState Machine Behaviourは、特定の状態に対してパラメーターを取得するのかどうかを選択するのは、ちょっといいアイデアです。まあ自分は、コンポーネントにたくさん処理を打ち込むのがイヤだ、というところなんですけど(笑)。

State Machineの基本機能

これはState Machineの基本機能です。

Blend Treeだと、どうしても表現しにくいいくつかの処理……例えば、アニメーションが切り替わるための条件がいくつかある場合や、切り替わるタイミングを厳密に調整したいといった場合があります。そんなときに使うのがState Machineです。Unityのチュートリアルでは、まずState Machineを紹介すると思いますが、どちらかというとこちらが後です。

State Machineを使う特殊な条件というのは、例えばコンバットモーションです。

アニメーションの終了時に次のアニメーションに行って、次のアニメーションに行く。例えば「そのアニメーションが終わった時にボタンが押されているか?」といった条件を使うことによって、そのステートを考えなくても実現できます。キャンセルがあったら、キャンセルも付けるという感じですね。こういった特定の条件で遷移する場合、State Machineは便利です。

もう1個、切り替わるタイミングがちょっとややこしいです。アニメーションが切り替わる時、そのアニメーションのタイミングによっては、うまく切り替わらない、ズレてしまうことがあります。

普通に走っている場合はきれいに動いてくれますが、アニメーションの切り替えタイミングがうまくいっていない場合は、足がたたらを踏むというか、不思議な挙動をします。

これはアニメーションの切り替えタイミングが、足のタイミングときっちり一致しているかどうかで変わります。これが一致しないと、うまく遷移してくれません。これは、AnimationのState Timeを使うことで、切り替わるタイミングやフェードの範囲を調整できます。

その切り替わるタイミングは、実は何ヶ所か設定することができます。

複数のHasExitの設定を使っているんですが、アニメーションが切り替わり始めた最初のタイミングでこの条件が満たされるので、例えば、右足をもとにダッシュに移行する場合は、そのタイミングでダッシュに移行するように微調整すればよく、左足からの場合は、そのタイミングをちょっとズラしてやることによって実現できます。

このあたりはコントロールが面倒くさいところではありますが、きちんと実現すると、ダッシュへの移行やアクションをきれいにつなぐことができます。これがState Machineの機能です。

複数の異なるAnimationがあった場合

こんな感じで頑張ってAnimatorControllerを作り込む訳ですが、よくある問題が、複数アニメーションするキャラクターがいた場合です。

これは実際にMecanimでいろいろ行った後に、問題になります。例えば大量のキャラクター向けに、State Machineをコピー&ペーストで大量に作ります。その後は挙動を少し変更するにも全部のStateMachineを書き換えなくてはならず、全部の挙動を変えるのは非常に面倒くさくなります。

アニメーションを何か1つ作って、他のものも全部変更するのはさすがにしんどいので、定義(StateMachine)だけは作って、中身(AnimationClip)は差し替えたいという希望があります。

例えば最初の段階で、普通にAnimationClip、Animator Controller、Animatorと、キャラクターを3体作りました。

これをAnimatorに突っ込むかわりに、1つをAnimator Override Controllerに突っ込みます。

そして、上書きしたAnimationに対してAnimationを普通に登録。Animator ControllerはそのままAnimatorに突っ込むかわりに、Animator Override Controllerに突っ込みます。そうすると、Animator Controllerに接続されているAnimationClipのかわりに、Animator Override Controllerに突っ込んだClipを使うことができます。

ですので、先ほど紹介したようにキャラクターが何体かいて、Animator Controllerが1個あって、その設定を使い回したい場合には、Animator Controller1個とキャラクター分のAnimator Override Controller、上書きするべきAnimationを設定すれば大丈夫です。

これを知らないと、Animator Controllerを大量に作って、調整するたびに非常にしんどい思いをすることになるので、ぜひとも覚えていただければと思います。