UnityのAnimation RiggingでIK/FK切り替えスライダ付きのTwo Bone IKコンストレイントを作成する

はじめに

UnityのAnimation Riggingにはじめから付いているTwo Bone IKが使いにくかったので、改良したコンストレイントを作成しました。

前回はかんたんなコンストレイントを作るだけでしたが今回は実用的なものです。

  • Unity: 2019.3.0b11
  • Animation Rigging: 0.2.3-preview

モデルの準備

Make Humanで適当にボーンの入ったモデルを用意しました。

2019 11 20 18 26 56

標準のTwo Bone IKを使ってみる

まずは標準で用意されているTwo Bone IKを利用してみます。

リグの構築

Unityの新しいプロジェクトを立ち上げます。 前回と同様の手順でAnimation Riggingをpackage managerからインストールします。

シーンにモデルを配置しました。

2019 11 20 18 36 47

2019 11 20 18 36 55

モデルのルートにAnimatorとRig Builderをアタッチします。

2019 11 20 18 38 28

モデルのルートの下に空のGameObjectを作成しControlRigと名付けます。

2019 11 20 18 39 44

RigコンポーネントをControlRigにアタッチし、Rig Builderに登録します。

2019 11 20 18 40 23

2019 11 20 18 40 38

ControlRigの下に空のGameObjectを作成しLeftArmと名付けます。

2019 11 20 18 41 32

LeftArmの下にGameObjectを2つ作り、それぞれLeftArmtargetCNTRL、LeftArmhintCNTRLと名付けます。

2019 11 20 18 42 53

LeftArmにTwo Bone IK Constraintをアタッチします。 Root、mid、tipにそれぞれ上腕、下腕、手のボーンをアタッチします。 Source ObjectsのTargetとHintにさきほど作成したCNTRLオブジェクトを割り当てます。

2019 11 20 18 45 14

LeftArmtargetCNTRLとLeftArmhintCNTRLをそれぞれ適切な位置に移動し、エフェクターを設定します。

2019 11 20 18 46 56

Two Bone IK ConstraintのSettingsでNoneからPosition and Rotationに変更します。

2019 11 20 18 49 03

うごかしてみる

Playモードに入ってTargetやHintを適当に動かすとIKが働いているのがわかります。

まずは通常のボーンのアニメーションを作ってみます。 モデルのルートを選択してAnimationウィンドウからCreateします。

2019 11 20 18 51 18

左腕の上腕、下腕、手、腰のボーンを追加しました。

2019 11 20 19 00 20

アニメーションを付けるためにいったんRigの重みをオフにします。

2019 11 20 19 01 12

適当に腕をふるアニメーションを付けました。

2019 11 20 19 01 35

Rigの重みを1に戻します。

2019 11 20 19 02 35

この状態で再生してみると次のようになります。

アニメーションが再生されたあとでIKの計算が追加でなされていることがわかります。

次にRigのアニメーションを用意します。 New Clipを作成します。

2019 11 20 19 04 53

CNTRLオブジェクトを登録します。

2019 11 20 19 05 55

適当なアニメーションを作成します。

2019 11 20 19 06 39

Animatorウィンドウから新しいレイヤーを追加します。

2019 11 20 19 07 44

新しいレイヤーにRigのアニメーションを与えます。

2019 11 20 19 08 12

レイヤーのウエイトを1にします。

2019 11 20 19 09 14

これでPlayモードに入ると次のようになります。

通常のアニメーションにIKのアニメーションをブレンドできました。

このようにRig用のLayerを用意してアニメーションさせるのはUnityが考えている使い方のようです。

次のリポジトリでは忍者がラーメンを食べるアニメーションがRig Overrideレイヤーで実現されていました。

Unity-Technologies/animation-rigging-workshop-siggraph2019: Repository for SIGGRAPH 2019 Animation Rigging workshop

次のブログ記事でもRig用のレイヤーを用意していました。

高度な Animation Rigging:キャラクターとプロップのインタラクション – Unity Blog

問題点

このような形は、あらかじめモーションがあって、それに追加でIKを与えてプロシージャルにモーションを作る場合にはとても有効です。

しかし、アニメーションをいちからUnity上で作成したい場合、FKとIKを切り替えるためにレイヤーごとバラバラでアニメーションを作っていちいちRigのウエイトをオフにして、と操作するのは面倒です。 IKが向いているアニメーションとFKが向いているアニメーションというものがあり、アニメーションを作成する際にはそれらを切り替えブレンドしながら作業をしていくというのがよくあります。 よくあるリグのようにIK/FKの切り替えスライダのようなものを用意したくなります。

標準のTwo Bone IKではこの要求に答えてくれません。 後付でIKを与えるという用途に特化しています。

そこで今回はIK/FK切り替えスライダ付きのTwo Bone IKコンストレイントを作成していくことにします。

IK/FK切り替えスライダ付きのTwo Bone IKコンストレイント

Deform Boneに直接アニメーションを書き込むのはコントロールリグとでフォームボーンを分離するという観点から気持ちが悪いので、IKのCNTRLの他にFKのCNTRLオブジェクトも用意することにします。

新しいC#のスクリプトを作成し、「TwoBoneIKFKConstraint」という名前にします。

2019 11 20 19 25 12

TwoBoneIKFKConstraint.csに次のように記述します。

using Unity.Burst;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Animations.Rigging;

[DisallowMultipleComponent, AddComponentMenu ("Animation Rigging/Custom/Two Bone IK FK Constraint")]
public class TwoBoneIKFKConstraint : RigConstraint<TwoBoneIKFKConstraintJob, TwoBoneIKFKConstraintData, TwoBoneIKFKConstraintBinder> { }

[BurstCompile]
public struct TwoBoneIKFKConstraintJob : IWeightedAnimationJob {

    public ReadWriteTransformHandle Root;
    public ReadWriteTransformHandle Mid;
    public ReadWriteTransformHandle Tip;

    public ReadOnlyTransformHandle IK_Target;
    public ReadOnlyTransformHandle IK_Hint;

    public ReadOnlyTransformHandle FK_Root;
    public ReadOnlyTransformHandle FK_Mid;
    public ReadOnlyTransformHandle FK_Tip;

    public ReadWriteTransformHandle Slider;

    public Vector2 LinkLengths;

    public FloatProperty jobWeight { get; set; }

    public void ProcessRootMotion (AnimationStream stream) { }

    public void ProcessAnimation (AnimationStream stream) {
        float w = jobWeight.Get (stream);

        var sliderPos = Slider.GetLocalPosition (stream);
        var t = Mathf.Clamp01 (sliderPos.y);
        Slider.SetLocalPosition (stream, new Vector3 (0, t, 0));

        if (w > 0f) {
            var rootRot = Root.GetRotation (stream);
            var midRot = Mid.GetRotation (stream);
            var tipRot = Tip.GetRotation (stream);

            var rootRotFK = Quaternion.Lerp (rootRot, FK_Root.GetRotation (stream), w);
            var midRotFK = Quaternion.Lerp (midRot, FK_Mid.GetRotation (stream), w);
            var tipRotFK = tipRot;

            AnimationRuntimeUtils.SolveTwoBoneIK (
                stream, Root, Mid, Tip, IK_Target, IK_Hint,
                posWeight : 1f * w,
                rotWeight : 0 * w,
                hintWeight : 1f * w,
                limbLengths : LinkLengths,
                targetOffset : AffineTransform.identity
            );
            var rootRotIK = Root.GetRotation (stream);
            var midRotIK = Mid.GetRotation (stream);
            var tipRotIK = Tip.GetRotation (stream);

            Root.SetRotation (stream, Quaternion.Lerp (rootRotFK, rootRotIK, t));
            Mid.SetRotation (stream, Quaternion.Lerp (midRotFK, midRotIK, t));
            Tip.SetRotation (stream, Quaternion.Lerp (tipRotFK, tipRotIK, t));
        }
    }
}

[System.Serializable]
public struct TwoBoneIKFKConstraintData : IAnimationJobData {

    public Transform Root;
    public Transform Mid;
    public Transform Tip;

    [SyncSceneToStream] public Transform IK_Target;
    [SyncSceneToStream] public Transform IK_Hint;

    [SyncSceneToStream] public Transform FK_Root;
    [SyncSceneToStream] public Transform FK_Mid;

    [SyncSceneToStream] public Transform Slider;

    public bool IsValid () => !(Tip == null || Mid == null || Root == null || IK_Target == null || FK_Root == null || FK_Mid == null || Slider == null);

    public void SetDefaultValues () {
        Root = null;
        Mid = null;
        Tip = null;
        IK_Target = null;
        IK_Hint = null;
        FK_Root = null;
        FK_Mid = null;
        Slider = null;
    }
}

public class TwoBoneIKFKConstraintBinder : AnimationJobBinder<TwoBoneIKFKConstraintJob, TwoBoneIKFKConstraintData> {

    public override TwoBoneIKFKConstraintJob Create (Animator animator, ref TwoBoneIKFKConstraintData data, Component component) {
        var job = new TwoBoneIKFKConstraintJob ();

        job.Root = ReadWriteTransformHandle.Bind (animator, data.Root);
        job.Mid = ReadWriteTransformHandle.Bind (animator, data.Mid);
        job.Tip = ReadWriteTransformHandle.Bind (animator, data.Tip);

        job.IK_Target = ReadOnlyTransformHandle.Bind (animator, data.IK_Target);
        if (data.IK_Hint != null)
            job.IK_Hint = ReadOnlyTransformHandle.Bind (animator, data.IK_Hint);

        job.FK_Root = ReadOnlyTransformHandle.Bind (animator, data.FK_Root);
        job.FK_Mid = ReadOnlyTransformHandle.Bind (animator, data.FK_Mid);

        job.Slider = ReadWriteTransformHandle.Bind (animator, data.Slider);

        job.LinkLengths[0] = Vector3.Distance (data.Root.position, data.Mid.position);
        job.LinkLengths[1] = Vector3.Distance (data.Mid.position, data.Tip.position);

        return job;
    }

    public override void Destroy (TwoBoneIKFKConstraintJob job) { }
}

リグの構築

さきほどのキャラクターは削除してしまいます。

2019 11 20 19 23 14

あらたにリグの組まれていないまっさらなモデルを配置し直します。

2019 11 20 19 23 51

モデルのルートにAnimatorとRig Builderをアタッチします。

2019 11 21 12 41 56

モデルのルートの下にControlRigと名付けたGameObjectを配置します。

2019 11 21 12 42 55

ControlRigにRigコンポーネントをつけて、Rig Builderに登録します。

2019 11 21 12 43 34

2019 11 21 12 43 45

ControlRigの下にLeftArmと名付けたGameObjectを生成します。

2019 11 21 12 44 35

LeftArmの下にIKTargetpivotと名付けたGameObjectを作成し、左手のあたりに配置します。

2019 11 21 12 54 14

2019 11 21 12 54 58

2019 11 21 12 55 08

IKTargetpivotの下にIKTargetCNTRLと名付けたGameObjectを配置します。

2019 11 21 12 55 59

pivotの下にCNTRLを配置するのには理由があります。 pivotを用意しなくてもリグは問題なく動くでしょう。 しかし、pivotがない場合、デフォルト位置が中途半端な位置になります。

2019 11 21 12 56 58

アニメーションを付ける最中にデフォルトの位置へ戻したくなったときに戻すのが難しくなります。

pivotをデフォルト位置において、pivotの下にCNTRLを配置すれば、CNTRLの(0, 0, 0)がデフォルトの位置や回転となりデフォルトに戻したくなったら0を入力するだけで済みます。

2019 11 21 12 58 55

次にLeftArmの下にIKHintpivotと名付けたGameObjectを作成し、左肘の後ろの方に配置します。

2019 11 21 13 00 39

2019 11 21 13 00 47

2019 11 21 13 00 55

IKHintpivotの下にIKHintCNTRLと名付けたGameObjectを配置します。

2019 11 21 13 02 04

次にLeftArmの下にFKRootpivotと名付けたGameObjectを作成します。

2019 11 21 13 03 01

FKRootpivotを選択しCtrlキーを押しながらモデルの上腕のボーン(ここではupperarm_l)を選択します。 選択する順番も重要です。

2019 11 21 13 04 07

選択をしたら「Animation Rigging > Align Transform」を実行します。

2019 11 21 13 04 41

これでFKRootpivotが腕のボーンに揃いました。

2019 11 21 13 05 14

FKRootpivotの下にFKRootCNTRLと名付けたGameObjectを配置します。

2019 11 21 13 06 39

次にFKRootCNTRLの下にFKMidpivotと名付けたGameObjectを作成します。

2019 11 21 13 07 27

FKMidpivotを選択しCtrlキーを押しながらモデルの下腕のボーン(ここではlowerarm_l)を選択します。選択する順番も重要です。

2019 11 21 13 08 21

Align Transformを実行します。これでFKMidpivotが腕のボーンに揃いました。

2019 11 21 13 09 08

FKMidpivotの下にFKMidCNTRLと名付けたGameObjectを配置します。

2019 11 21 13 11 26

次にLeftArmの下にSlider_pivotと名付けたGameObjectを作成し、適当に左腕の近くに配置します。

2019 11 21 13 12 44

2019 11 21 13 12 50

Slider_pivotのScaleのyを0.25にします。

2019 11 21 13 20 26

Sliderpivotの下にSliderCNTRLと名付けたGameObjectを配置します。

2019 11 21 13 13 31

LeftArmにさきほど作成したTwo Bone IK FK Constraintをアタッチします。

2019 11 21 13 14 14

Dataに上腕、下腕、手のボーンとCNTRLオブジェクトを登録します。

2019 11 21 13 15 29

CNTRLオブジェクトをシーンビューから選択できるようにEffectorを設定していきます。

Effector用に次のようなMeshをBlenderで作成しました。

2019 11 20 22 16 23

2019 11 21 12 20 01

2019 11 20 22 24 24

Blenderからobjで書き出す際にInclude Edgesにチェックを入れます。

2019 11 21 11 50 44

このメッシュを使うにはひと工夫が必要でした。 単純にobjに書き出してUnityで読み込もうとするとTriangleが存在しないため無視されてしまうようです。

UnityはMeshTopologyでLineStripにも対応しているのですが、標準のimporterではその設定が見当たりませんでした(quadの設定ならあるのですが)。

Unity - Scripting API: MeshTopology

そこでlineのメッシュをobjファイルから作成するEditor拡張を作成しました。 次のスクリプトをEditor/フォルダの下に配置します。

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public class LineObjToMesh : EditorWindow {
    [MenuItem ("Animation Rigging/Utils/Line Obj To Mesh")]
    static void Open () {
        GetWindow<LineObjToMesh> ("Line Obj To Mesh");
    }

    Object obj;

    void OnGUI () {
        obj = EditorGUILayout.ObjectField (obj, typeof (Object), false);
        if (GUILayout.Button ("Create Line Mesh")) {
            CreateLineMesh (obj);
        }
    }

    void CreateLineMesh (Object obj) {
        var path = AssetDatabase.GetAssetPath (obj);

        var vertices = new List<Vector3> ();
        var indices = new List<int> ();

        using (var reader = new StreamReader (path)) {
            string line;
            while ((line = reader.ReadLine ()) != null) {
                if (line.Length <= 0) continue;
                var l = line.Split (' ');
                switch (l[0]) {
                    case "v":
                        vertices.Add (new Vector3 (
                            float.Parse (l[1]),
                            float.Parse (l[2]),
                            float.Parse (l[3])
                        ));
                        break;
                    case "l":
                        indices.Add (int.Parse (l[1]) - 1);
                        indices.Add (int.Parse (l[2]) - 1);
                        break;
                    default:
                        break;
                }

            }
        }

        var mesh = new Mesh ();
        mesh.SetVertices (vertices);
        mesh.SetIndices (indices, MeshTopology.Lines, 0);

        var assetPath = Path.ChangeExtension (path, "asset");
        AssetDatabase.CreateAsset (mesh, assetPath);
    }
}

Lineで構成されたobjファイルをセットしてCreate Line Meshを実行します。

2019 11 21 12 35 04

するとラインで構成されたMeshが生成されます。

2019 11 21 12 35 47

2019 11 21 12 35 56

このスクリプトはobjの読み込みを大分簡略化していて、Lineで構成されたオブジェクトが1つの場合しか考えていません。

IKTargetCNTRLにはBoxEffectorを設定しました。

2019 11 21 13 16 40

IKHintCNTRLにはBallEffectorを設定しました。

2019 11 21 13 17 07

上腕のFKRootCNTRLにはBlenderで作成したライン状のメッシュを設定しました。

2019 11 21 13 17 53

下腕のFKMidCNTRLにもBlenderで作成したライン状のメッシュを設定しました。

2019 11 21 13 19 02

Slider_CNTRLにはBallEffectorを設定しました。

2019 11 21 13 19 38

Slider_pivotにBlenderで作成したIK/FK切り替えのフレームのメッシュを設定し適当に位置を揃えました。

2019 11 21 13 21 26

これでリグが完成です。

うごかしてみる

Playモードに入ってCNTRLオブジェクトを動かすと期待どおりにリグが働くことを確認できます。

スライダを動かすことでIKとFKをスムーズに切り替えられています。

手の向きについては今回はコントロールしていません。 別途コントローラが必要になるでしょう。

デフォルトのTwo Bone IKでは、FKアニメーションをBaseレイヤーでつけて、追加でIKのモーションを別レイヤーでつけるといった形で作成する必要がありました。 今回作成したTwo Bone IK FKではIKとFKを同じリグでコントロールできます。 プロシージャルモーション用途であとからIKを追加するだけならば標準のTwo Bone IKでも問題ないでしょうが、 アニメーション作成にAnimation Riggingを作成する場合、今回作成したコンストレイントのほうが使いやすいと思います。

スクリプトの解説

Animation Rigginは例によって4つの部分からなっています。順番に見ていきます。

最初にRigConstraintを継承したコンポーネントのクラスです。

[DisallowMultipleComponent, AddComponentMenu ("Animation Rigging/Custom/Two Bone IK FK Constraint")]
public class TwoBoneIKFKConstraint : RigConstraint<TwoBoneIKFKConstraintJob, TwoBoneIKFKConstraintData, TwoBoneIKFKConstraintBinder> { }

見たとおりなので特に解説することはありません。

次にIweightedAnimationJobを継承した構造体です。

[BurstCompile]
public struct TwoBoneIKFKConstraintJob : IWeightedAnimationJob {

    public ReadWriteTransformHandle Root;
    public ReadWriteTransformHandle Mid;
    public ReadWriteTransformHandle Tip;

    public ReadOnlyTransformHandle IK_Target;
    public ReadOnlyTransformHandle IK_Hint;

    public ReadOnlyTransformHandle FK_Root;
    public ReadOnlyTransformHandle FK_Mid;
    public ReadOnlyTransformHandle FK_Tip;

    public ReadWriteTransformHandle Slider;

    public Vector2 LinkLengths;

    public FloatProperty jobWeight { get; set; }

    public void ProcessRootMotion (AnimationStream stream) { }

    public void ProcessAnimation (AnimationStream stream) {
        float w = jobWeight.Get (stream);

        var sliderPos = Slider.GetLocalPosition (stream);
        var t = Mathf.Clamp01 (sliderPos.y);
        Slider.SetLocalPosition (stream, new Vector3 (0, t, 0));

        if (w > 0f) {
            var rootRot = Root.GetRotation (stream);
            var midRot = Mid.GetRotation (stream);
            var tipRot = Tip.GetRotation (stream);

            var rootRotFK = Quaternion.Lerp (rootRot, FK_Root.GetRotation (stream), w);
            var midRotFK = Quaternion.Lerp (midRot, FK_Mid.GetRotation (stream), w);
            var tipRotFK = tipRot;

            AnimationRuntimeUtils.SolveTwoBoneIK (
                stream, Root, Mid, Tip, IK_Target, IK_Hint,
                posWeight : 1f * w,
                rotWeight : 0 * w,
                hintWeight : 1f * w,
                limbLengths : LinkLengths,
                targetOffset : AffineTransform.identity
            );
            var rootRotIK = Root.GetRotation (stream);
            var midRotIK = Mid.GetRotation (stream);
            var tipRotIK = Tip.GetRotation (stream);

            Root.SetRotation (stream, Quaternion.Lerp (rootRotFK, rootRotIK, t));
            Mid.SetRotation (stream, Quaternion.Lerp (midRotFK, midRotIK, t));
            Tip.SetRotation (stream, Quaternion.Lerp (tipRotFK, tipRotIK, t));
        }
    }
}

ProcessAnimation (AnimationStream stream)が今回作成したリグのメインの部分です。

最初にjobWeightを取得しています。

float w = jobWeight.Get (stream);

次にスライダーの位置からIKとFKの混合割合のtを取得しています。 スライダーの位置をxとzは動かないように、yは01の区間に収まるように上書きもしています。

var sliderPos = Slider.GetLocalPosition (stream);
var t = Mathf.Clamp01 (sliderPos.y);
Slider.SetLocalPosition (stream, new Vector3 (0, t, 0));

ウエイトが0より大きい場合に後続の処理を行います。

最初にボーンの回転情報をそれぞれ取得します。

var rootRot = Root.GetRotation (stream);
var midRot = Mid.GetRotation (stream);
var tipRot = Tip.GetRotation (stream);

次にFKの回転を計算します。 jobWeightのwを使ってボーンの回転とFKコントローラの回転を混ぜています。

var rootRotFK = Quaternion.Lerp (rootRot, FK_Root.GetRotation (stream), w);
var midRotFK = Quaternion.Lerp (midRot, FK_Mid.GetRotation (stream), w);
var tipRotFK = tipRot;

次にIKの回転を計算します。

AnimationRuntimeUtils.SolveTwoBoneIK (
    stream, Root, Mid, Tip, IK_Target, IK_Hint,
    posWeight : 1f * w,
    rotWeight : 0 * w,
    hintWeight : 1f * w,
    limbLengths : LinkLengths,
    targetOffset : AffineTransform.identity
);
var rootRotIK = Root.GetRotation (stream);
var midRotIK = Mid.GetRotation (stream);
var tipRotIK = Tip.GetRotation (stream);

AnimationRuntimeUtils.SolveTwoBoneIKは標準のTowBoneIkConstraintで使われているものです。 これはroot、mid、tipのいちを書き換える挙動をしているので、書き換えたあとの回転を取得してIKの回転としています。

最後にIKとFKの回転を混ぜて処理は終了です。

Root.SetRotation (stream, Quaternion.Lerp (rootRotFK, rootRotIK, t));
Mid.SetRotation (stream, Quaternion.Lerp (midRotFK, midRotIK, t));
Tip.SetRotation (stream, Quaternion.Lerp (tipRotFK, tipRotIK, t));

次にIAnimationJobDataを継承した構造体です。

[System.Serializable]
public struct TwoBoneIKFKConstraintData : IAnimationJobData {

    public Transform Root;
    public Transform Mid;
    public Transform Tip;

    [SyncSceneToStream] public Transform IK_Target;
    [SyncSceneToStream] public Transform IK_Hint;

    [SyncSceneToStream] public Transform FK_Root;
    [SyncSceneToStream] public Transform FK_Mid;

    [SyncSceneToStream] public Transform Slider;

    public bool IsValid () => !(Tip == null || Mid == null || Root == null || IK_Target == null || FK_Root == null || FK_Mid == null || Slider == null);

    public void SetDefaultValues () {
        Root = null;
        Mid = null;
        Tip = null;
        IK_Target = null;
        IK_Hint = null;
        FK_Root = null;
        FK_Mid = null;
        Slider = null;
    }
}

見てのとおりですが、publicフィールドでTransformをシリアライズしています。 IsValidで有効になる場合を指定し、SetDefaultValuesでデフォルトの値を指定しています。

最後に`AnimationJobBinder`を継承したクラスです。

public class TwoBoneIKFKConstraintBinder : AnimationJobBinder<TwoBoneIKFKConstraintJob, TwoBoneIKFKConstraintData> {

    public override TwoBoneIKFKConstraintJob Create (Animator animator, ref TwoBoneIKFKConstraintData data, Component component) {
        var job = new TwoBoneIKFKConstraintJob ();

        job.Root = ReadWriteTransformHandle.Bind (animator, data.Root);
        job.Mid = ReadWriteTransformHandle.Bind (animator, data.Mid);
        job.Tip = ReadWriteTransformHandle.Bind (animator, data.Tip);

        job.IK_Target = ReadOnlyTransformHandle.Bind (animator, data.IK_Target);
        if (data.IK_Hint != null)
            job.IK_Hint = ReadOnlyTransformHandle.Bind (animator, data.IK_Hint);

        job.FK_Root = ReadOnlyTransformHandle.Bind (animator, data.FK_Root);
        job.FK_Mid = ReadOnlyTransformHandle.Bind (animator, data.FK_Mid);

        job.Slider = ReadWriteTransformHandle.Bind (animator, data.Slider);

        job.LinkLengths[0] = Vector3.Distance (data.Root.position, data.Mid.position);
        job.LinkLengths[1] = Vector3.Distance (data.Mid.position, data.Tip.position);

        return job;
    }

    public override void Destroy (TwoBoneIKFKConstraintJob job) { }
}

CreateDestroyをオーバーライドしています。

CreateではJobを作成しています。

var job = new TwoBoneIKFKConstraintJob ();

そしてJobとDataのバインドを行っています。

job.Root = ReadWriteTransformHandle.Bind (animator, data.Root);
job.Mid = ReadWriteTransformHandle.Bind (animator, data.Mid);
job.Tip = ReadWriteTransformHandle.Bind (animator, data.Tip);

job.IK_Target = ReadOnlyTransformHandle.Bind (animator, data.IK_Target);
if (data.IK_Hint != null)
    job.IK_Hint = ReadOnlyTransformHandle.Bind (animator, data.IK_Hint);

job.FK_Root = ReadOnlyTransformHandle.Bind (animator, data.FK_Root);
job.FK_Mid = ReadOnlyTransformHandle.Bind (animator, data.FK_Mid);

job.Slider = ReadWriteTransformHandle.Bind (animator, data.Slider);

HintはAnimationRuntimeUtils.SolveTwoBoneIKではオプションとなっています。 Hintがdataに登録されていない場合はBindをしないようにしています。

if (data.IK_Hint != null)
    job.IK_Hint = ReadOnlyTransformHandle.Bind (animator, data.IK_Hint);

IKの計算で使われる腕の長さも計算しています。

job.LinkLengths[0] = Vector3.Distance (data.Root.position, data.Mid.position);
job.LinkLengths[1] = Vector3.Distance (data.Mid.position, data.Tip.position);

最後にjobを返しています。

return job;

おわりに

Animation Riggingでアニメーション作成に使っていけそうなIK/FKコンストレイントを作成しました。 ちゃんと拡張できるように作られていてありがたいですね。

https://github.com/MatchaChoco010/UnityAnimationRiggingTwoBoneIKFKConstraint