Explorar o código

[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.

Harald Csaszar %!s(int64=4) %!d(string=hai) anos
pai
achega
3bf9c3508f

+ 1 - 0
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.
   * 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.
   * 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.
   * 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**
 * **Changes of default values**
   * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.
   * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.

+ 8 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs

@@ -39,7 +39,8 @@ namespace Spine.Unity.Editor {
 	[CustomEditor(typeof(BoneFollowerGraphic)), CanEditMultipleObjects]
 	[CustomEditor(typeof(BoneFollowerGraphic)), CanEditMultipleObjects]
 	public class BoneFollowerGraphicInspector : Editor {
 	public class BoneFollowerGraphicInspector : Editor {
 
 
-		SerializedProperty boneName, skeletonGraphic, followXYPosition, followZPosition, followBoneRotation, followLocalScale, followSkeletonFlip;
+		SerializedProperty boneName, skeletonGraphic, followXYPosition, followZPosition, followBoneRotation,
+			followLocalScale, followSkeletonFlip, maintainedAxisOrientation;
 		BoneFollowerGraphic targetBoneFollower;
 		BoneFollowerGraphic targetBoneFollower;
 		bool needsReset;
 		bool needsReset;
 
 
@@ -77,6 +78,7 @@ namespace Spine.Unity.Editor {
 			followZPosition = serializedObject.FindProperty("followZPosition");
 			followZPosition = serializedObject.FindProperty("followZPosition");
 			followLocalScale = serializedObject.FindProperty("followLocalScale");
 			followLocalScale = serializedObject.FindProperty("followLocalScale");
 			followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
 			followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
+			maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation");
 
 
 			targetBoneFollower = (BoneFollowerGraphic)target;
 			targetBoneFollower = (BoneFollowerGraphic)target;
 			if (targetBoneFollower.SkeletonGraphic != null)
 			if (targetBoneFollower.SkeletonGraphic != null)
@@ -171,6 +173,11 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.PropertyField(followZPosition);
 				EditorGUILayout.PropertyField(followZPosition);
 				EditorGUILayout.PropertyField(followLocalScale);
 				EditorGUILayout.PropertyField(followLocalScale);
 				EditorGUILayout.PropertyField(followSkeletonFlip);
 				EditorGUILayout.PropertyField(followSkeletonFlip);
+				if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) &&
+					(followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) {
+					using (new SpineInspectorUtility.IndentScope())
+						EditorGUILayout.PropertyField(maintainedAxisOrientation);
+				}
 
 
 				//BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
 				//BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
 			} else {
 			} else {

+ 8 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs

@@ -37,7 +37,8 @@ namespace Spine.Unity.Editor {
 
 
 	[CustomEditor(typeof(BoneFollower)), CanEditMultipleObjects]
 	[CustomEditor(typeof(BoneFollower)), CanEditMultipleObjects]
 	public class BoneFollowerInspector : Editor {
 	public class BoneFollowerInspector : Editor {
-		SerializedProperty boneName, skeletonRenderer, followXYPosition, followZPosition, followBoneRotation, followLocalScale, followSkeletonFlip;
+		SerializedProperty boneName, skeletonRenderer, followXYPosition, followZPosition, followBoneRotation,
+			followLocalScale, followSkeletonFlip, maintainedAxisOrientation;
 		BoneFollower targetBoneFollower;
 		BoneFollower targetBoneFollower;
 		bool needsReset;
 		bool needsReset;
 
 
@@ -86,6 +87,7 @@ namespace Spine.Unity.Editor {
 			followZPosition = serializedObject.FindProperty("followZPosition");
 			followZPosition = serializedObject.FindProperty("followZPosition");
 			followLocalScale = serializedObject.FindProperty("followLocalScale");
 			followLocalScale = serializedObject.FindProperty("followLocalScale");
 			followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
 			followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
+			maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation");
 
 
 			targetBoneFollower = (BoneFollower)target;
 			targetBoneFollower = (BoneFollower)target;
 			if (targetBoneFollower.SkeletonRenderer != null)
 			if (targetBoneFollower.SkeletonRenderer != null)
@@ -177,6 +179,11 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.PropertyField(followZPosition);
 				EditorGUILayout.PropertyField(followZPosition);
 				EditorGUILayout.PropertyField(followLocalScale);
 				EditorGUILayout.PropertyField(followLocalScale);
 				EditorGUILayout.PropertyField(followSkeletonFlip);
 				EditorGUILayout.PropertyField(followSkeletonFlip);
+				if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) &&
+					(followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) {
+					using (new SpineInspectorUtility.IndentScope())
+						EditorGUILayout.PropertyField(maintainedAxisOrientation);
+				}
 
 
 				BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
 				BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
 			} else {
 			} else {

+ 32 - 8
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.")]
 		[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 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")]
 		[UnityEngine.Serialization.FormerlySerializedAs("resetOnAwake")]
 		public bool initializeOnAwake = true;
 		public bool initializeOnAwake = true;
 		#endregion
 		#endregion
@@ -142,6 +152,7 @@ namespace Spine.Unity {
 			}
 			}
 
 
 			Transform thisTransform = this.transform;
 			Transform thisTransform = this.transform;
+			float additionalFlipScale = 1;
 			if (skeletonTransformIsParent) {
 			if (skeletonTransformIsParent) {
 				// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
 				// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
 				thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX : thisTransform.localPosition.x,
 				thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX : thisTransform.localPosition.x,
@@ -166,26 +177,39 @@ namespace Spine.Unity {
 					targetWorldPosition.y = thisTransform.position.y;
 					targetWorldPosition.y = thisTransform.position.y;
 				}
 				}
 
 
-				float boneWorldRotation = bone.WorldRotationX;
-
+				Vector3 skeletonLossyScale = skeletonTransform.lossyScale;
 				Transform transformParent = thisTransform.parent;
 				Transform transformParent = thisTransform.parent;
-				if (transformParent != null) {
-					Matrix4x4 m = transformParent.localToWorldMatrix;
-					if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative
+				Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
+				if (followBoneRotation) {
+					float boneWorldRotation = bone.WorldRotationX;
+
+					if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
 						boneWorldRotation = -boneWorldRotation;
 						boneWorldRotation = -boneWorldRotation;
-				}
 
 
-				if (followBoneRotation) {
+					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;
 					Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
 					if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f;
 					if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f;
 					thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
 					thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
 				} else {
 				} else {
 					thisTransform.position = targetWorldPosition;
 					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);
 			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;
 			thisTransform.localScale = localScale;
 		}
 		}
 	}
 	}

+ 30 - 9
spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs

@@ -33,7 +33,9 @@
 
 
 using UnityEngine;
 using UnityEngine;
 
 
+
 namespace Spine.Unity {
 namespace Spine.Unity {
+	using AxisOrientation = BoneFollower.AxisOrientation;
 
 
 	#if NEW_PREFAB_SYSTEM
 	#if NEW_PREFAB_SYSTEM
 	[ExecuteAlways]
 	[ExecuteAlways]
@@ -66,6 +68,11 @@ namespace Spine.Unity {
 		public bool followLocalScale = false;
 		public bool followLocalScale = false;
 		public bool followXYPosition = true;
 		public bool followXYPosition = true;
 		public bool followZPosition = 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;
 		[System.NonSerialized] public Bone bone;
 
 
@@ -134,6 +141,7 @@ namespace Spine.Unity {
 			if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
 			if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
 			float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f;
 			float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f;
 
 
+			float additionalFlipScale = 1;
 			if (skeletonTransformIsParent) {
 			if (skeletonTransformIsParent) {
 				// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
 				// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
 				thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX * scale : thisTransform.localPosition.x,
 				thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX * scale : thisTransform.localPosition.x,
@@ -149,25 +157,38 @@ namespace Spine.Unity {
 					targetWorldPosition.y = thisTransform.position.y;
 					targetWorldPosition.y = thisTransform.position.y;
 				}
 				}
 
 
-				float boneWorldRotation = bone.WorldRotationX;
-
+				Vector3 skeletonLossyScale = skeletonTransform.lossyScale;
 				Transform transformParent = thisTransform.parent;
 				Transform transformParent = thisTransform.parent;
-				if (transformParent != null) {
-					Matrix4x4 m = transformParent.localToWorldMatrix;
-					if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative
+				Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
+				if (followBoneRotation) {
+					float boneWorldRotation = bone.WorldRotationX;
+
+					if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
 						boneWorldRotation = -boneWorldRotation;
 						boneWorldRotation = -boneWorldRotation;
-				}
 
 
-				if (followBoneRotation) {
+					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;
 					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 {
 				} else {
 					thisTransform.position = targetWorldPosition;
 					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);
 			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;
 			thisTransform.localScale = localScale;
 		}
 		}