1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Unity2018.2のIAnimationJob

Last updated at Posted at 2018-05-20

更新履歴

  • 20180711 2018.2.0リリース。ちょっと追記
  • 20180523 書いた

概要

で、Animation Jobs C# APIと紹介されているものです。

あと、Unity 2017.1 feature spotlight: Playable APIの後半のUnity2017Europeの動画より後ろに書いてある。

動かしてみた

まだExperimentalなので変わるだろうけど、とりあえず動かしてみた(Unity2018.2.0b4)。

animjob.png

ドキュメントに書いてあるものを少し改造して、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)。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?