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 で流れるアニメーションストリームを修正できます。
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 は、ボーン(ジョイントとも呼ばれます)がエフェクターに対して向きを定める大変わかりやすい例です。以下の使用例では、これを 3D Game Kit パッケージに含まれる赤い四つ足のキャラクターに使用している様子をご覧いただけます。
TwoBoneIK は、3 つの連続したジョイント([例]人間の腕や脚)に適用できるシンプルな 2 ボーン IK アルゴリズムを実装しています。デモのキャラクターは一般的なヒューマノイドアバターです。
FullbodyIK の使用例では、ヒューマノイドアバターに含まれる値 ([例]Goal、Hint、Look At、Body Rotation など)の修正方法を確認いただけます。この例では、個別にアニメーション ストリームを手動で実装しています。
Damping は動物のしっぽや、髪型のポニーテールに適用できる減衰アルゴリズムを実装しています。このサンプルではプロシージャルアニメーションの生成方法をご確認いただけます。
SimpleMixer は、アニメーションミキサーにおける「Hello, World!」のようなものです。2 つの入力ストリーム([例]アニメーションクリップ)を取得し、ブレンディング値に基づいてミックスしてまとめます。AnimationMixerPlayable での場合と、まったく同じです。
WeightedMaskMixer は、もう少し高度なアニメーションミキサーの使用例です。細かく一つ一つのジョイントのブレンド具合を定義するウェイトマスクに基づき、2 つの入力ストリームを取得して、ミックスします。例えば、典型的な待機アニメーションを再生し、腕のアニメーションだけは別のアニメーションクリップから取得するということも可能です。あるいは、脊椎の骨へ適用するウェイトを徐々に高くすることで、上半身のアニメーションのブレンドを滑らかにしたりすることもできます。
Animation C# Jobs は Playable API によって機能しています。Animation C# Jobs には AnimationScriptPlayable、IAnimationJob、AnimationStream という 3 つの新しい構造体が含まれています。
AnimationScriptPlayable は、新しいアニメーション Playable で、他の Playable と同様に PlayableGraph 内のどこにでも追加することができます。注目すべきは、アニメーションジョブが含まれていることで、対象の PlayableGraph とジョブの間でプロキシとして動作します。このジョブはユーザー定義の構造体で、IAnimationJob を実装します。
標準的なジョブとして Playable 入力のストリームを処理して、結果をそのストリーム内にミックスします。アニメーション処理は以下の 2 つのパスに分けられ、各パスが IPlayableJob 内にそれぞれ、自身のコールバックを受けています。
以下の例は Animation C# Jobs における「Hello, World!」のようなものです。これはデモではありませんが、Animation job で AnimationScriptPlayable を作成する方法がご確認いただけます。
using UnityEngine; using UnityEngine.Playables; using UnityEngine.Animations; using UnityEngine.Experimental.Animations; public struct AnimationJob : IAnimationJob { public void ProcessRootMotion(AnimationStream stream) { } public void ProcessAnimation(AnimationStream stream) { } } [RequireComponent(typeof(Animator))] public class AnimationScriptExample : MonoBehaviour { PlayableGraph m_Graph; AnimationScriptPlayable m_ScriptPlayable; void OnEnable() { // グラフを作成する m_Graph = PlayableGraph.Create("AnimationScriptExample"); // Animation Job とその Playable を作成する var animationJob = new AnimationJob(); m_ScriptPlayable = AnimationScriptPlayable.Create(m_Graph, animationJob); // 出力を作成し、それを Playable にリンクさせる var output = AnimationPlayableOutput.Create(m_Graph, "Output", GetComponent()); output.SetSourcePlayable(m_ScriptPlayable); } void OnDisable() { m_Graph.Destroy(); } }
IAnimationJob メソッドのパラメーターとしてパスされているストリームを、それぞれの処理パス中に扱うことになります。
デフォルトでは、すべての AnimationScriptPlayable 入力が処理されます。入力が 1 つだけ(いわゆるポストプロセスジョブ)ならば、このストリームには処理された入力の結果が含まれることになります。入力が複数ある(つまりミックスジョブ)場合は、入力を手動で処理するほうが賢明です。これは AnimationScriptPlayable.SetProcessInputs(bool) メソッドで入力時に処理するパスの有効化・無効化を行うことができるようになります。
マニュアルモードで入力処理をトリガーに、結果として出るストリームを取得するには AnimationStream.GetInputStream() を呼び出します。
AnimationStream により、ある Playable から別の Playable へグラフで流れるデータに対してアクセスすることができます。Animator コンポーネントでアニメーション化されているすべての値にアクセスが可能です。
public struct AnimationStream { public bool isValid { get; } public float deltaTime { get; } public Vector3 velocity { get; set; } public Vector3 angularVelocity { get; set; } public Vector3 rootMotionPosition { get; } public Quaternion rootMotionRotation { get; } public bool isHumanStream { get; } public AnimationHumanStream AsHuman(); public int inputStreamCount { get; } public AnimationStream GetInputStream(int index); }
ストリームデータへ直接アクセスすることは不可能です。なぜなら同じデータでもフレームが移るごとに、ストリーム内では異なる Offset となりうる場合があるからです(例えば、グラフ内の AnimationClip の追加や削除があった場合など)。ストリーム内でデータがすでに移動されていたり、もう存在していない可能性があります。こういったアクセスを安定して有効に保つために、新しく「ストリームハンドル」と「シーンハンドル」の 2 つのハンドルのセットを導入しております。それぞれが Transform と Component プロパティハンドルを保持しています。
ストリームハンドルは、安全な方法で AnimationStream データへの全アクセスを扱います。エラーが起こるとシステムは C# の例外をスローします。ストリームハンドルは TransformStreamHandle と PropertyStreamHandle の 2 種類が存在しています。
TransformStreamHandle は Transform を管理し、Transform のヒエラルキーを処理します。つまり、ストリーム内でローカルまたはグローバルの Transform の位置を変更でき、さらに未来の位置リクエストに対しての予測結果を得ることができます。
PropertyStreamHandle は、他すべてのプロパティを管理します。システムは他のコンポーネントのデータを見つけたりアニメーションさせることが可能です。例えば、Light.m_Intensity プロパティの値の読み出しや書き込みという形で使用することができます。
public struct TransformStreamHandle { public bool IsValid(AnimationStream stream); public bool IsResolved(AnimationStream stream); public void Resolve(AnimationStream stream); public void SetLocalPosition(AnimationStream stream, Vector3 position); public Vector3 GetLocalPosition(AnimationStream stream); public void SetLocalRotation(AnimationStream stream, Quaternion rotation); public Quaternion GetLocalRotation(AnimationStream stream); public void SetLocalScale(AnimationStream stream, Vector3 scale); public Vector3 GetLocalScale(AnimationStream stream); public void SetPosition(AnimationStream stream, Vector3 position); public Vector3 GetPosition(AnimationStream stream); public void SetRotation(AnimationStream stream, Quaternion rotation); public Quaternion GetRotation(AnimationStream stream); } public struct PropertyStreamHandle { public bool IsValid(AnimationStream stream); public bool IsResolved(AnimationStream stream); public void Resolve(AnimationStream stream); public void SetFloat(AnimationStream stream, float value); public float GetFloat(AnimationStream stream); public void SetInt(AnimationStream stream, int value); public int GetInt(AnimationStream stream); public void SetBool(AnimationStream stream, bool value); public bool GetBool(AnimationStream stream); }
シーンハンドルもすべての値に安全にアクセスできる方法のひとつですが、これを AnimationStream からではなく、シーンから行います。ストリームハンドルのように、シーンハンドルにも TransformSceneHandle と PropertySceneHandle の 2 種類があります。
シーンハンドルの堅実な使用法としては、Foot IK 用エフェクターの実装です。IK エフェクターは通常、アニメーターでアニメーションされないゲームオブジェクトです。そのため、PlayableGraph 内のアニメーションクリップによって修正される Transform では外部にあたります。ジョブとしては、望ましい足の位置を計算するために IK エフェクターのグローバルポジション情報が必要になります。このためストリームハンドルがレッグボーンに使用される一方で、IK エフェクターへはシーンハンドル経由でアクセスされることとなります。
public struct TransformSceneHandle { public bool IsValid(AnimationStream stream); public void SetLocalPosition(AnimationStream stream, Vector3 position); public Vector3 GetLocalPosition(AnimationStream stream); public void SetLocalRotation(AnimationStream stream, Quaternion rotation); public Quaternion GetLocalRotation(AnimationStream stream); public void SetLocalScale(AnimationStream stream, Vector3 scale); public Vector3 GetLocalScale(AnimationStream stream); public void SetPosition(AnimationStream stream, Vector3 position); public Vector3 GetPosition(AnimationStream stream); public void SetRotation(AnimationStream stream, Quaternion rotation); public Quaternion GetRotation(AnimationStream stream); } public struct PropertySceneHandle { public bool IsValid(AnimationStream stream); public bool IsResolved(AnimationStream stream); public void Resolve(AnimationStream stream); public void SetFloat(AnimationStream stream, float value); public float GetFloat(AnimationStream stream); public void SetInt(AnimationStream stream, int value); public int GetInt(AnimationStream stream); public void SetBool(AnimationStream stream, bool value); public bool GetBool(AnimationStream stream); }
最後は AnimationJobExtension クラスです。すべてを動くようにする接着剤のような働きをします。4 つのメソッド(BindStreamTransform、BindStreamProperty、BindSceneTransform、BindSceneProperty)を使用するために、これまでご紹介した 4 種類のハンドルを作成できるように、Animator を拡張します。
public static class AnimatorJobExtensions { public static TransformStreamHandle BindStreamTransform(this Animator animator, Transform transform); public static PropertyStreamHandle BindStreamProperty(this Animator animator, Transform transform, Type type, string property); public static TransformSceneHandle BindSceneTransform(this Animator animator, Transform transform); public static PropertySceneHandle BindSceneProperty(this Animator animator, Transform transform, Type type, string property); }
「BindStream」メソッドは、ストリーム内ですでにアニメーション化されたプロパティや新規にアニメーション化されたプロパティのハンドルを作成するため使えます。
API に関するドキュメンテーション
バグを発見された場合は、Unity 組み込みのバグ報告アプリからご報告ください。
この実験的機能に関するフィードバックは、フォーラム「Animation C# jobs in 2018.2a5」からお寄せください。
API に関するドキュメンテーション
バグを発見された場合は、Unity 組み込みのバグ報告アプリからご報告ください。
この実験的機能に関するフィードバックは、フォーラム「Animation C# jobs in 2018.2a5」からお寄せください。
Is this article helpful for you?
Thank you for your feedback!