Search Unity

Unity 2018.2 では、Unity 2018.1 リリースの C# Job System 内のアニメーション Playable が機能拡張されました。Animation C# Jobs を使用すると、アニメーションシステムを実装時に自由で独創的なソリューションを展開できるだけでなく、同時に安全なマルチスレッドコードによってパフォーマンスを向上させることができます。Animation C# Jobs は低レベルの API ということもあり、使用にあたっては Playable API についての厳密な理解が必要となります。そのため Animation C# Jobs は、Unity のアニメーションシステムを既成の枠を超えて拡張したいデベロッパー向けの機能となります。その上でご興味あるとのことでしたら、ぜひ本記事から「どうすれば最大限に Animation C# Jobs を活用できるのか」「いつがその使いどころなのか」を探してみてください!

Animation C# Jobs では、C# コードを記述してそれを PlayableGraph 内のユーザー定義の場所で実行させることができます。そして C# Job System の機能により、昨今のマルチコアハードウェアの持つ能力を引き出すことが可能となります。さらにメインスレッドの C# スクリプトの負荷が非常に高くなることが見込まれるプロジェクトの場合は、その負荷の高いアニメーションタスクの一部を並列処理にすることも可能で、これは有効なパフォーマンスを取得するための新たな一歩となりうるものです。この改善により、ユーザーが作成した C# をスクリプトで PlayableGraph で流れるアニメーションストリームを修正できます。

機能

  • 新しい Playable ノード:AnimationScriptPlayable
  • PlayableGraph 内のアニメーションデータ ストリームの制御
  • マルチスレッド C# コード

免責事項

Animation C# Jobs は、まだ実験的な機能です(この機能は UnityEngine.Experimental.Animations 内にあります)。この API には、随時皆様のフィードバック次第で細かく変更が加えられる可能性があります。ぜひAnimation フォーラムでの意見交換にご参加ください!

ユースケース

例えば、新しいドラゴンのキャラクターの足を固定する機能が欲しいとしましょう。これは通常の MonoBehaviour で作成することもできますが、すべてのコードはメインスレッドで、アニメーションパスが終了してから実行されることになります。Animation C# Jobs を使えば独自のアルゴリズムで記述でき、それを PlayableGraph 内のカスタムした Playable ノードへ直接使うことができる上、そのコードは別スレッドとして、Playable Graph 処理中に実行されるようになります。

あるいは、ドラゴンのしっぽをアニメーション化したくない場合、その動きをプロシージャルに計算できるように設定するツールとしても Animation C# Jobs はもってこいです。

また Animation C# Jobs を使うと、とびきり緻密で具体的な LookAt アルゴリズムも記述することもできます。「ドラゴンの首の中の 10 個のボーンをターゲットとする」などがその例となります。

さらなるユースケースの好例としては、独自のアニメーションミキサーを作成できることです。例えば「ある入力から位置を取得するノード、別の入力により回転するノード、そして 3 つ目のノードでスケール。そして、単一のアニメーションストリーム内でそれらをすべてミックス」という、かなり具体的なものが必要だとしましょう。このような場合でも、Animation C# Jobs を使えば創造的に、具体的な要求に応じた構築ができます。

使用例

実際に Animation C# Jobs API 使用についての説明の海に飛び込む前に、ちょっとこの機能でどんなことができるか、いくつか並べてみましたので見てみましょう。

使用例はすべて Animation Jobs Samples の GitHub ページで利用できます。インストールは、git clone するか、最新版をダウンロードする形で行ってください。インストールが完了されると、すべて「Scenes」ディレクトリ内で、以下のような個別のシーンを使用例としてご確認いただけるようになります。

LookAt

LookAt は、ボーン(ジョイントとも呼ばれます)がエフェクターに対して向きを定める大変わかりやすい例です。以下の使用例では、これを 3D Game Kit パッケージに含まれる赤い四つ足のキャラクターに使用している様子をご覧いただけます。

TwoBoneIK

TwoBoneIK は、3 つの連続したジョイント([例]人間の腕や脚)に適用できるシンプルな 2 ボーン IK アルゴリズムを実装しています。デモのキャラクターは一般的なヒューマノイドアバターです。

FullbodyIK

FullbodyIK の使用例では、ヒューマノイドアバターに含まれる値 ([例]Goal、Hint、Look At、Body Rotation など)の修正方法を確認いただけます。この例では、個別にアニメーション ストリームを手動で実装しています。

Damping

Damping は動物のしっぽや、髪型のポニーテールに適用できる減衰アルゴリズムを実装しています。このサンプルではプロシージャルアニメーションの生成方法をご確認いただけます。

SimpleMixer

SimpleMixer は、アニメーションミキサーにおける「Hello, World!」のようなものです。2 つの入力ストリーム([例]アニメーションクリップ)を取得し、ブレンディング値に基づいてミックスしてまとめます。AnimationMixerPlayable での場合と、まったく同じです。

WeightedMaskMixer

WeightedMaskMixer は、もう少し高度なアニメーションミキサーの使用例です。細かく一つ一つのジョイントのブレンド具合を定義するウェイトマスクに基づき、2 つの入力ストリームを取得して、ミックスします。例えば、典型的な待機アニメーションを再生し、腕のアニメーションだけは別のアニメーションクリップから取得するということも可能です。あるいは、脊椎の骨へ適用するウェイトを徐々に高くすることで、上半身のアニメーションのブレンドを滑らかにしたりすることもできます。

API

Animation C# Jobs は Playable API によって機能しています。Animation C# Jobs には AnimationScriptPlayable、IAnimationJob、AnimationStream という 3 つの新しい構造体が含まれています。

AnimationScriptPlayable と IAnimationJob

AnimationScriptPlayable は、新しいアニメーション Playable で、他の Playable と同様に PlayableGraph 内のどこにでも追加することができます。注目すべきは、アニメーションジョブが含まれていることで、対象の PlayableGraph とジョブの間でプロキシとして動作します。このジョブはユーザー定義の構造体で、IAnimationJob を実装します。

標準的なジョブとして Playable 入力のストリームを処理して、結果をそのストリーム内にミックスします。アニメーション処理は以下の 2 つのパスに分けられ、各パスが IPlayableJob 内にそれぞれ、自身のコールバックを受けています。

  1. ProcessRootMotion は Root Transform のモーションを扱います。常に ProcessAnimation の前に呼び出され、ProcessAnimation を呼び出すべきかを決定します(Animator のカリングモードの設定によって異なります)
  2. ProcessAnimation はルートモーション以外をすべてを処理します。

以下の例は Animation C# Jobs における「Hello, World!」のようなものです。これはデモではありませんが、Animation job で AnimationScriptPlayable を作成する方法がご確認いただけます。

IAnimationJob メソッドのパラメーターとしてパスされているストリームを、それぞれの処理パス中に扱うことになります。

デフォルトでは、すべての AnimationScriptPlayable 入力が処理されます。入力が 1 つだけ(いわゆるポストプロセスジョブ)ならば、このストリームには処理された入力の結果が含まれることになります。入力が複数ある(つまりミックスジョブ)場合は、入力を手動で処理するほうが賢明です。これは AnimationScriptPlayable.SetProcessInputs(bool) メソッドで入力時に処理するパスの有効化・無効化を行うことができるようになります。
マニュアルモードで入力処理をトリガーに、結果として出るストリームを取得するには AnimationStream.GetInputStream() を呼び出します。

AnimationStream とハンドル

AnimationStream により、ある Playable から別の Playable へグラフで流れるデータに対してアクセスすることができます。Animator コンポーネントでアニメーション化されているすべての値にアクセスが可能です。

ストリームデータへ直接アクセスすることは不可能です。なぜなら同じデータでもフレームが移るごとに、ストリーム内では異なる Offset となりうる場合があるからです(例えば、グラフ内の AnimationClip の追加や削除があった場合など)。ストリーム内でデータがすでに移動されていたり、もう存在していない可能性があります。こういったアクセスを安定して有効に保つために、新しく「ストリームハンドル」と「シーンハンドル」の 2 つのハンドルのセットを導入しております。それぞれが Transform と Component プロパティハンドルを保持しています。

ストリームハンドル

ストリームハンドルは、安全な方法で AnimationStream データへの全アクセスを扱います。エラーが起こるとシステムは C# の例外をスローします。ストリームハンドルは TransformStreamHandle と PropertyStreamHandle の 2 種類が存在しています。

TransformStreamHandle は Transform を管理し、Transform のヒエラルキーを処理します。つまり、ストリーム内でローカルまたはグローバルの Transform の位置を変更でき、さらに未来の位置リクエストに対しての予測結果を得ることができます。

PropertyStreamHandle は、他すべてのプロパティを管理します。システムは他のコンポーネントのデータを見つけたりアニメーションさせることが可能です。例えば、Light.m_Intensity プロパティの値の読み出しや書き込みという形で使用することができます。

シーンハンドル

シーンハンドルもすべての値に安全にアクセスできる方法のひとつですが、これを AnimationStream からではなく、シーンから行います。ストリームハンドルのように、シーンハンドルにも TransformSceneHandle と PropertySceneHandle の 2 種類があります。

シーンハンドルの堅実な使用法としては、Foot IK 用エフェクターの実装です。IK エフェクターは通常、アニメーターでアニメーションされないゲームオブジェクトです。そのため、PlayableGraph 内のアニメーションクリップによって修正される Transform では外部にあたります。ジョブとしては、望ましい足の位置を計算するために IK エフェクターのグローバルポジション情報が必要になります。このためストリームハンドルがレッグボーンに使用される一方で、IK エフェクターへはシーンハンドル経由でアクセスされることとなります。

AnimationJobExtensions

最後は AnimationJobExtension クラスです。すべてを動くようにする接着剤のような働きをします。4 つのメソッド(BindStreamTransform、BindStreamProperty、BindSceneTransform、BindSceneProperty)を使用するために、これまでご紹介した 4 種類のハンドルを作成できるように、Animator を拡張します。

「BindStream」メソッドは、ストリーム内ですでにアニメーション化されたプロパティや新規にアニメーション化されたプロパティのハンドルを作成するため使えます。

参考資料

API に関するドキュメンテーション

バグを発見された場合は、Unity 組み込みのバグ報告アプリからご報告ください。

この実験的機能に関するフィードバックは、フォーラム「Animation C# jobs in 2018.2a5」からお寄せください。

コメント受付を終了しました。

  1. Hello

    Are you going to visually upgrade animation timeline ? I mean, in the current version we have all keyframes in the same color, shape and (up down) lines are very close to eachother. Work flow is very very weak compared to this in the Spine. In the Spine we have colors for each key frame’s type, scaling is red, transform blue etc. What also is importing, filtering would be a great idea, when I click on the texture I could see only this texture’s key frames, not all. Hope that timeline will be upgraded because currently really slow down work flow and new features are not so wonderful when we cant use them in the work flow enought.

    Regards,
    Michał

    1. Romain Failliot

      9月 15, 2018 5:17 am

      Hi Michał,

      We are actually working on it, but I can’t really say more here since it is not really related to this blog post. If you would like to know more, or if you would like to express your ideas, I encourage you to use our animation forums: https://forum.unity.com/forums/animation.52/

      Thank you ;)
      Romain

  2. Aubrey Hesselgren

    8月 30, 2018 5:31 pm

    Really great to have some examples. I’ve been trying some things with this, and so it’s great to have something to sanity check against.

    I’d love to see more about how we are supposed to use the Root Motion aspect of this. I’ve been using OnAnimatorMove() to receive the output of root animation on a generic rig, from our own custom graph, but any time a loop occurs in an AnimationClipPlayable, the delta I’m given jumps me back (i.e. it’s not noticing the loop, and thus doesn’t). I wonder if this is something you intended the AnimationStream to allow you to fix? And if so, how should I go about signalling that an AnimationClipPlayable has looped this frame? Should I have one of these animation scripts wrapping every AnimationClipPlayable? Or is this simply a bug?

    1. Aubrey Hesselgren

      8月 30, 2018 10:13 pm

      Please disregard my last post. It seems that the issues has been patched in a recent update. Phew.

      Anyway. Very happy about these extra docs. Thank you.

  3. Really great article! Lots of examples and useful information.

  4. Isaac Surfraz

    8月 28, 2018 1:22 pm

    “Romain Failliot”, I just wanted to thank you for a great blog post. Animation C# Jobs was a feature that was in dire need of some better explanation, and I think your blog post was a great mix of useful “laymans” translations of what things do, some conceptual ideas of what one may want to do with it, and actual examples. Was not too long or short a read either! Keep up the great articles, hopefully you will post some more!

    1. Romain Failliot

      8月 28, 2018 4:00 pm

      Thank you! That’s really appreciated ;)