UnityのAnimation Riggingで良い感じの足のコントローラを作成する
はじめに
Animation Riggingで足のコントローラを作成したのでメモします。
- Unity: 2019.3.0b12
- Animation Rigging: 0.2.3-preview
Animation Riggingをインポートする
前回までと同様にしてPackage ManagerからAnimation Riggingをインストールします。
スクリプトを作成する
RotationBlendSliderConstraint.csを作成します。
スクリプトには次のように記述します。
using Unity.Burst;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Animations.Rigging;
[DisallowMultipleComponent, AddComponentMenu ("Animation Rigging/Custom/Rotation Blend Slider")]
public class RotationBlendSliderConstraint : RigConstraint<RotationBlendSliderConstraintJob, RotationBlendSliderConstraintData, RotationBlendSliderConstraintBinder> { }
[BurstCompile]
public struct RotationBlendSliderConstraintJob : IWeightedAnimationJob {
public ReadWriteTransformHandle Target;
public ReadOnlyTransformHandle SourceA;
public ReadOnlyTransformHandle SourceB;
public ReadWriteTransformHandle Slider;
public FloatProperty jobWeight { get; set; }
public void ProcessRootMotion (AnimationStream stream) { }
public void ProcessAnimation (AnimationStream stream) {
float w = jobWeight.Get (stream);
if (w > 0f) {
var sliderPos = Slider.GetLocalPosition (stream);
var t = Mathf.Clamp01 (sliderPos.y);
Slider.SetLocalPosition (stream, new Vector3 (0, t, 0));
var rot = Quaternion.Lerp (
SourceA.GetRotation (stream),
SourceB.GetRotation (stream),
t
);
var targetRot = Target.GetRotation (stream);
Target.SetRotation (stream, Quaternion.Lerp (targetRot, rot, w));
}
}
}
[System.Serializable]
public struct RotationBlendSliderConstraintData : IAnimationJobData {
public Transform Target;
[SyncSceneToStream] public Transform SourceA;
[SyncSceneToStream] public Transform SourceB;
[SyncSceneToStream] public Transform Slider;
public bool IsValid () => !(Target == null || SourceA == null || SourceB == null || Slider == null);
public void SetDefaultValues () {
Target = null;
SourceA = null;
SourceB = null;
Slider = null;
}
}
public class RotationBlendSliderConstraintBinder : AnimationJobBinder<RotationBlendSliderConstraintJob, RotationBlendSliderConstraintData> {
public override RotationBlendSliderConstraintJob Create (Animator animator, ref RotationBlendSliderConstraintData data, Component component) {
var job = new RotationBlendSliderConstraintJob ();
job.Target = ReadWriteTransformHandle.Bind (animator, data.Target);
job.SourceA = ReadOnlyTransformHandle.Bind (animator, data.SourceA);
job.SourceB = ReadOnlyTransformHandle.Bind (animator, data.SourceB);
job.Slider = ReadWriteTransformHandle.Bind (animator, data.Slider);
return job;
}
public override void Destroy (RotationBlendSliderConstraintJob job) { }
}
FootIKConstraint.csを作成します。
スクリプトには次のように記述します。
using Unity.Burst;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Animations.Rigging;
[DisallowMultipleComponent, AddComponentMenu ("Animation Rigging/Custom/Foot IK")]
public class FootIKConstraint : RigConstraint<FootIKConstraintJob, FootIKConstraintData, FootIKConstraintBinder> { }
[BurstCompile]
public struct FootIKConstraintJob : IWeightedAnimationJob {
public ReadWriteTransformHandle FootIKTarget;
public ReadOnlyTransformHandle FootControllerBase;
public ReadWriteTransformHandle FootRollCursor;
public ReadOnlyTransformHandle ToeEnd;
public ReadOnlyTransformHandle FootHeel;
public ReadOnlyTransformHandle FootLeftSide;
public ReadOnlyTransformHandle FootRightSide;
public FloatProperty jobWeight { get; set; }
public void ProcessRootMotion (AnimationStream stream) { }
public void ProcessAnimation (AnimationStream stream) {
float w = jobWeight.Get (stream);
if (w > 0f) {
var cursorPos = FootRollCursor.GetLocalPosition (stream);
FootRollCursor.SetLocalPosition (stream, new Vector3 (cursorPos.x, cursorPos.y, 0));
var pos = FootControllerBase.GetPosition (stream);
var rot = FootControllerBase.GetRotation (stream);
if (cursorPos.x >= 0f) {
var footRightSidePos = FootRightSide.GetPosition (stream);
var axisZ = rot * Vector3.forward;
var rotZ = Quaternion.AngleAxis (180 * cursorPos.x, axisZ);
pos = footRightSidePos + rotZ * (pos - footRightSidePos);
rot = rotZ * rot;
if (cursorPos.y >= 0f) {
var toePos = footRightSidePos + rotZ * (ToeEnd.GetPosition (stream) - footRightSidePos);
var axisX = rot * Vector3.right;
var rotX = Quaternion.AngleAxis (180 * cursorPos.y, axisX);
pos = toePos + rotX * (pos - toePos);
rot = rotX * rot;
} else {
var heelPos = footRightSidePos + rotZ * (FootHeel.GetPosition (stream) - footRightSidePos);
var axisX = rot * Vector3.right;
var rotX = Quaternion.AngleAxis (180 * cursorPos.y, axisX);
pos = heelPos + rotX * (pos - heelPos);
rot = rotX * rot;
}
} else {
var footLeftSidePos = FootLeftSide.GetPosition (stream);
var axisZ = rot * Vector3.forward;
var rotZ = Quaternion.AngleAxis (180 * cursorPos.x, axisZ);
pos = footLeftSidePos + rotZ * (pos - footLeftSidePos);
rot = rotZ * rot;
if (cursorPos.y >= 0f) {
var toePos = footLeftSidePos + rotZ * (ToeEnd.GetPosition (stream) - footLeftSidePos);
var axisX = rot * Vector3.right;
var rotX = Quaternion.AngleAxis (180 * cursorPos.y, axisX);
pos = toePos + rotX * (pos - toePos);
rot = rotX * rot;
} else {
var heelPos = footLeftSidePos + rotZ * (FootHeel.GetPosition (stream) - footLeftSidePos);
var axisX = rot * Vector3.right;
var rotX = Quaternion.AngleAxis (180 * cursorPos.y, axisX);
pos = heelPos + rotX * (pos - heelPos);
rot = rotX * rot;
}
}
var footIKTargetPos = FootIKTarget.GetPosition (stream);
var footIKTargetRot = FootIKTarget.GetRotation (stream);
FootIKTarget.SetPosition (stream, Vector3.Lerp (footIKTargetPos, pos, w));
FootIKTarget.SetRotation (stream, Quaternion.Lerp (footIKTargetRot, rot, w));
}
}
}
[System.Serializable]
public struct FootIKConstraintData : IAnimationJobData {
public Transform FootIKTarget;
[SyncSceneToStream] public Transform FootControllerBase;
[SyncSceneToStream] public Transform FootRollCursor;
[SyncSceneToStream] public Transform ToeEnd;
[SyncSceneToStream] public Transform FootHeel;
[SyncSceneToStream] public Transform FootLeftSide;
[SyncSceneToStream] public Transform FootRightSide;
public bool IsValid () => !(FootIKTarget == null || FootControllerBase == null || FootRollCursor == null || ToeEnd == null || FootHeel == null || FootLeftSide == null || FootRightSide == null);
public void SetDefaultValues () {
FootIKTarget = null;
FootControllerBase = null;
FootRollCursor = null;
ToeEnd = null;
FootHeel = null;
FootLeftSide = null;
FootRightSide = null;
}
}
public class FootIKConstraintBinder : AnimationJobBinder<FootIKConstraintJob, FootIKConstraintData> {
public override FootIKConstraintJob Create (Animator animator, ref FootIKConstraintData data, Component component) {
var job = new FootIKConstraintJob ();
job.FootIKTarget = ReadWriteTransformHandle.Bind (animator, data.FootIKTarget);
job.FootControllerBase = ReadOnlyTransformHandle.Bind (animator, data.FootControllerBase);
job.FootRollCursor = ReadWriteTransformHandle.Bind (animator, data.FootRollCursor);
job.ToeEnd = ReadOnlyTransformHandle.Bind (animator, data.ToeEnd);
job.FootHeel = ReadOnlyTransformHandle.Bind (animator, data.FootHeel);
job.FootLeftSide = ReadOnlyTransformHandle.Bind (animator, data.FootLeftSide);
job.FootRightSide = ReadOnlyTransformHandle.Bind (animator, data.FootRightSide);
return job;
}
public override void Destroy (FootIKConstraintJob job) { }
}
モデルを読み込む
前回と同様にしてMake Humanで作成したモデルを読み込みます。 読み込んだモデルをシーンに配置しました。
リグを構築する
モデルのルートにAnimatorとRig Builderコンポーネントをつけます。
モデルのルートの下にGameObjectを配置しControlRigと名付けます。
ControlRigにRigコンポーネントをつけます。
LeftLegのRigコンポーネントをControlRigのRig Builderに登録します。
ControlRigの下にGameObjectを追加してLeftLegと名付けます。
LeftLegの下にGameObjectを追加してLeftLegFKと名付けます。
LeftLegFKの下にGameObjectを追加してPelvisと名付けます。
Pelvisを選択し、Ctrlキーを押しながら骨盤のボーン(この場合はpelvis)を選択します。 選択する順番も重要です。
「Animation Rigging > Align Transform」を実行します。
これでPelvisが骨盤のボーンの位置に揃いました。
Pelvisを骨盤のボーンに追従させます。
PelvisにMulti-Parent Constraintをつけます。
Constrained ObjectにPelvisを、Source Objectに骨盤のボーン(この場合はpelvis)を指定します。
これで骨盤のボーンにリグのPelvisが追従するようになります。
Pelvisの下にGameObjectを追加してUpperLeg_pivotと名付けます。
UpperLeg_pivotを選択し、Ctrlキーを押しながら左の太もものボーンを選択します。 選択する順番も重要です。
Align Transformします。
UpperLegpivotの下にGameObjectを追加してUpperLegCNTRLと名付けます。
UpperLegCNTRLの下にGameObjectを追加してLowerLegpivotと名付けます。
LowerLeg_pivotを選択し、Ctrlキーを押しながらふくらはぎのボーンを選択し、Align Transformします。
LowerLegpivotの下にGameObjectを追加してLowerLegCNTRLと名付けます。
LowerLegCNTRLの下にGameObjectを作成しFootFKpivotと名付けます。
足のボーンとAlign Transformします。
FootFKpivotの下にGameObjectを作成しFootFKCNTRLと名付けます。
FootFKCNTRLの下にGameObjectを追加してToeFKpivotと名付けます。
つま先のボーンとAlign Transformします。
ToeFKpivotの下にGameObjectを追加してToeFKCNTRLと名付けます。
ここまででFK用の階層が作成できました。
次に、LeftLegの下にGameObjectを追加してLeftLegIKと名付けます。
LeftLegIKの下にGameObjectを追加してPelvisと名付けます。
骨盤のボーンとAlign Transformします。
PelvisにMulti-Parent Constraintをつけます。 Constrained Objectに自身を、Source Objectとして骨盤のボーンを指定します。 これで骨盤のボーンにリグのPelvisが追従するようになります。
Pelvisの下にGameObjectを追加してUpperLegと名付けます。 UpperLegを太もものボーンにAlign Transformします。
UpperLegの下にGameObjectを追加してLowerLegと名付けます。 LowerLegをふくらはぎのボーンにAlign Transformします。
LowerLegの下にGameObjectを追加してFootと名付けます。 Footを足のボーンにAlign Transformします。
Footの下にGameObjectを追加してToeIKpivotと名付けます。 ToeIKpivotをつま先のボーンにAlign Transformします。
ToeIKpivotの下にGameObjectを追加してToeIKCNTRLと名付けます。
次にLeftLegIKの下にGameObjectを追加してFootControllerBase_CNTRLと名付けます。
FootControllerBase_CNTRLを選択し、Ctrlキーを押しながら足のボーンを選択します。 選択する順番も重要です。
「Animation Rigging > Align Position」を実行し位置を揃えます。
FootControllerBaseCNTRLの下にGameObjectを追加しCursorpivotと名付けます。
適当に足のちょっと後ろに配置します。
適当にScaleを小さくしておきます。
Cursorpivotの下にGameObjectを追加してCursorCNTRLと名付けます。
FootControllerBase_CNTRLの下にGameObjectを追加しToeEndと名付けます。
ToeEndを次のようにつま先の位置に配置します。
FootControllerBase_CNTRLの下にGameObjectを追加しHeelと名付けます。
Heelを次のようにかかとの位置に配置します。
FootControllerBase_CNTRLの下にGameObjectを追加しLeftSideと名付けます。
LeftSideを次のように足の左側の位置に配置します。
FootControllerBase_CNTRLの下にGameObjectを追加しRightSideと名付けます。
RightSideを次のように足の右側の位置に配置します。
次にLeftLegIKの下にGameObjectを追加してIKTargetと名付けます。
IKTargetを足のボーンにAlign Positionします。
LeftLegIKの下にGameObjectを追加してIKHint_CNTRLと名付けます。
膝の前の方に配置します。
次にLeftLegIKに作成したFootIKコンポーネントをつけます。
Dataに次の図のように設定します。
LeftLegIKにTwo Bone IK Constraintをつけて次のように設定します。
Maintain Target OffsetはRotationに設定します。
これでLeftLegIKの下のPelvisから下の階層がIKで動くようになりました。
FKの階層とIKの階層をブレンドするリグを作ります。
LeftLegの下にGameObjectを作成しIKFKSliderと名付けます。
IKFKSliderの下にGameObjectを追加してSlider_pivotと名付けます。
Slider_pivotを適当に左脚の近くに配置します。
適当にScaleを小さく変更しておきます。
Sliderpivotの下にGameObjectを追加してSLiderCNTRLと名付けます。
IKFKSliderの下にGameObjectを4つ追加してそれぞれUpperLeg、LowerLeg、Foot、Toeと名付けます。
IKFKSliderの下のUpperLegに作成したRotationBlendSliderをつけます。
DataのTargetには太もものボーンを、SourceAにはFKの階層のUpperLegCNTRLを、SourceBにはIKの階層のUpperLegを、SliderにはSliderCNTRLをそれぞれアタッチします。
同様にしてLowerLeg、Foot、ToeについてもIKとFKをブレンドするよう設定します。
Effector用のMeshをいくつかBlenderで作成しました。
前回のスクリプトを利用してobjファイルからLine状のMeshにしておきます。
CNTRLオブジェクトにEffectorを設定していき、次のようになりました。
青がIKで赤がFKにしてあります。
動かしてみる
IKモードのカーソルを使って足をぐりぐりするのがよい感じです。
おわりに
足のリグの作成でした。 手順で書くと長くなりましたがやってることは大した作業量じゃないので、そんな時間かからずに構築できると思います。
今回のプロジェクトは次のリポジトリにまとめてあります。
https://github.com/MatchaChoco010/UnityAnimationRiggingLegRig