From 90dd4bc8af005055abfcc2ac91e506d9f284a62d Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Tue, 16 Nov 2021 18:01:00 +0100 Subject: [PATCH] [unity] SkeletonRootMotion components now respect TransformConstraint timelines. Closes #1944. --- spine-csharp/src/Animation.cs | 41 +++--- .../RootMotion/SkeletonRootMotionBase.cs | 124 +++++++++++++++--- .../spine-unity/Utility/TimelineExtensions.cs | 31 ++++- 3 files changed, 157 insertions(+), 39 deletions(-) diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index c2c520b4d..ca956d5b2 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -2348,6 +2348,30 @@ namespace Spine { } 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]; switch (curveType) { case LINEAR: @@ -2383,23 +2407,6 @@ namespace Spine { shearY = GetBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER); 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; - } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs index 6fd9014b3..a9ceed30c 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs @@ -66,6 +66,8 @@ namespace Spine.Unity { protected ISkeletonComponent skeletonComponent; protected Bone rootMotionBone; protected int rootMotionBoneIndex; + protected List transformConstraintIndices = new List(); + protected List transformConstraintLastPos = new List(); protected List topLevelBones = new List(); protected Vector2 initialOffset = Vector2.zero; protected Vector2 tempSkeletonDisplacement; @@ -94,7 +96,6 @@ namespace Spine.Unity { return; // Root motion is only applied when component is enabled. if (rigidBody2D != null) { - Vector2 gravityAndVelocityMovement = Vector2.zero; if (applyRigidbody2DGravity) { float deltaTime = Time.fixedDeltaTime; @@ -108,10 +109,12 @@ namespace Spine.Unity { rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(transform.position.x, transform.position.y) + rigidbodyDisplacement); } - if (rigidBody != null) { + else if (rigidBody != null) { rigidBody.MovePosition(transform.position + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0)); } + else return; + Vector2 parentBoneScale; GetScaleAffectingRootMotion(out parentBoneScale); ClearEffectiveBoneOffsets(parentBoneScale); @@ -155,6 +158,7 @@ namespace Spine.Unity { if (bone != null) { this.rootMotionBoneIndex = bone.Data.Index; this.rootMotionBone = bone; + FindTransformConstraintsAffectingBone(); } else { Debug.Log("Bone named \"" + name + "\" could not be found."); this.rootMotionBoneIndex = 0; @@ -196,11 +200,70 @@ namespace Spine.Unity { public Vector2 GetAnimationRootMotion (float startTime, float endTime, Animation animation) { - var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex); - if (timeline != null) { - return GetTimelineMovementDelta(startTime, endTime, timeline, animation); + if (startTime == endTime) + 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); } - return Vector2.zero; } public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) { @@ -218,18 +281,18 @@ namespace Spine.Unity { return rootMotion; } - Vector2 GetTimelineMovementDelta (float startTime, float endTime, - TranslateTimeline timeline, Animation animation) { - - Vector2 currentDelta; - if (startTime > endTime) // Looped - currentDelta = (timeline.Evaluate(animation.Duration) - timeline.Evaluate(startTime)) - + (timeline.Evaluate(endTime) - timeline.Evaluate(0)); - else if (startTime != endTime) // Non-looped - currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime); - else - currentDelta = Vector2.zero; - return currentDelta; + void FindTransformConstraintsAffectingBone () { + var constraints = skeletonComponent.Skeleton.TransformConstraints; + var constraintsItems = constraints.Items; + for (int i = 0, n = constraints.Count; i < n; ++i) { + TransformConstraint constraint = constraintsItems[i]; + if (constraint.Bones.Contains(rootMotionBone)) { + transformConstraintIndices.Add(i); + Bone targetBone = constraint.Target; + Vector2 constraintPos = new Vector2(targetBone.X, targetBone.Y); + transformConstraintLastPos.Add(constraintPos); + } + } } 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 parentBoneScale; return GetScaleAffectingRootMotion(out parentBoneScale); @@ -309,6 +385,9 @@ namespace Spine.Unity { } void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) { + + ApplyTransformConstraints(); + // Move top level bones in opposite direction of the root motion bone var skeleton = skeletonComponent.Skeleton; foreach (var topLevelBone in topLevelBones) { @@ -316,8 +395,13 @@ namespace Spine.Unity { if (transformPositionX) topLevelBone.X = displacementSkeletonSpace.x / skeleton.ScaleX; if (transformPositionY) topLevelBone.Y = displacementSkeletonSpace.y / skeleton.ScaleY; } else { - float offsetX = (initialOffset.x - rootMotionBone.X) * parentBoneScale.x; - float offsetY = (initialOffset.y - rootMotionBone.Y) * parentBoneScale.y; + bool useAppliedPosition = transformConstraintIndices.Count > 0; + 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 (transformPositionY) topLevelBone.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs index cce2f9c43..d58be1108 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs @@ -36,7 +36,7 @@ namespace Spine.Unity.AnimationTools { /// Evaluates the resulting value of a TranslateTimeline 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 + /// If no SkeletonData is provided, values are returned as difference to setup pose /// instead of absolute values. public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) { if (time < timeline.Frames[0]) return Vector2.zero; @@ -52,8 +52,18 @@ namespace Spine.Unity.AnimationTools { } } + /// Evaluates the resulting X and Y translate mix values of a + /// TransformConstraintTimeline at a given time. + 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); + } + /// 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. /// This will return null if a TranslateTimeline is not found. public static TranslateTimeline FindTranslateTimelineForBone (this Animation a, int boneIndex) { @@ -67,5 +77,22 @@ namespace Spine.Unity.AnimationTools { } return null; } + + /// 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. + 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; + } } }