更新履歴
- 20180711 2018.2.0リリース。ちょっと追記
- 20180523 書いた
概要
で、Animation Jobs C# APIと紹介されているものです。
あと、Unity 2017.1 feature spotlight: Playable APIの後半のUnity2017Europeの動画より後ろに書いてある。
動かしてみた
まだExperimentalなので変わるだろうけど、とりあえず動かしてみた(Unity2018.2.0b4)。
ドキュメントに書いてあるものを少し改造して、TwoBoneIKの上流にAnimationClipのPlayableを連結したらうまくいった。
AnimationClip => TwoBoneIK => Animator
ソース
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;
using UnityEngine.Experimental.Animations;
using System;
public struct TwoBoneIKJob : IAnimationJob
{
public float time;
public TransformSceneHandle effector;
public TransformStreamHandle top;
public TransformStreamHandle mid;
public TransformStreamHandle low;
Vector3 topT;
Vector3 midT;
Vector3 lowT;
Quaternion topQ;
Quaternion midQ;
Quaternion lowQ;
public void Setup(Animator animator, Transform topX, Transform midX, Transform lowX, Transform effectorX)
{
top = animator.BindStreamTransform(topX);
mid = animator.BindStreamTransform(midX);
low = animator.BindStreamTransform(lowX);
effector = animator.BindSceneTransform(effectorX);
}
public void ProcessRootMotion(AnimationStream stream)
{
}
public void ProcessAnimation(AnimationStream stream)
{
Solve(stream, top, mid, low, effector.GetPosition(stream));
}
/// <summary>
/// Returns the angle needed between v1 and v2 so that their extremities are
/// spaced with a specific length.
/// </summary>
/// <returns>The angle between v1 and v2.</returns>
/// <param name="aLen">The desired length between the extremities of v1 and v2.</param>
/// <param name="v1">First triangle edge.</param>
/// <param name="v2">Second triangle edge.</param>
private static float TriangleAngle(float aLen, Vector3 v1, Vector3 v2)
{
float aLen1 = v1.magnitude;
float aLen2 = v2.magnitude;
float c = Mathf.Clamp((aLen1 * aLen1 + aLen2 * aLen2 - aLen * aLen) / (aLen1 * aLen2) / 2.0f, -1.0f, 1.0f);
return Mathf.Acos(c);
}
private static void Solve(AnimationStream stream, TransformStreamHandle topHandle, TransformStreamHandle midHandle, TransformStreamHandle lowHandle, Vector3 effector)
{
Quaternion aRotation = topHandle.GetRotation(stream);
Quaternion bRotation = midHandle.GetRotation(stream);
Quaternion cRotation = lowHandle.GetRotation(stream);
Vector3 aPosition = topHandle.GetPosition(stream);
Vector3 bPosition = midHandle.GetPosition(stream);
Vector3 cPosition = lowHandle.GetPosition(stream);
Vector3 ab = bPosition - aPosition;
Vector3 bc = cPosition - bPosition;
Vector3 ac = cPosition - aPosition;
Vector3 ad = effector - aPosition;
float oldAbcAngle = TriangleAngle(ac.magnitude, ab, bc);
float newAbcAngle = TriangleAngle(ad.magnitude, ab, bc);
Vector3 axis = Vector3.Cross(ab, bc).normalized;
float a = 0.5f * (oldAbcAngle - newAbcAngle);
float sin = Mathf.Sin(a);
float cos = Mathf.Cos(a);
Quaternion q = new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos);
Quaternion worldQ = q * bRotation;
midHandle.SetRotation(stream, worldQ);
aRotation = topHandle.GetRotation(stream);
cPosition = lowHandle.GetPosition(stream);
ac = cPosition - aPosition;
Quaternion fromTo = Quaternion.FromToRotation(ac, ad);
topHandle.SetRotation(stream, fromTo * aRotation);
lowHandle.SetRotation(stream, cRotation);
}
}
[Serializable]
struct TwoBoneIKPlayableSource
{
public Transform effector;
public Transform shoulder;
public Transform elbow;
public Transform hand;
public AnimationScriptPlayable CreateSource(PlayableGraph graph, Animator animator)
{
var twoBoneIKJob = new TwoBoneIKJob();
twoBoneIKJob.Setup(animator, shoulder, elbow, hand, effector);
return AnimationScriptPlayable.Create(graph, twoBoneIKJob,
1 // 入力口を1に変更。上流にPlayableを連結する
);
}
}
public class TwoBoneIK : MonoBehaviour
{
[SerializeField]
TwoBoneIKPlayableSource Source;
[SerializeField]
AnimationClip m_clip;
private void BuildGraph(Animator animator)
{
var output = AnimationPlayableOutput.Create(m_Graph, "output", animator);
var source = Source.CreateSource(m_Graph, animator);
output.SetSourcePlayable(source);
var clipPlayable = AnimationClipPlayable.Create(m_Graph, m_clip);
m_Graph.Connect(clipPlayable, 0, source, 0); // TowBoneIKの入力にclipPlayableを連結
}
PlayableGraph m_Graph;
void OnEnable()
{
m_Graph = PlayableGraph.Create("TwoBoneIK");
BuildGraph(GetComponent<Animator>());
m_Graph.Play();
}
void OnDisable()
{
m_Graph.Destroy();
}
}
どんなことができそうか
ドキュメントには以下のような用途が示されている。
- 2-Bone IK
- Full body IK
- Custom mixers
- Tail or Pony Tail
既存のPlayableはグラフを流れてくるデータを操作する方法が無かったのでミキサー操作以外にすることが無いように思えたのだけど、IAnimationJob経由でAnimationStreamを操作してデータ生成・変更がきるようになった。PlayableAPI始まったのではないかと期待。
Googleで検索したらサンプルを発見しました(20180711)。