[unity] SkeletonRootMotion components now respect TransformConstraint timelines. Closes #1944.

This commit is contained in:
Harald Csaszar 2021-11-16 18:01:00 +01:00
parent da6016cf07
commit 90dd4bc8af
3 changed files with 157 additions and 39 deletions

View File

@ -2348,6 +2348,30 @@ namespace Spine {
} }
float rotate, x, y, scaleX, scaleY, shearY; float rotate, x, y, scaleX, scaleY, shearY;
GetCurveValue(out rotate, out x, out y, out scaleX, out scaleY, out shearY, time);
if (blend == MixBlend.Setup) {
TransformConstraintData data = constraint.data;
constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
constraint.mixX = data.mixX + (x - data.mixX) * alpha;
constraint.mixY = data.mixY + (y - data.mixY) * alpha;
constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha;
constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha;
constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha;
} else {
constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
constraint.mixX += (x - constraint.mixX) * alpha;
constraint.mixY += (y - constraint.mixY) * alpha;
constraint.mixScaleX += (scaleX - constraint.mixScaleX) * alpha;
constraint.mixScaleY += (scaleY - constraint.mixScaleY) * alpha;
constraint.mixShearY += (shearY - constraint.mixShearY) * alpha;
}
}
public void GetCurveValue (out float rotate, out float x, out float y,
out float scaleX, out float scaleY, out float shearY, float time) {
float[] frames = this.frames;
int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES];
switch (curveType) { switch (curveType) {
case LINEAR: case LINEAR:
@ -2383,23 +2407,6 @@ namespace Spine {
shearY = GetBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER); shearY = GetBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER);
break; break;
} }
if (blend == MixBlend.Setup) {
TransformConstraintData data = constraint.data;
constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
constraint.mixX = data.mixX + (x - data.mixX) * alpha;
constraint.mixY = data.mixY + (y - data.mixY) * alpha;
constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha;
constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha;
constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha;
} else {
constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
constraint.mixX += (x - constraint.mixX) * alpha;
constraint.mixY += (y - constraint.mixY) * alpha;
constraint.mixScaleX += (scaleX - constraint.mixScaleX) * alpha;
constraint.mixScaleY += (scaleY - constraint.mixScaleY) * alpha;
constraint.mixShearY += (shearY - constraint.mixShearY) * alpha;
}
} }
} }

View File

@ -66,6 +66,8 @@ namespace Spine.Unity {
protected ISkeletonComponent skeletonComponent; protected ISkeletonComponent skeletonComponent;
protected Bone rootMotionBone; protected Bone rootMotionBone;
protected int rootMotionBoneIndex; protected int rootMotionBoneIndex;
protected List<int> transformConstraintIndices = new List<int>();
protected List<Vector2> transformConstraintLastPos = new List<Vector2>();
protected List<Bone> topLevelBones = new List<Bone>(); protected List<Bone> topLevelBones = new List<Bone>();
protected Vector2 initialOffset = Vector2.zero; protected Vector2 initialOffset = Vector2.zero;
protected Vector2 tempSkeletonDisplacement; protected Vector2 tempSkeletonDisplacement;
@ -94,7 +96,6 @@ namespace Spine.Unity {
return; // Root motion is only applied when component is enabled. return; // Root motion is only applied when component is enabled.
if (rigidBody2D != null) { if (rigidBody2D != null) {
Vector2 gravityAndVelocityMovement = Vector2.zero; Vector2 gravityAndVelocityMovement = Vector2.zero;
if (applyRigidbody2DGravity) { if (applyRigidbody2DGravity) {
float deltaTime = Time.fixedDeltaTime; float deltaTime = Time.fixedDeltaTime;
@ -108,10 +109,12 @@ namespace Spine.Unity {
rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(transform.position.x, transform.position.y) rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(transform.position.x, transform.position.y)
+ rigidbodyDisplacement); + rigidbodyDisplacement);
} }
if (rigidBody != null) { else if (rigidBody != null) {
rigidBody.MovePosition(transform.position rigidBody.MovePosition(transform.position
+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0)); + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0));
} }
else return;
Vector2 parentBoneScale; Vector2 parentBoneScale;
GetScaleAffectingRootMotion(out parentBoneScale); GetScaleAffectingRootMotion(out parentBoneScale);
ClearEffectiveBoneOffsets(parentBoneScale); ClearEffectiveBoneOffsets(parentBoneScale);
@ -155,6 +158,7 @@ namespace Spine.Unity {
if (bone != null) { if (bone != null) {
this.rootMotionBoneIndex = bone.Data.Index; this.rootMotionBoneIndex = bone.Data.Index;
this.rootMotionBone = bone; this.rootMotionBone = bone;
FindTransformConstraintsAffectingBone();
} else { } else {
Debug.Log("Bone named \"" + name + "\" could not be found."); Debug.Log("Bone named \"" + name + "\" could not be found.");
this.rootMotionBoneIndex = 0; this.rootMotionBoneIndex = 0;
@ -196,11 +200,70 @@ namespace Spine.Unity {
public Vector2 GetAnimationRootMotion (float startTime, float endTime, public Vector2 GetAnimationRootMotion (float startTime, float endTime,
Animation animation) { Animation animation) {
var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex); if (startTime == endTime)
if (timeline != null) {
return GetTimelineMovementDelta(startTime, endTime, timeline, animation);
}
return Vector2.zero; return Vector2.zero;
TranslateTimeline translateTimeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
// Non-looped base
Vector2 endPos = Vector2.zero;
Vector2 startPos = Vector2.zero;
if (translateTimeline != null) {
endPos = translateTimeline.Evaluate(endTime);
startPos = translateTimeline.Evaluate(startTime);
}
var transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items;
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
ApplyConstraintToPos(animation, constraint, constraintIndex, endTime, false, ref endPos);
ApplyConstraintToPos(animation, constraint, constraintIndex, startTime, true, ref startPos);
}
Vector2 currentDelta = endPos - startPos;
// Looped additions
if (startTime > endTime) {
Vector2 loopPos = Vector2.zero;
Vector2 zeroPos = Vector2.zero;
if (translateTimeline != null) {
loopPos = translateTimeline.Evaluate(animation.Duration);
zeroPos = translateTimeline.Evaluate(0);
}
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
ApplyConstraintToPos(animation, constraint, constraintIndex, animation.Duration, false, ref loopPos);
ApplyConstraintToPos(animation, constraint, constraintIndex, 0, false, ref zeroPos);
}
currentDelta += loopPos - zeroPos;
}
UpdateLastConstraintPos(transformConstraintsItems);
return currentDelta;
}
void ApplyConstraintToPos (Animation animation, TransformConstraint constraint,
int constraintIndex, float time, bool useLastConstraintPos, ref Vector2 pos) {
TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
if (timeline == null)
return;
Vector2 mixXY = timeline.EvaluateTranslateXYMix(time);
Vector2 invMixXY = timeline.EvaluateTranslateXYMix(time);
Vector2 constraintPos;
if (useLastConstraintPos)
constraintPos = transformConstraintLastPos[constraintIndex];
else {
Bone targetBone = constraint.Target;
constraintPos = new Vector2(targetBone.X, targetBone.Y);
}
pos = new Vector2(
pos.x * invMixXY.x + constraintPos.x * mixXY.x,
pos.y * invMixXY.y + constraintPos.y * mixXY.y);
}
void UpdateLastConstraintPos (TransformConstraint[] transformConstraintsItems) {
foreach (int constraintIndex in this.transformConstraintIndices) {
TransformConstraint constraint = transformConstraintsItems[constraintIndex];
Bone targetBone = constraint.Target;
transformConstraintLastPos[constraintIndex] = new Vector2(targetBone.X, targetBone.Y);
}
} }
public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) { public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
@ -218,18 +281,18 @@ namespace Spine.Unity {
return rootMotion; return rootMotion;
} }
Vector2 GetTimelineMovementDelta (float startTime, float endTime, void FindTransformConstraintsAffectingBone () {
TranslateTimeline timeline, Animation animation) { var constraints = skeletonComponent.Skeleton.TransformConstraints;
var constraintsItems = constraints.Items;
Vector2 currentDelta; for (int i = 0, n = constraints.Count; i < n; ++i) {
if (startTime > endTime) // Looped TransformConstraint constraint = constraintsItems[i];
currentDelta = (timeline.Evaluate(animation.Duration) - timeline.Evaluate(startTime)) if (constraint.Bones.Contains(rootMotionBone)) {
+ (timeline.Evaluate(endTime) - timeline.Evaluate(0)); transformConstraintIndices.Add(i);
else if (startTime != endTime) // Non-looped Bone targetBone = constraint.Target;
currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime); Vector2 constraintPos = new Vector2(targetBone.X, targetBone.Y);
else transformConstraintLastPos.Add(constraintPos);
currentDelta = Vector2.zero; }
return currentDelta; }
} }
void GatherTopLevelBones () { void GatherTopLevelBones () {
@ -267,6 +330,19 @@ namespace Spine.Unity {
} }
} }
void ApplyTransformConstraints () {
rootMotionBone.AX = rootMotionBone.X;
rootMotionBone.AY = rootMotionBone.Y;
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()
/// can be called afterwards without having a different starting point.
constraint.Update();
}
}
Vector2 GetScaleAffectingRootMotion () { Vector2 GetScaleAffectingRootMotion () {
Vector2 parentBoneScale; Vector2 parentBoneScale;
return GetScaleAffectingRootMotion(out parentBoneScale); return GetScaleAffectingRootMotion(out parentBoneScale);
@ -309,6 +385,9 @@ namespace Spine.Unity {
} }
void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) { void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) {
ApplyTransformConstraints();
// Move top level bones in opposite direction of the root motion bone // Move top level bones in opposite direction of the root motion bone
var skeleton = skeletonComponent.Skeleton; var skeleton = skeletonComponent.Skeleton;
foreach (var topLevelBone in topLevelBones) { foreach (var topLevelBone in topLevelBones) {
@ -316,8 +395,13 @@ namespace Spine.Unity {
if (transformPositionX) topLevelBone.X = displacementSkeletonSpace.x / skeleton.ScaleX; if (transformPositionX) topLevelBone.X = displacementSkeletonSpace.x / skeleton.ScaleX;
if (transformPositionY) topLevelBone.Y = displacementSkeletonSpace.y / skeleton.ScaleY; if (transformPositionY) topLevelBone.Y = displacementSkeletonSpace.y / skeleton.ScaleY;
} else { } else {
float offsetX = (initialOffset.x - rootMotionBone.X) * parentBoneScale.x; bool useAppliedPosition = transformConstraintIndices.Count > 0;
float offsetY = (initialOffset.y - rootMotionBone.Y) * parentBoneScale.y; float rootMotionBoneX = useAppliedPosition ? rootMotionBone.AX : rootMotionBone.X;
float rootMotionBoneY = useAppliedPosition ? 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 (transformPositionX) topLevelBone.X = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
if (transformPositionY) topLevelBone.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY; if (transformPositionY) topLevelBone.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
} }

View File

@ -36,7 +36,7 @@ namespace Spine.Unity.AnimationTools {
/// <summary>Evaluates the resulting value of a TranslateTimeline at a given time. /// <summary>Evaluates the resulting value of a TranslateTimeline at a given time.
/// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData. /// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData.
/// If no SkeletonData is given, values are returned as difference to setup pose /// If no SkeletonData is provided, values are returned as difference to setup pose
/// instead of absolute values.</summary> /// instead of absolute values.</summary>
public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) { public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) {
if (time < timeline.Frames[0]) return Vector2.zero; if (time < timeline.Frames[0]) return Vector2.zero;
@ -52,8 +52,18 @@ namespace Spine.Unity.AnimationTools {
} }
} }
/// <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) {
if (time < timeline.Frames[0]) return Vector2.zero;
float rotate, mixX, mixY, scaleX, scaleY, shearY;
timeline.GetCurveValue(out rotate, out mixX, out mixY, out scaleX, out scaleY, out shearY, time);
return new Vector2(mixX, mixY);
}
/// <summary>Gets the translate timeline for a given boneIndex. /// <summary>Gets the translate timeline for a given boneIndex.
/// You can get the boneIndex using SkeletonData.FindBoneIndex. /// You can get the boneIndex using SkeletonData.FindBone().Index.
/// The root bone is always boneIndex 0. /// The root bone is always boneIndex 0.
/// This will return null if a TranslateTimeline is not found.</summary> /// This will return null if a TranslateTimeline is not found.</summary>
public static TranslateTimeline FindTranslateTimelineForBone (this Animation a, int boneIndex) { public static TranslateTimeline FindTranslateTimelineForBone (this Animation a, int boneIndex) {
@ -67,5 +77,22 @@ namespace Spine.Unity.AnimationTools {
} }
return null; return null;
} }
/// <summary>Gets the transform constraint timeline for a given boneIndex.
/// You can get the boneIndex using SkeletonData.FindBone().Index.
/// The root bone is always boneIndex 0.
/// This will return null if a TranslateTimeline is not found.</summary>
public static TransformConstraintTimeline FindTransformConstraintTimeline (this Animation a, int transformConstraintIndex) {
foreach (var timeline in a.Timelines) {
if (timeline.GetType().IsSubclassOf(typeof(TransformConstraintTimeline)))
continue;
var transformConstraintTimeline = timeline as TransformConstraintTimeline;
if (transformConstraintTimeline != null &&
transformConstraintTimeline.TransformConstraintIndex == transformConstraintIndex)
return transformConstraintTimeline;
}
return null;
}
} }
} }