diff --git a/CHANGELOG.md b/CHANGELOG.md index 9578d901d..9e6e33b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,6 +163,7 @@ b) to an arbitrary directory outside the Assets directory and then open Package Manager in Unity, select the `+` icon, choose `Add package from disk..` and point it to the package.json file. The Project panel should now show an entry `Spine Timeline Extensions` under `Packages`. If the directory is not yet listed, you will need to close and re-open Unity to have it display the directory and its contents. * `SkeletonMecanim`'s `Layer Mix Mode` enum name `MixMode.SpineStyle` has been renamed to `MixMode.Hard`. This is most likely not set via code and thus unlikely to be a problem. Serialized scenes and prefabs are unaffected. + * `SkeletonRootMotion` and `SkeletonMecanimRootMotion` components now support arbitrary bones in the hierarchy as `Root Motion Bone`. Previously there were problems when selecting a non-root bone as `Root Motion Bone`. `Skeleton.ScaleX` and `.ScaleY` and parent bone scale is now respected as well. * **Additions** * **Spine Preferences stored in Assets/Editor/SpineSettings.asset** Now Spine uses the new `SettingsProvider` API, storing settings in a SpineSettings.asset file which can be shared with team members. Your old preferences are automatically migrated to the new system. @@ -255,6 +256,9 @@ * `BoneFollower` and `BoneFollowerGraphic` components now provide better support for following bones when the skeleton's Transform is not the parent of the follower's Transform. Previously e.g. rotating a common parent Transform did not lead to the desired result, as well as negatively scaling a skeleton's Transform when it is not a parent of the follower's Transform. * 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. + * `Attachment.GetRemappedClone(Sprite)` method now provides an additional optional parameter `useOriginalRegionScale`. When set to `true`, the replaced attachment's scale is used instead of the Sprite's `Pixel per Unity` setting, allowing for more consistent scaling. *Note:* When remapping Sprites, be sure to set the Sprite's `Mesh Type` to `Full Rect` and not `Tight`, otherwise the scale will be wrong. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-cpp/spine-cpp/include/spine/SkeletonBinary.h b/spine-cpp/spine-cpp/include/spine/SkeletonBinary.h index 88af8bd77..0a59f3085 100644 --- a/spine-cpp/spine-cpp/include/spine/SkeletonBinary.h +++ b/spine-cpp/spine-cpp/include/spine/SkeletonBinary.h @@ -68,7 +68,7 @@ namespace spine { explicit SkeletonBinary(Atlas* atlasArray); - explicit SkeletonBinary(AttachmentLoader* attachmentLoader); + explicit SkeletonBinary(AttachmentLoader* attachmentLoader, bool ownsLoader = false); ~SkeletonBinary(); diff --git a/spine-cpp/spine-cpp/include/spine/SkeletonJson.h b/spine-cpp/spine-cpp/include/spine/SkeletonJson.h index b719855a4..aeb60f5ba 100644 --- a/spine-cpp/spine-cpp/include/spine/SkeletonJson.h +++ b/spine-cpp/spine-cpp/include/spine/SkeletonJson.h @@ -57,7 +57,7 @@ class SP_API SkeletonJson : public SpineObject { public: explicit SkeletonJson(Atlas *atlas); - explicit SkeletonJson(AttachmentLoader *attachmentLoader); + explicit SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader = false); ~SkeletonJson(); diff --git a/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp b/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp index f26398a77..ac61ab83d 100644 --- a/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp +++ b/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp @@ -98,8 +98,8 @@ SkeletonBinary::SkeletonBinary(Atlas *atlasArray) : _attachmentLoader( } -SkeletonBinary::SkeletonBinary(AttachmentLoader *attachmentLoader) : _attachmentLoader(attachmentLoader), _error(), - _scale(1), _ownsLoader(false) +SkeletonBinary::SkeletonBinary(AttachmentLoader* attachmentLoader, bool ownsLoader) : _attachmentLoader(attachmentLoader), _error(), +_scale(1), _ownsLoader(ownsLoader) { assert(_attachmentLoader != NULL); } diff --git a/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp b/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp index 18be34c90..54f94c6a9 100644 --- a/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp +++ b/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp @@ -84,8 +84,8 @@ SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new(__FILE__, __LIN _scale(1), _ownsLoader(true) {} -SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader) : _attachmentLoader(attachmentLoader), _scale(1), - _ownsLoader(false) +SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader(attachmentLoader), _scale(1), + _ownsLoader(ownsLoader) { assert(_attachmentLoader != NULL); } 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 7731374ab..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,12 +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 () { @@ -18,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); + rootMotion.AdjustRootMotionToDistance(toTarget, trackIndex, adjustX, adjustY, + minScaleX, maxScaleX, minScaleY, maxScaleY, + allowXTranslation, allowYTranslation); } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs index 6614da270..ef9b437e4 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs @@ -136,7 +136,10 @@ namespace Spine.Unity.Editor { if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Set Mipmap Bias to " + SpinePreferences.DEFAULT_MIPMAPBIAS, tooltip: "This may help textures with mipmaps be less blurry when used for 2D sprites."))) { foreach (var m in atlasAsset.materials) { var texture = m.mainTexture; - texture.mipMapBias = SpinePreferences.DEFAULT_MIPMAPBIAS; + string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID()); + var importer = (TextureImporter)TextureImporter.GetAtPath(texturePath); + importer.mipMapBias = SpinePreferences.DEFAULT_MIPMAPBIAS; + EditorUtility.SetDirty(texture); } Debug.Log("Texture mipmap bias set to " + SpinePreferences.DEFAULT_MIPMAPBIAS); } 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 9f7defcd0..a6222fb23 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,10 @@ namespace Spine.Unity { public float rootMotionScaleX = 1; public float rootMotionScaleY = 1; + /// Skeleton space X translation per skeleton space Y translation root motion. + public float rootMotionTranslateXPerY = 0; + /// Skeleton space Y translation per skeleton space X translation root motion. + public float rootMotionTranslateYPerX = 0; [Header("Optional")] public Rigidbody2D rigidBody2D; @@ -61,6 +66,8 @@ namespace Spine.Unity { protected Bone rootMotionBone; protected int rootMotionBoneIndex; protected List topLevelBones = new List(); + protected Vector2 initialOffset = Vector2.zero; + protected Vector2 tempSkeletonDisplacement; protected Vector2 rigidbodyDisplacement; protected virtual void Reset () { @@ -71,10 +78,14 @@ namespace Spine.Unity { skeletonComponent = GetComponent(); GatherTopLevelBones(); SetRootMotionBone(rootMotionBoneName); + if (rootMotionBone != null) + initialOffset = new Vector2(rootMotionBone.x, rootMotionBone.y); var skeletonAnimation = skeletonComponent as ISkeletonAnimation; - if (skeletonAnimation != null) + if (skeletonAnimation != null) { + skeletonAnimation.UpdateLocal -= HandleUpdateLocal; skeletonAnimation.UpdateLocal += HandleUpdateLocal; + } } protected virtual void FixedUpdate () { @@ -89,11 +100,16 @@ namespace Spine.Unity { rigidBody.MovePosition(transform.position + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0)); } + Vector2 parentBoneScale; + GetScaleAffectingRootMotion(out parentBoneScale); + ClearEffectiveBoneOffsets(parentBoneScale); rigidbodyDisplacement = Vector2.zero; + tempSkeletonDisplacement = Vector2.zero; } protected virtual void OnDisable () { rigidbodyDisplacement = Vector2.zero; + tempSkeletonDisplacement = Vector2.zero; } protected void FindRigidbodyComponent () { @@ -112,6 +128,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); @@ -126,14 +151,31 @@ namespace Spine.Unity { } } - public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0) { - Vector2 remainingRootMotion = GetRemainingRootMotion(trackIndex); - if (remainingRootMotion.x == 0) - remainingRootMotion.x = 0.0001f; - if (remainingRootMotion.y == 0) - remainingRootMotion.y = 0.0001f; - rootMotionScaleX = distanceToTarget.x / remainingRootMotion.x; - rootMotionScaleY = distanceToTarget.y / remainingRootMotion.y; + 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) { + + Vector2 distanceToTargetSkeletonSpace = (Vector2)transform.InverseTransformVector(distanceToTarget); + Vector2 scaleAffectingRootMotion = GetScaleAffectingRootMotion(); + if (UsesRigidbody) + distanceToTargetSkeletonSpace -= tempSkeletonDisplacement; + + Vector2 remainingRootMotionSkeletonSpace = GetRemainingRootMotion(trackIndex); + remainingRootMotionSkeletonSpace.Scale(scaleAffectingRootMotion); + if (remainingRootMotionSkeletonSpace.x == 0) + remainingRootMotionSkeletonSpace.x = 0.0001f; + if (remainingRootMotionSkeletonSpace.y == 0) + remainingRootMotionSkeletonSpace.y = 0.0001f; + + if (adjustX) + rootMotionScaleX = Math.Min(maxX, Math.Max(minX, distanceToTargetSkeletonSpace.x / remainingRootMotionSkeletonSpace.x)); + if (adjustY) + rootMotionScaleY = Math.Min(maxY, Math.Max(minY, distanceToTargetSkeletonSpace.y / remainingRootMotionSkeletonSpace.y)); + + if (allowXTranslation) + rootMotionTranslateXPerY = (distanceToTargetSkeletonSpace.x - remainingRootMotionSkeletonSpace.x * rootMotionScaleX) / remainingRootMotionSkeletonSpace.y; + if (allowYTranslation) + rootMotionTranslateYPerX = (distanceToTargetSkeletonSpace.y - remainingRootMotionSkeletonSpace.y * rootMotionScaleY) / remainingRootMotionSkeletonSpace.x; } public Vector2 GetAnimationRootMotion (Animation animation) { @@ -150,6 +192,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) { @@ -177,38 +234,89 @@ namespace Spine.Unity { if (!this.isActiveAndEnabled) return; // Root motion is only applied when component is enabled. - var movementDelta = CalculateAnimationsMovementDelta(); - AdjustMovementDeltaToConfiguration(ref movementDelta, animatedSkeletonComponent.Skeleton); - ApplyRootMotion(movementDelta); + var boneLocalDelta = CalculateAnimationsMovementDelta(); + Vector2 parentBoneScale; + Vector2 skeletonDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale); + ApplyRootMotion(skeletonDelta, parentBoneScale); } - void AdjustMovementDeltaToConfiguration (ref Vector2 localDelta, Skeleton skeleton) { - if (skeleton.ScaleX < 0) localDelta.x = -localDelta.x; - if (skeleton.ScaleY < 0) localDelta.y = -localDelta.y; - if (!transformPositionX) localDelta.x = 0f; - if (!transformPositionY) localDelta.y = 0f; - } - - void ApplyRootMotion (Vector2 localDelta) { - localDelta *= AdditionalScale; - localDelta.x *= rootMotionScaleX; - localDelta.y *= rootMotionScaleY; - + void ApplyRootMotion (Vector2 skeletonDelta, Vector2 parentBoneScale) { // Apply root motion to Transform or RigidBody; if (UsesRigidbody) { - rigidbodyDisplacement += (Vector2)transform.TransformVector(localDelta); - // Accumulated displacement is applied on the next Physics update (FixedUpdate) + rigidbodyDisplacement += (Vector2)transform.TransformVector(skeletonDelta); + + // 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); } else { - - transform.position += transform.TransformVector(localDelta); + transform.position += transform.TransformVector(skeletonDelta); + ClearEffectiveBoneOffsets(parentBoneScale); } + } + 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; + while ((scaleBone = scaleBone.parent) != null) { + parentBoneScale.x *= scaleBone.ScaleX; + parentBoneScale.y *= scaleBone.ScaleY; + } + totalScale = Vector2.Scale(totalScale, parentBoneScale); + totalScale *= AdditionalScale; + return totalScale; + } + + Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale) { + Vector2 skeletonDelta = boneLocalDelta; + Vector2 totalScale = GetScaleAffectingRootMotion(out parentBoneScale); + skeletonDelta.Scale(totalScale); + + Vector2 rootMotionTranslation = new Vector2( + rootMotionTranslateXPerY * skeletonDelta.y, + rootMotionTranslateYPerX * skeletonDelta.x); + + skeletonDelta.x *= rootMotionScaleX; + skeletonDelta.y *= rootMotionScaleY; + skeletonDelta.x += rootMotionTranslation.x; + skeletonDelta.y += rootMotionTranslation.y; + + if (!transformPositionX) skeletonDelta.x = 0f; + if (!transformPositionY) skeletonDelta.y = 0f; + return skeletonDelta; + } + + void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) { // Move top level bones in opposite direction of the root motion bone + var skeleton = skeletonComponent.Skeleton; foreach (var topLevelBone in topLevelBones) { - if (transformPositionX) topLevelBone.x -= rootMotionBone.x; - if (transformPositionY) topLevelBone.y -= rootMotionBone.y; + if (topLevelBone == rootMotionBone) { + 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; + if (transformPositionX) topLevelBone.x = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX; + if (transformPositionY) topLevelBone.y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY; + } } } + + void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) { + SetEffectiveBoneOffsetsTo(Vector2.zero, parentBoneScale); + } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index d01fb7523..38ef41147 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -202,6 +202,7 @@ namespace Spine.Unity { protected override void Awake () { base.Awake (); + updateMode = updateWhenInvisible; SyncRawImagesWithCanvasRenderers(); if (!this.IsValid) { #if UNITY_EDITOR diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index fc29fab9e..332ece243 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -280,6 +280,7 @@ namespace Spine.Unity { public virtual void Awake () { Initialize(false); + updateMode = updateWhenInvisible; } #if UNITY_EDITOR && CONFIGURABLE_ENTER_PLAY_MODE @@ -690,6 +691,9 @@ namespace Spine.Unity { } for (int i = 0; i < meshRenderer.sharedMaterials.Length; ++i) { + if (!meshRenderer.sharedMaterials[i]) + continue; + if (!hasPerRendererBlock) meshRenderer.GetPropertyBlock(reusedPropertyBlock, i); // Note: this parameter shall not exist at any shader, then Unity will create separate // material instances (not in terms of memory cost or leakage). diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentCloneExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentCloneExtensions.cs index 52c4ba1ad..2629c41f9 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentCloneExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentCloneExtensions.cs @@ -86,16 +86,25 @@ namespace Spine.Unity.AttachmentTools { /// If true and the original Attachment is a MeshAttachment, then /// a non-central sprite pivot will shift uv coords in the opposite direction. Vertices will not be offset in /// any case when the original Attachment is a MeshAttachment. + /// If true and the original Attachment is a RegionAttachment, then + /// the original region's scale value is used instead of the Sprite's pixels per unit property. Since uniform scale is used, + /// x scale of the original attachment (width scale) is used, scale in y direction (height scale) is ignored. public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial, bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false, - bool pivotShiftsMeshUVCoords = true) { + bool pivotShiftsMeshUVCoords = true, bool useOriginalRegionScale = false) { var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture } ); if (!pivotShiftsMeshUVCoords && o is MeshAttachment) { // prevent non-central sprite pivot setting offsetX/Y and shifting uv coords out of mesh bounds atlasRegion.offsetX = 0; atlasRegion.offsetY = 0; } - return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, 1f/sprite.pixelsPerUnit); + float scale = 1f / sprite.pixelsPerUnit; + if (useOriginalRegionScale) { + var regionAttachment = o as RegionAttachment; + if (regionAttachment != null) + scale = regionAttachment.width / regionAttachment.regionOriginalWidth; + } + return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, scale); } ///