[unity] SkeletonRootMotion components now support rotation root motion.

This commit is contained in:
Harald Csaszar 2022-02-23 20:49:15 +01:00
parent 94c42ab33a
commit 3688bdb25e
6 changed files with 1518 additions and 30 deletions

1255
CHANGELOG.md.orig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@ namespace Spine.Unity.Editor {
protected SerializedProperty rootMotionBoneName;
protected SerializedProperty transformPositionX;
protected SerializedProperty transformPositionY;
protected SerializedProperty transformRotation;
protected SerializedProperty rootMotionScaleX;
protected SerializedProperty rootMotionScaleY;
protected SerializedProperty rootMotionTranslateXPerY;
@ -48,6 +49,7 @@ namespace Spine.Unity.Editor {
protected GUIContent rootMotionBoneNameLabel;
protected GUIContent transformPositionXLabel;
protected GUIContent transformPositionYLabel;
protected GUIContent transformRotationLabel;
protected GUIContent rootMotionScaleXLabel;
protected GUIContent rootMotionScaleYLabel;
protected GUIContent rootMotionTranslateXPerYLabel;
@ -61,6 +63,7 @@ namespace Spine.Unity.Editor {
rootMotionBoneName = serializedObject.FindProperty("rootMotionBoneName");
transformPositionX = serializedObject.FindProperty("transformPositionX");
transformPositionY = serializedObject.FindProperty("transformPositionY");
transformRotation = serializedObject.FindProperty("transformRotation");
rootMotionScaleX = serializedObject.FindProperty("rootMotionScaleX");
rootMotionScaleY = serializedObject.FindProperty("rootMotionScaleY");
rootMotionTranslateXPerY = serializedObject.FindProperty("rootMotionTranslateXPerY");
@ -72,6 +75,7 @@ namespace Spine.Unity.Editor {
rootMotionBoneNameLabel = new UnityEngine.GUIContent("Root Motion Bone", "The bone to take the motion from.");
transformPositionXLabel = new UnityEngine.GUIContent("X", "Root transform position (X)");
transformPositionYLabel = new UnityEngine.GUIContent("Y", "Use the Y-movement of the bone.");
transformRotationLabel = new UnityEngine.GUIContent("Rotation", "Use the rotation of the bone.");
rootMotionScaleXLabel = new UnityEngine.GUIContent("Root Motion Scale (X)", "Scale applied to the horizontal root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
rootMotionScaleYLabel = new UnityEngine.GUIContent("Root Motion Scale (Y)", "Scale applied to the vertical root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
rootMotionTranslateXPerYLabel = new UnityEngine.GUIContent("Root Motion Translate (X)", "Added X translation per root motion Y delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
@ -102,6 +106,7 @@ namespace Spine.Unity.Editor {
EditorGUILayout.PropertyField(rootMotionBoneName, rootMotionBoneNameLabel);
EditorGUILayout.PropertyField(transformPositionX, transformPositionXLabel);
EditorGUILayout.PropertyField(transformPositionY, transformPositionYLabel);
EditorGUILayout.PropertyField(transformRotation, transformRotationLabel);
EditorGUILayout.PropertyField(rootMotionScaleX, rootMotionScaleXLabel);
EditorGUILayout.PropertyField(rootMotionScaleY, rootMotionScaleYLabel);

View File

@ -53,6 +53,7 @@ namespace Spine.Unity {
#endregion
protected Vector2 movementDelta;
protected float rotationDelta;
SkeletonMecanim skeletonMecanim;
public SkeletonMecanim SkeletonMecanim {
@ -107,14 +108,29 @@ namespace Spine.Unity {
} else {
movementDelta -= weight * GetAnimationRootMotion(time, lastTime, animation);
}
if (transformRotation) {
if (!playsBackward) {
rotationDelta += weight * GetAnimationRootMotionRotation(lastTime, time, animation);
} else {
rotationDelta -= weight * GetAnimationRootMotionRotation(time, lastTime, animation);
}
}
}
protected override Vector2 CalculateAnimationsMovementDelta () {
// Note: movement delta is not gather after animation but
// Note: movement delta is not gathered after animation but
// in OnClipApplied after every applied animation.
Vector2 result = movementDelta;
movementDelta = Vector2.zero;
return result;
}
protected override float CalculateAnimationsRotationDelta () {
// Note: movement delta is not gathered after animation but
// in OnClipApplied after every applied animation.
float result = rotationDelta;
rotationDelta = 0;
return result;
}
}
}

View File

@ -128,8 +128,50 @@ namespace Spine.Unity {
return localDelta;
}
protected override float CalculateAnimationsRotationDelta () {
float localDelta = 0;
int trackCount = animationState.Tracks.Count;
for (int trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
// note: animationTrackFlags != -1 below covers trackIndex >= 32,
// with -1 corresponding to entry "everything" of the dropdown list.
if (animationTrackFlags != -1 && (animationTrackFlags & 1 << trackIndex) == 0)
continue;
TrackEntry track = animationState.GetCurrent(trackIndex);
TrackEntry next = null;
while (track != null) {
var animation = track.Animation;
float start = track.AnimationLast;
float end = track.AnimationTime;
var currentDelta = GetAnimationRootMotionRotation(start, end, animation);
if (currentDelta != 0) {
ApplyMixAlphaToDelta(ref currentDelta, next, track);
localDelta += currentDelta;
}
// Traverse mixingFrom chain.
next = track;
track = track.MixingFrom;
}
}
return localDelta;
}
void ApplyMixAlphaToDelta (ref Vector2 currentDelta, TrackEntry next, TrackEntry track) {
// Apply mix alpha to the delta position (based on AnimationState.cs).
float mixAlpha = 1;
GetMixAlpha(ref mixAlpha, next, track);
currentDelta *= mixAlpha;
}
void ApplyMixAlphaToDelta (ref float currentDelta, TrackEntry next, TrackEntry track) {
float mixAlpha = 1;
GetMixAlpha(ref mixAlpha, next, track);
currentDelta *= mixAlpha;
}
void GetMixAlpha (ref float cumulatedMixAlpha, TrackEntry next, TrackEntry track) {
// code below based on AnimationState.cs
float mix;
if (next != null) {
if (next.MixDuration == 0) { // Single frame mix to undo mixingFrom changes.
@ -139,7 +181,7 @@ namespace Spine.Unity {
if (mix > 1) mix = 1;
}
float mixAndAlpha = track.Alpha * next.InterruptAlpha * (1 - mix);
currentDelta *= mixAndAlpha;
cumulatedMixAlpha *= mixAndAlpha;
} else {
if (track.MixDuration == 0) {
mix = 1;
@ -147,7 +189,7 @@ namespace Spine.Unity {
mix = track.Alpha * (track.MixTime / track.MixDuration);
if (mix > 1) mix = 1;
}
currentDelta *= mix;
cumulatedMixAlpha *= mix;
}
}
}

View File

@ -45,6 +45,7 @@ namespace Spine.Unity {
protected string rootMotionBoneName = "root";
public bool transformPositionX = true;
public bool transformPositionY = true;
public bool transformRotation = false;
public float rootMotionScaleX = 1;
public float rootMotionScaleY = 1;
@ -67,7 +68,14 @@ namespace Spine.Unity {
/// Returns <c>Vector2.zero</c> when <c>rigidbody</c> and <c>rigidbody2D</c> are null.
/// This can be necessary when multiple scripts call <c>Rigidbody2D.MovePosition</c>,
/// where the last call overwrites the effect of preceding ones.</summary>
public Vector2 PreviousRigidbodyRootMotion {
public Vector2 PreviousRigidbodyRootMotion2D {
get { return new Vector2(previousRigidbodyRootMotion.x, previousRigidbodyRootMotion.y); }
}
/// <summary>Root motion translation that has been applied in the preceding <c>FixedUpdate</c> call
/// if a rigidbody is assigned at either <c>rigidbody</c> or <c>rigidbody2D</c>.
/// Returns <c>Vector3.zero</c> when <c>rigidbody</c> and <c>rigidbody2D</c> are null.</summary>
public Vector3 PreviousRigidbodyRootMotion3D {
get { return previousRigidbodyRootMotion; }
}
@ -86,13 +94,19 @@ namespace Spine.Unity {
protected int rootMotionBoneIndex;
protected List<int> transformConstraintIndices = new List<int>();
protected List<Vector2> transformConstraintLastPos = new List<Vector2>();
protected List<float> transformConstraintLastRotation = new List<float>();
protected List<Bone> topLevelBones = new List<Bone>();
protected Vector2 initialOffset = Vector2.zero;
protected Vector2 tempSkeletonDisplacement;
protected Vector2 rigidbodyDisplacement;
protected Vector2 previousRigidbodyRootMotion = Vector2.zero;
protected Vector3 rigidbodyDisplacement;
protected Vector3 previousRigidbodyRootMotion = Vector2.zero;
protected Vector2 additionalRigidbody2DMovement = Vector2.zero;
protected Quaternion rigidbodyRotation = Quaternion.identity;
protected float rigidbody2DRotation;
protected float initialOffsetRotation;
protected float tempSkeletonRotation;
protected virtual void Reset () {
FindRigidbodyComponent();
}
@ -101,8 +115,10 @@ namespace Spine.Unity {
skeletonComponent = GetComponent<ISkeletonComponent>();
GatherTopLevelBones();
SetRootMotionBone(rootMotionBoneName);
if (rootMotionBone != null)
if (rootMotionBone != null) {
initialOffset = new Vector2(rootMotionBone.X, rootMotionBone.Y);
initialOffsetRotation = rootMotionBone.Rotation;
}
var skeletonAnimation = skeletonComponent as ISkeletonAnimation;
if (skeletonAnimation != null) {
@ -126,24 +142,26 @@ namespace Spine.Unity {
rigidBody2D.velocity * deltaTime;
}
Vector2 rigidbodyDisplacement2D = new Vector2(rigidbodyDisplacement.x, rigidbodyDisplacement.y);
rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(transform.position.x, transform.position.y)
+ rigidbodyDisplacement + additionalRigidbody2DMovement);
+ rigidbodyDisplacement2D + additionalRigidbody2DMovement);
rigidBody2D.MoveRotation(rigidbody2DRotation + rigidBody2D.rotation);
} else if (rigidBody != null) {
rigidBody.MovePosition(transform.position
+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0));
rigidBody.MoveRotation(rigidBody.rotation * rigidbodyRotation);
} else return;
Vector2 parentBoneScale;
GetScaleAffectingRootMotion(out parentBoneScale);
ClearEffectiveBoneOffsets(parentBoneScale);
previousRigidbodyRootMotion = rigidbodyDisplacement;
rigidbodyDisplacement = Vector2.zero;
tempSkeletonDisplacement = Vector2.zero;
ClearRigidbodyTempMovement();
}
protected virtual void OnDisable () {
rigidbodyDisplacement = Vector2.zero;
tempSkeletonDisplacement = Vector2.zero;
ClearRigidbodyTempMovement();
}
protected void FindRigidbodyComponent () {
@ -160,6 +178,7 @@ namespace Spine.Unity {
protected virtual float AdditionalScale { get { return 1.0f; } }
abstract protected Vector2 CalculateAnimationsMovementDelta ();
protected virtual float CalculateAnimationsRotationDelta () { return 0; }
abstract public Vector2 GetRemainingRootMotion (int trackIndex = 0);
public struct RootMotionInfo {
@ -278,6 +297,52 @@ namespace Spine.Unity {
return currentDelta;
}
public float GetAnimationRootMotionRotation (Animation animation) {
return GetAnimationRootMotionRotation(0, animation.Duration, animation);
}
public float GetAnimationRootMotionRotation (float startTime, float endTime,
Animation animation) {
if (startTime == endTime)
return 0;
RotateTimeline rotateTimeline = animation.FindTimelineForBone<RotateTimeline>(rootMotionBoneIndex);
// Non-looped base
float endRotation = 0;
float startRotation = 0;
if (rotateTimeline != null) {
endRotation = rotateTimeline.Evaluate(endTime);
startRotation = rotateTimeline.Evaluate(startTime);
}
var transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items;
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
ApplyConstraintToRotation(animation, constraint, constraintIndex, endTime, false, ref endRotation);
ApplyConstraintToRotation(animation, constraint, constraintIndex, startTime, true, ref startRotation);
}
float currentDelta = endRotation - startRotation;
// Looped additions
if (startTime > endTime) {
float loopRotation = 0;
float zeroPos = 0;
if (rotateTimeline != null) {
loopRotation = rotateTimeline.Evaluate(animation.Duration);
zeroPos = rotateTimeline.Evaluate(0);
}
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
ApplyConstraintToRotation(animation, constraint, constraintIndex, animation.Duration, false, ref loopRotation);
ApplyConstraintToRotation(animation, constraint, constraintIndex, 0, false, ref zeroPos);
}
currentDelta += loopRotation - zeroPos;
}
UpdateLastConstraintRotation(transformConstraintsItems);
return currentDelta;
}
void ApplyConstraintToPos (Animation animation, TransformConstraint constraint,
int constraintIndex, float time, bool useLastConstraintPos, ref Vector2 pos) {
TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
@ -297,6 +362,23 @@ namespace Spine.Unity {
pos.y * invMixXY.y + constraintPos.y * mixXY.y);
}
void ApplyConstraintToRotation (Animation animation, TransformConstraint constraint,
int constraintIndex, float time, bool useLastConstraintRotation, ref float rotation) {
TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
if (timeline == null)
return;
float mixRotate = timeline.EvaluateRotateMix(time);
float invMixRotate = timeline.EvaluateRotateMix(time);
float constraintRotation;
if (useLastConstraintRotation)
constraintRotation = transformConstraintLastRotation[constraintIndex];
else {
Bone targetBone = constraint.Target;
constraintRotation = targetBone.Rotation;
}
rotation = rotation * invMixRotate + constraintRotation * mixRotate;
}
void UpdateLastConstraintPos (TransformConstraint[] transformConstraintsItems) {
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
@ -305,6 +387,14 @@ namespace Spine.Unity {
}
}
void UpdateLastConstraintRotation (TransformConstraint[] transformConstraintsItems) {
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
Bone targetBone = constraint.Target;
transformConstraintLastRotation[constraintIndex] = targetBone.Rotation;
}
}
public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
RootMotionInfo rootMotion = new RootMotionInfo();
float duration = animation.Duration;
@ -340,6 +430,7 @@ namespace Spine.Unity {
Bone targetBone = constraint.Target;
Vector2 constraintPos = new Vector2(targetBone.X, targetBone.Y);
transformConstraintLastPos.Add(constraintPos);
transformConstraintLastRotation.Add(targetBone.Rotation);
}
}
}
@ -375,24 +466,49 @@ namespace Spine.Unity {
if (!this.isActiveAndEnabled)
return; // Root motion is only applied when component is enabled.
var boneLocalDelta = CalculateAnimationsMovementDelta();
Vector2 boneLocalDelta = CalculateAnimationsMovementDelta();
Vector2 parentBoneScale;
Vector2 skeletonDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale);
ApplyRootMotion(skeletonDelta, parentBoneScale);
Vector2 totalScale;
Vector2 skeletonTranslationDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale, out totalScale);
float skeletonRotationDelta = 0;
if (transformRotation) {
float boneLocalDeltaRotation = CalculateAnimationsRotationDelta();
skeletonRotationDelta = GetSkeletonSpaceRotationDelta(boneLocalDeltaRotation, totalScale);
}
ApplyRootMotion(skeletonTranslationDelta, skeletonRotationDelta, parentBoneScale);
}
void ApplyRootMotion (Vector2 skeletonDelta, Vector2 parentBoneScale) {
void ApplyRootMotion (Vector2 skeletonTranslationDelta, float skeletonRotationDelta, Vector2 parentBoneScale) {
// Apply root motion to Transform or RigidBody;
if (UsesRigidbody) {
rigidbodyDisplacement += (Vector2)transform.TransformVector(skeletonDelta);
rigidbodyDisplacement += transform.TransformVector(skeletonTranslationDelta);
// Accumulated displacement is applied on the next Physics update in FixedUpdate.
// Until the next Physics update, tempBoneDisplacement is offsetting bone locations
// to prevent stutter which would otherwise occur if we don't move every Update.
tempSkeletonDisplacement += skeletonDelta;
SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, parentBoneScale);
tempSkeletonDisplacement += skeletonTranslationDelta;
if (skeletonRotationDelta != 0.0f) {
if (rigidBody != null) {
Quaternion addedWorldRotation = Quaternion.AngleAxis(skeletonRotationDelta, transform.forward);
rigidbodyRotation = rigidbodyRotation * addedWorldRotation;
} else if (rigidBody2D != null) {
Vector3 lossyScale = transform.lossyScale;
float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
rigidbody2DRotation += rotationSign * skeletonRotationDelta;
}
tempSkeletonRotation += skeletonRotationDelta;
}
SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, tempSkeletonRotation, parentBoneScale);
} else {
transform.position += transform.TransformVector(skeletonDelta);
transform.position += transform.TransformVector(skeletonTranslationDelta);
if (skeletonRotationDelta != 0.0f) {
Vector3 lossyScale = transform.lossyScale;
float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
transform.Rotate(0, 0, rotationSign * skeletonRotationDelta);
}
ClearEffectiveBoneOffsets(parentBoneScale);
}
}
@ -400,11 +516,12 @@ namespace Spine.Unity {
void ApplyTransformConstraints () {
rootMotionBone.AX = rootMotionBone.X;
rootMotionBone.AY = rootMotionBone.Y;
rootMotionBone.AppliedRotation = rootMotionBone.Rotation;
var transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items;
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
// apply the constraint and sets Bone.ax and Bone.ay values.
/// Update is based on Bone.x and Bone.y, so skeleton.UpdateWorldTransform()
// apply the constraint and sets Bone.ax, Bone.ay and Bone.arotation values.
/// Update is based on Bone.x, Bone.y and Bone.rotation, so skeleton.UpdateWorldTransform()
/// can be called afterwards without having a different starting point.
constraint.Update();
}
@ -432,9 +549,9 @@ namespace Spine.Unity {
return totalScale;
}
Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale) {
Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale, out Vector2 totalScale) {
Vector2 skeletonDelta = boneLocalDelta;
Vector2 totalScale = GetScaleAffectingRootMotion(out parentBoneScale);
totalScale = GetScaleAffectingRootMotion(out parentBoneScale);
skeletonDelta.Scale(totalScale);
Vector2 rootMotionTranslation = new Vector2(
@ -451,7 +568,12 @@ namespace Spine.Unity {
return skeletonDelta;
}
void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) {
float GetSkeletonSpaceRotationDelta (float boneLocalDelta, Vector2 totalScaleAffectingRootMotion) {
float rotationSign = totalScaleAffectingRootMotion.x * totalScaleAffectingRootMotion.y > 0 ? 1 : -1;
return rotationSign * boneLocalDelta;
}
void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, float rotationSkeletonSpace, Vector2 parentBoneScale) {
ApplyTransformConstraints();
@ -461,22 +583,44 @@ namespace Spine.Unity {
if (topLevelBone == rootMotionBone) {
if (transformPositionX) topLevelBone.X = displacementSkeletonSpace.x / skeleton.ScaleX;
if (transformPositionY) topLevelBone.Y = displacementSkeletonSpace.y / skeleton.ScaleY;
if (transformRotation) {
float rotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1;
topLevelBone.Rotation = rotationSign * rotationSkeletonSpace;
}
} else {
bool useAppliedPosition = transformConstraintIndices.Count > 0;
float rootMotionBoneX = useAppliedPosition ? rootMotionBone.AX : rootMotionBone.X;
float rootMotionBoneY = useAppliedPosition ? rootMotionBone.AY : rootMotionBone.Y;
bool useAppliedTransform = transformConstraintIndices.Count > 0;
float rootMotionBoneX = useAppliedTransform ? rootMotionBone.AX : rootMotionBone.X;
float rootMotionBoneY = useAppliedTransform ? rootMotionBone.AY : rootMotionBone.Y;
float offsetX = (initialOffset.x - rootMotionBoneX) * parentBoneScale.x;
float offsetY = (initialOffset.y - rootMotionBoneY) * parentBoneScale.y;
if (transformPositionX) topLevelBone.X = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
if (transformPositionY) topLevelBone.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
if (transformRotation) {
float rootMotionBoneRotation = useAppliedTransform ? rootMotionBone.AppliedRotation : rootMotionBone.Rotation;
float parentBoneRotationSign = (parentBoneScale.x * parentBoneScale.y > 0 ? 1 : -1);
float offsetRotation = (initialOffsetRotation - rootMotionBoneRotation) * parentBoneRotationSign;
float skeletonRotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1;
topLevelBone.Rotation = (rotationSkeletonSpace * skeletonRotationSign) + offsetRotation;
}
}
}
}
void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) {
SetEffectiveBoneOffsetsTo(Vector2.zero, parentBoneScale);
SetEffectiveBoneOffsetsTo(Vector2.zero, 0, parentBoneScale);
}
void ClearRigidbodyTempMovement () {
rigidbodyDisplacement = Vector2.zero;
tempSkeletonDisplacement = Vector2.zero;
rigidbodyRotation = Quaternion.identity;
rigidbody2DRotation = 0;
tempSkeletonRotation = 0;
}
}
}

View File

@ -73,6 +73,22 @@ namespace Spine.Unity.AnimationTools {
}
}
/// <summary>Evaluates the resulting value of a RotateTimeline at a given time.
/// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData.
/// If no SkeletonData is given, values are returned as difference to setup pose
/// instead of absolute values.</summary>
public static float Evaluate (this RotateTimeline timeline, float time, SkeletonData skeletonData = null) {
if (time < timeline.Frames[0]) return 0f;
float rotation = timeline.GetCurveValue(time);
if (skeletonData == null) {
return rotation;
} else {
BoneData boneData = skeletonData.Bones.Items[timeline.BoneIndex];
return (boneData.Rotation + rotation);
}
}
/// <summary>Evaluates the resulting X and Y translate mix values of a
/// TransformConstraintTimeline at a given time.</summary>
public static Vector2 EvaluateTranslateXYMix (this TransformConstraintTimeline timeline, float time) {
@ -83,6 +99,16 @@ namespace Spine.Unity.AnimationTools {
return new Vector2(mixX, mixY);
}
/// <summary>Evaluates the resulting rotate mix values of a
/// TransformConstraintTimeline at a given time.</summary>
public static float EvaluateRotateMix (this TransformConstraintTimeline timeline, float time) {
if (time < timeline.Frames[0]) return 0;
float rotate, mixX, mixY, scaleX, scaleY, shearY;
timeline.GetCurveValue(out rotate, out mixX, out mixY, out scaleX, out scaleY, out shearY, time);
return rotate;
}
/// <summary>Gets the translate timeline for a given boneIndex.
/// You can get the boneIndex using SkeletonData.FindBone().Index.
/// The root bone is always boneIndex 0.