diff --git a/CHANGELOG.md b/CHANGELOG.md index 49c41a2ae..59d655eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -257,6 +257,7 @@ * URP and LWRP `Sprite` and `SkeletonLit` shaders no longer require `Advanced - Add Normals` enabled to properly cast and receive shadows. It is recommended to disable `Add Normals` if normals are otherwise not needed. * Added an example component `RootMotionDeltaCompensation` located in `Spine Examples/Scripts/Sample Components` which can be used for applying simple delta compensation. You can enable and disable the component to toggle delta compensation of the currently playing animation on and off. * Root motion delta compensation now allows to only adjust X or Y components instead of both. Adds two parameters to `SkeletonRootMotionBase.AdjustRootMotionToDistance()` which default to adjusting both X and Y as before. The `RootMotionDeltaCompensation` example component exposes these parameters as public attributes. + * Root motion delta compensation now allows to also add translation root motion to e.g. adjust a horizontal jump upwards or downwards over time. This is necessary because a Y root motion of zero cannot be scaled to become non-zero. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/RootMotionDeltaCompensation.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/RootMotionDeltaCompensation.cs index c1d9f3530..52f5f306e 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/RootMotionDeltaCompensation.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/RootMotionDeltaCompensation.cs @@ -5,14 +5,22 @@ namespace Spine.Unity.Examples { public class RootMotionDeltaCompensation : MonoBehaviour { - protected SkeletonRootMotionBase rootMotion; + [SerializeField] protected SkeletonRootMotionBase rootMotion; public Transform targetPosition; public int trackIndex = 0; public bool adjustX = true; public bool adjustY = true; + public float minScaleX = -999; + public float minScaleY = -999; + public float maxScaleX = 999; + public float maxScaleY = 999; + + public bool allowXTranslation = false; + public bool allowYTranslation = true; void Start () { - rootMotion = this.GetComponent(); + if (rootMotion == null) + rootMotion = this.GetComponent(); } void Update () { @@ -20,12 +28,21 @@ namespace Spine.Unity.Examples { } void OnDisable () { - rootMotion.rootMotionScaleX = rootMotion.rootMotionScaleY = 1; + if (adjustX) + rootMotion.rootMotionScaleX = 1; + if (adjustY) + rootMotion.rootMotionScaleY = 1; + if (allowXTranslation) + rootMotion.rootMotionTranslateXPerY = 0; + if (allowYTranslation) + rootMotion.rootMotionTranslateYPerX = 0; } void AdjustDelta() { Vector3 toTarget = targetPosition.position - this.transform.position; - rootMotion.AdjustRootMotionToDistance(toTarget, trackIndex, adjustX, adjustY); + rootMotion.AdjustRootMotionToDistance(toTarget, trackIndex, adjustX, adjustY, + minScaleX, maxScaleX, minScaleY, maxScaleY, + allowXTranslation, allowYTranslation); } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs index 4f1af9540..8314eb092 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs @@ -39,6 +39,8 @@ namespace Spine.Unity.Editor { protected SerializedProperty transformPositionY; protected SerializedProperty rootMotionScaleX; protected SerializedProperty rootMotionScaleY; + protected SerializedProperty rootMotionTranslateXPerY; + protected SerializedProperty rootMotionTranslateYPerX; protected SerializedProperty rigidBody2D; protected SerializedProperty rigidBody; @@ -47,6 +49,8 @@ namespace Spine.Unity.Editor { protected GUIContent transformPositionYLabel; protected GUIContent rootMotionScaleXLabel; protected GUIContent rootMotionScaleYLabel; + protected GUIContent rootMotionTranslateXPerYLabel; + protected GUIContent rootMotionTranslateYPerXLabel; protected GUIContent rigidBody2DLabel; protected GUIContent rigidBodyLabel; @@ -57,6 +61,8 @@ namespace Spine.Unity.Editor { transformPositionY = serializedObject.FindProperty("transformPositionY"); rootMotionScaleX = serializedObject.FindProperty("rootMotionScaleX"); rootMotionScaleY = serializedObject.FindProperty("rootMotionScaleY"); + rootMotionTranslateXPerY = serializedObject.FindProperty("rootMotionTranslateXPerY"); + rootMotionTranslateYPerX = serializedObject.FindProperty("rootMotionTranslateYPerX"); rigidBody2D = serializedObject.FindProperty("rigidBody2D"); rigidBody = serializedObject.FindProperty("rigidBody"); @@ -65,6 +71,8 @@ namespace Spine.Unity.Editor { transformPositionYLabel = new UnityEngine.GUIContent("Y", "Use the Y-movement 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."); + rootMotionTranslateYPerXLabel = new UnityEngine.GUIContent("Root Motion Translate (Y)", "Added Y translation per root motion X delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal."); rigidBody2DLabel = new UnityEngine.GUIContent("Rigidbody2D", "Optional Rigidbody2D: Assign a Rigidbody2D here if you want " + " to apply the root motion to the rigidbody instead of the Transform." + @@ -92,10 +100,12 @@ namespace Spine.Unity.Editor { EditorGUILayout.PropertyField(rootMotionScaleX, rootMotionScaleXLabel); EditorGUILayout.PropertyField(rootMotionScaleY, rootMotionScaleYLabel); + + EditorGUILayout.PropertyField(rootMotionTranslateXPerY, rootMotionTranslateXPerYLabel); + EditorGUILayout.PropertyField(rootMotionTranslateYPerX, rootMotionTranslateYPerXLabel); } protected virtual void OptionalPropertyFields () { - //EditorGUILayout.LabelField("Optional", EditorStyles.boldLabel); EditorGUILayout.PropertyField(rigidBody2D, rigidBody2DLabel); EditorGUILayout.PropertyField(rigidBody, rigidBodyLabel); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs index e42e8ec27..4a90bdb00 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs @@ -73,6 +73,15 @@ namespace Spine.Unity { return GetAnimationRootMotion(start, end, animation); } + public override RootMotionInfo GetRootMotionInfo (int layerIndex) { + var pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex); + var animation = pair.Key; + var time = pair.Value; + if (animation == null) + return new RootMotionInfo(); + return GetAnimationRootMotionInfo(animation, time); + } + protected override void Reset () { base.Reset(); mecanimLayerFlags = DefaultMecanimLayerFlags; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs index b493af45f..b2293ba60 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs @@ -67,6 +67,16 @@ namespace Spine.Unity { return GetAnimationRootMotion(start, end, animation); } + public override RootMotionInfo GetRootMotionInfo (int trackIndex) { + TrackEntry track = animationState.GetCurrent(trackIndex); + if (track == null) + return new RootMotionInfo(); + + var animation = track.Animation; + float time = track.AnimationTime; + return GetAnimationRootMotionInfo(track.Animation, time); + } + protected override float AdditionalScale { get { return canvas ? canvas.referencePixelsPerUnit: 1.0f; 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 a0b259d78..00c222e2b 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 @@ -30,6 +30,7 @@ using UnityEngine; using System.Collections.Generic; using Spine.Unity.AnimationTools; +using System; namespace Spine.Unity { @@ -47,6 +48,8 @@ namespace Spine.Unity { public float rootMotionScaleX = 1; public float rootMotionScaleY = 1; + public float rootMotionTranslateXPerY = 0; + public float rootMotionTranslateYPerX = 0; [Header("Optional")] public Rigidbody2D rigidBody2D; @@ -117,6 +120,15 @@ namespace Spine.Unity { abstract protected Vector2 CalculateAnimationsMovementDelta (); abstract public Vector2 GetRemainingRootMotion (int trackIndex = 0); + public struct RootMotionInfo { + public Vector2 start; + public Vector2 current; + public Vector2 mid; + public Vector2 end; + public bool timeIsPastMid; + }; + abstract public RootMotionInfo GetRootMotionInfo (int trackIndex = 0); + public void SetRootMotionBone (string name) { var skeleton = skeletonComponent.Skeleton; int index = skeleton.FindBoneIndex(name); @@ -131,16 +143,30 @@ namespace Spine.Unity { } } - public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0, bool adjustX = true, bool adjustY = true) { + public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0, bool adjustX = true, bool adjustY = true, + float minX = 0, float maxX = float.MaxValue, float minY = 0, float maxY = float.MaxValue, + bool allowXTranslation = false, bool allowYTranslation = false) { + + distanceToTarget = (Vector2)transform.InverseTransformVector(distanceToTarget); + + Vector2 scaleAffectingRootMotion = GetScaleAffectingRootMotion(); + distanceToTarget.Scale(new Vector2(1f / scaleAffectingRootMotion.x, 1f / scaleAffectingRootMotion.y)); + Vector2 remainingRootMotion = GetRemainingRootMotion(trackIndex); if (remainingRootMotion.x == 0) remainingRootMotion.x = 0.0001f; if (remainingRootMotion.y == 0) remainingRootMotion.y = 0.0001f; + + if (allowXTranslation) + rootMotionTranslateXPerY = (distanceToTarget.x - remainingRootMotion.x) / Math.Abs(remainingRootMotion.y); + if (allowYTranslation) + rootMotionTranslateYPerX = (distanceToTarget.y - remainingRootMotion.y) / Math.Abs(remainingRootMotion.x); + if (adjustX) - rootMotionScaleX = distanceToTarget.x / remainingRootMotion.x; + rootMotionScaleX = Math.Min(maxX, Math.Max(minX, distanceToTarget.x / remainingRootMotion.x)); if (adjustY) - rootMotionScaleY = distanceToTarget.y / remainingRootMotion.y; + rootMotionScaleY = Math.Min(maxY, Math.Max(minY, distanceToTarget.y / remainingRootMotion.y)); } public Vector2 GetAnimationRootMotion (Animation animation) { @@ -157,6 +183,21 @@ namespace Spine.Unity { return Vector2.zero; } + public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) { + RootMotionInfo rootMotion = new RootMotionInfo(); + var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex); + if (timeline != null) { + float duration = animation.duration; + float mid = duration * 0.5f; + rootMotion.start = timeline.Evaluate(0); + rootMotion.current = timeline.Evaluate(currentTime); + rootMotion.mid = timeline.Evaluate(mid); + rootMotion.end = timeline.Evaluate(duration); + rootMotion.timeIsPastMid = currentTime > mid; + } + return rootMotion; + } + Vector2 GetTimelineMovementDelta (float startTime, float endTime, TranslateTimeline timeline, Animation animation) { @@ -190,9 +231,16 @@ namespace Spine.Unity { ApplyRootMotion(movementDelta, parentBoneScale); } - void AdjustMovementDeltaToConfiguration (ref Vector2 localDelta, out Vector2 parentBoneScale, Skeleton skeleton) { - localDelta.x *= skeleton.ScaleX; - localDelta.y *= skeleton.ScaleY; + Vector2 GetScaleAffectingRootMotion () { + Vector2 parentBoneScale; + return GetScaleAffectingRootMotion(out parentBoneScale); + } + + Vector2 GetScaleAffectingRootMotion (out Vector2 parentBoneScale) { + var skeleton = skeletonComponent.Skeleton; + Vector2 totalScale = Vector2.one; + totalScale.x *= skeleton.ScaleX; + totalScale.y *= skeleton.ScaleY; parentBoneScale = Vector2.one; Bone scaleBone = rootMotionBone; @@ -200,11 +248,23 @@ namespace Spine.Unity { parentBoneScale.x *= scaleBone.ScaleX; parentBoneScale.y *= scaleBone.ScaleY; } - localDelta = Vector2.Scale(localDelta, parentBoneScale); + totalScale = Vector2.Scale(totalScale, parentBoneScale); + totalScale *= AdditionalScale; + return totalScale; + } + + void AdjustMovementDeltaToConfiguration (ref Vector2 localDelta, out Vector2 parentBoneScale, Skeleton skeleton) { + Vector2 totalScale = GetScaleAffectingRootMotion(out parentBoneScale); + localDelta.Scale(totalScale); + + Vector2 rootMotionTranslation = new Vector2( + rootMotionTranslateXPerY * Math.Abs(localDelta.y), + rootMotionTranslateYPerX * Math.Abs(localDelta.x)); - localDelta *= AdditionalScale; localDelta.x *= rootMotionScaleX; localDelta.y *= rootMotionScaleY; + localDelta.x += rootMotionTranslation.x; + localDelta.y += rootMotionTranslation.y; if (!transformPositionX) localDelta.x = 0f; if (!transformPositionY) localDelta.y = 0f;