From 3bf9c3508fc46ee84f4d6db4e4fbfe91032ef4a4 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Thu, 11 Feb 2021 17:36:29 +0100 Subject: [PATCH] [unity] Fixed BoneFollower and BoneFollowerGraphic not reacting correctly to parent Transform rotation, as well as to negative Transform scale of a non-parent skeleton Transform. Closes #1837. --- CHANGELOG.md | 1 + .../BoneFollowerGraphicInspector.cs | 9 +++- .../Components/BoneFollowerInspector.cs | 9 +++- .../Components/Following/BoneFollower.cs | 42 +++++++++++++++---- .../Following/BoneFollowerGraphic.cs | 41 +++++++++++++----- 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a86982e75..d77668b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -251,6 +251,7 @@ * Now all URP (Universal Render Pipeline) and LWRP (Lightweight Render Pipeline) shaders support SRP (Scriptable Render Pipeline) batching. See [Unity SRPBatcher documentation pages](https://docs.unity3d.com/Manual/SRPBatcher.html) for additional information. * Sprite shaders now provide four `Diffuse Ramp` modes as an Inspector Material parameter: `Hard`, `Soft`, `Old Hard` and `Old Soft`. In spine-unity 3.8 it defaults to `Old Hard` to keep the behaviour of existing projects unchanged. Note that `Old Hard` and `Old Soft` ramp versions were using only the right half of the ramp texture, and additionally multiplying the light intensity by 2, both leading to brighter lighting than without a ramp texture active. The new ramp modes `Hard` and `Soft` use the full ramp texture and do not modify light intensity, being consistent with lighting without a ramp texture active. * Added **native support for slot blend modes** `Additive`, `Multiply` and `Screen` with automatic assignment at newly imported skeleton assets. `BlendModeMaterialAssets` are now obsolete and replaced by the native properties at `SkeletonDataAsset`. The `SkeletonDataAsset` Inspector provides a new `Blend Modes - Upgrade` button to upgrade an obsolete `BlendModeMaterialAsset` to the native blend modes properties. This upgrade will be performed automatically on imported and re-imported assets in Unity 2020.1 and newer to prevent reported `BlendModeMaterialAsset` issues in these Unity versions. spine-unity 4.0 and newer will automatically perform this upgrade regardless of the Unity version. + * `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. * **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/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs index 53570bb09..1603385f6 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs @@ -39,7 +39,8 @@ namespace Spine.Unity.Editor { [CustomEditor(typeof(BoneFollowerGraphic)), CanEditMultipleObjects] public class BoneFollowerGraphicInspector : Editor { - SerializedProperty boneName, skeletonGraphic, followXYPosition, followZPosition, followBoneRotation, followLocalScale, followSkeletonFlip; + SerializedProperty boneName, skeletonGraphic, followXYPosition, followZPosition, followBoneRotation, + followLocalScale, followSkeletonFlip, maintainedAxisOrientation; BoneFollowerGraphic targetBoneFollower; bool needsReset; @@ -77,6 +78,7 @@ namespace Spine.Unity.Editor { followZPosition = serializedObject.FindProperty("followZPosition"); followLocalScale = serializedObject.FindProperty("followLocalScale"); followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip"); + maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation"); targetBoneFollower = (BoneFollowerGraphic)target; if (targetBoneFollower.SkeletonGraphic != null) @@ -171,6 +173,11 @@ namespace Spine.Unity.Editor { EditorGUILayout.PropertyField(followZPosition); EditorGUILayout.PropertyField(followLocalScale); EditorGUILayout.PropertyField(followSkeletonFlip); + if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) && + (followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) { + using (new SpineInspectorUtility.IndentScope()) + EditorGUILayout.PropertyField(maintainedAxisOrientation); + } //BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower); } else { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs index 4bdae2e6a..17cc73978 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs @@ -37,7 +37,8 @@ namespace Spine.Unity.Editor { [CustomEditor(typeof(BoneFollower)), CanEditMultipleObjects] public class BoneFollowerInspector : Editor { - SerializedProperty boneName, skeletonRenderer, followXYPosition, followZPosition, followBoneRotation, followLocalScale, followSkeletonFlip; + SerializedProperty boneName, skeletonRenderer, followXYPosition, followZPosition, followBoneRotation, + followLocalScale, followSkeletonFlip, maintainedAxisOrientation; BoneFollower targetBoneFollower; bool needsReset; @@ -86,6 +87,7 @@ namespace Spine.Unity.Editor { followZPosition = serializedObject.FindProperty("followZPosition"); followLocalScale = serializedObject.FindProperty("followLocalScale"); followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip"); + maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation"); targetBoneFollower = (BoneFollower)target; if (targetBoneFollower.SkeletonRenderer != null) @@ -177,6 +179,11 @@ namespace Spine.Unity.Editor { EditorGUILayout.PropertyField(followZPosition); EditorGUILayout.PropertyField(followLocalScale); EditorGUILayout.PropertyField(followSkeletonFlip); + if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) && + (followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) { + using (new SpineInspectorUtility.IndentScope()) + EditorGUILayout.PropertyField(maintainedAxisOrientation); + } BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower); } else { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs index 4938e15f1..cb75c432c 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs @@ -70,6 +70,16 @@ namespace Spine.Unity { [Tooltip("Follows the target bone's local scale. BoneFollower cannot inherit world/skewed scale because of UnityEngine.Transform property limitations.")] public bool followLocalScale = false; + public enum AxisOrientation { + XAxis = 1, + YAxis + } + [Tooltip("Applies when 'Follow Skeleton Flip' is disabled but 'Follow Bone Rotation' is enabled." + + " When flipping the skeleton by scaling its Transform, this follower's rotation is adjusted" + + " instead of its scale to follow the bone orientation. When one of the axes is flipped, " + + " only one axis can be followed, either the X or the Y axis, which is selected here.")] + public AxisOrientation maintainedAxisOrientation = AxisOrientation.XAxis; + [UnityEngine.Serialization.FormerlySerializedAs("resetOnAwake")] public bool initializeOnAwake = true; #endregion @@ -142,6 +152,7 @@ namespace Spine.Unity { } Transform thisTransform = this.transform; + float additionalFlipScale = 1; if (skeletonTransformIsParent) { // Recommended setup: Use local transform properties if Spine GameObject is the immediate parent thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX : thisTransform.localPosition.x, @@ -166,26 +177,39 @@ namespace Spine.Unity { targetWorldPosition.y = thisTransform.position.y; } - float boneWorldRotation = bone.WorldRotationX; - + Vector3 skeletonLossyScale = skeletonTransform.lossyScale; Transform transformParent = thisTransform.parent; - if (transformParent != null) { - Matrix4x4 m = transformParent.localToWorldMatrix; - if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative - boneWorldRotation = -boneWorldRotation; - } - + Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one; if (followBoneRotation) { + float boneWorldRotation = bone.WorldRotationX; + + if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0) + boneWorldRotation = -boneWorldRotation; + + if (followSkeletonFlip || maintainedAxisOrientation == AxisOrientation.XAxis) { + if ((skeletonLossyScale.x * parentLossyScale.x < 0)) + boneWorldRotation += 180f; + } + else { + if ((skeletonLossyScale.y * parentLossyScale.y < 0)) + boneWorldRotation += 180f; + } + Vector3 worldRotation = skeletonTransform.rotation.eulerAngles; if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f; thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation)); } else { thisTransform.position = targetWorldPosition; } + + additionalFlipScale = Mathf.Sign(skeletonLossyScale.x * parentLossyScale.x + * skeletonLossyScale.y * parentLossyScale.y); } Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f); - if (followSkeletonFlip) localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY); + if (followSkeletonFlip) + localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY) * additionalFlipScale; + thisTransform.localScale = localScale; } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs index 54bdb8b7d..6d5ea7bef 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs @@ -33,7 +33,9 @@ using UnityEngine; + namespace Spine.Unity { + using AxisOrientation = BoneFollower.AxisOrientation; #if NEW_PREFAB_SYSTEM [ExecuteAlways] @@ -66,6 +68,11 @@ namespace Spine.Unity { public bool followLocalScale = false; public bool followXYPosition = true; public bool followZPosition = true; + [Tooltip("Applies when 'Follow Skeleton Flip' is disabled but 'Follow Bone Rotation' is enabled." + + " When flipping the skeleton by scaling its Transform, this follower's rotation is adjusted" + + " instead of its scale to follow the bone orientation. When one of the axes is flipped, " + + " only one axis can be followed, either the X or the Y axis, which is selected here.")] + public AxisOrientation maintainedAxisOrientation = AxisOrientation.XAxis; [System.NonSerialized] public Bone bone; @@ -134,6 +141,7 @@ namespace Spine.Unity { if (canvas == null) canvas = skeletonGraphic.GetComponentInParent(); float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f; + float additionalFlipScale = 1; if (skeletonTransformIsParent) { // Recommended setup: Use local transform properties if Spine GameObject is the immediate parent thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX * scale : thisTransform.localPosition.x, @@ -149,25 +157,38 @@ namespace Spine.Unity { targetWorldPosition.y = thisTransform.position.y; } - float boneWorldRotation = bone.WorldRotationX; - + Vector3 skeletonLossyScale = skeletonTransform.lossyScale; Transform transformParent = thisTransform.parent; - if (transformParent != null) { - Matrix4x4 m = transformParent.localToWorldMatrix; - if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative - boneWorldRotation = -boneWorldRotation; - } - + Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one; if (followBoneRotation) { + float boneWorldRotation = bone.WorldRotationX; + + if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0) + boneWorldRotation = -boneWorldRotation; + + if (followSkeletonFlip || maintainedAxisOrientation == AxisOrientation.XAxis) { + if ((skeletonLossyScale.x * parentLossyScale.x < 0)) + boneWorldRotation += 180f; + } + else { + if ((skeletonLossyScale.y * parentLossyScale.y < 0)) + boneWorldRotation += 180f; + } + Vector3 worldRotation = skeletonTransform.rotation.eulerAngles; - thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, skeletonTransform.rotation.eulerAngles.z + boneWorldRotation)); + if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f; + thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation)); } else { thisTransform.position = targetWorldPosition; } + + additionalFlipScale = Mathf.Sign(skeletonLossyScale.x * parentLossyScale.x + * skeletonLossyScale.y * parentLossyScale.y); } Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f); - if (followSkeletonFlip) localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY); + if (followSkeletonFlip) + localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY) * additionalFlipScale; thisTransform.localScale = localScale; }