Эх сурвалжийг харах

Merge branch '3.8' of https://github.com/esotericsoftware/spine-runtimes into 3.8

badlogic 5 жил өмнө
parent
commit
9ce953a194

+ 1 - 0
CHANGELOG.md

@@ -194,6 +194,7 @@
 		You can leave this parameter disabled when everything is drawn correctly to save the additional performance cost.
   * **Additional Timeline features.** SpineAnimationStateClip now provides a `Speed Multiplier`, a start time offset parameter `Clip In`, support for blending successive animations by overlapping tracks. An additional `Use Blend Duration` parameter *(defaults to true)* allows for automatic synchronisation of MixDuration with the current overlap blend duration. An additional Spine preferences parameter `Use Blend Duration` has been added which can be disabled to default to the previous behaviour before this update.
   * Additional `SpriteMask and RectMask2D` example scene added for demonstration of mask setup and interaction.
+  * `Real physics hinge chains` for both 2D and 3D physics. The [SkeletonUtilityBone](http://esotericsoftware.com/spine-unity#SkeletonUtilityBone) Inspector provides an interface to create 2D and 3D hinge chains. Previously created chains have only been respecting gravity, but not momentum of the skeleton or parent bones. The new physics rig created when pressing `Create 3D Hinge Chain` and `Create 2D Hinge Chain` creates a more complex setup that also works when flipping the skeleton. Note that the chain root node is no longer parented to bones of the skeleton. This is a requirement in Unity to have momentum applied properly - do not reparent the chain root to bones of your skeleton, or you will loose any momentum applied by the skeleton's movement.
 
 * **Changes of default values**
   * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.

+ 210 - 49
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs

@@ -29,6 +29,10 @@
 
 // Contributed by: Mitch Thompson
 
+#if UNITY_2019_2_OR_NEWER
+#define HINGE_JOINT_NEW_BEHAVIOUR
+#endif
+
 using UnityEngine;
 using UnityEditor;
 using System.Collections.Generic;
@@ -39,7 +43,8 @@ namespace Spine.Unity.Editor {
 
 	[CustomEditor(typeof(SkeletonUtilityBone)), CanEditMultipleObjects]
 	public class SkeletonUtilityBoneInspector : UnityEditor.Editor {
-		SerializedProperty mode, boneName, zPosition, position, rotation, scale, overrideAlpha, parentReference;
+		SerializedProperty mode, boneName, zPosition, position, rotation, scale, overrideAlpha, hierarchy, parentReference;
+		GUIContent hierarchyLabel;
 
 		//multi selected flags
 		bool containsFollows, containsOverrides, multiObject;
@@ -59,7 +64,12 @@ namespace Spine.Unity.Editor {
 			rotation = this.serializedObject.FindProperty("rotation");
 			scale = this.serializedObject.FindProperty("scale");
 			overrideAlpha = this.serializedObject.FindProperty("overrideAlpha");
+			hierarchy = this.serializedObject.FindProperty("hierarchy");
+			hierarchyLabel = new GUIContent("Skeleton Utility Parent");
 			parentReference = this.serializedObject.FindProperty("parentReference");
+
+			utilityBone = (SkeletonUtilityBone)target;
+			skeletonUtility = utilityBone.hierarchy;
 			EvaluateFlags();
 
 			if (!utilityBone.valid && skeletonUtility != null && skeletonUtility.skeletonRenderer != null)
@@ -99,9 +109,6 @@ namespace Spine.Unity.Editor {
 		}
 
 		void EvaluateFlags () {
-			utilityBone = (SkeletonUtilityBone)target;
-			skeletonUtility = utilityBone.hierarchy;
-
 			if (Selection.objects.Length == 1) {
 				containsFollows = utilityBone.mode == SkeletonUtilityBone.Mode.Follow;
 				containsOverrides = utilityBone.mode == SkeletonUtilityBone.Mode.Override;
@@ -156,6 +163,7 @@ namespace Spine.Unity.Editor {
 			using (new EditorGUI.DisabledGroupScope(containsFollows)) {
 				EditorGUILayout.PropertyField(overrideAlpha);
 				EditorGUILayout.PropertyField(parentReference);
+				EditorGUILayout.PropertyField(hierarchy, hierarchyLabel);
 			}
 
 			EditorGUILayout.Space();
@@ -292,85 +300,238 @@ namespace Spine.Unity.Editor {
 		}
 
 		void CreateHingeChain2D () {
-			var utilBoneArr = utilityBone.GetComponentsInChildren<SkeletonUtilityBone>();
-
-			foreach (var utilBone in utilBoneArr) {
-				if (utilBone.GetComponent<Collider2D>() == null) {
-					if (utilBone.bone.Data.Length == 0) {
-						var sphere = utilBone.gameObject.AddComponent<CircleCollider2D>();
-						sphere.radius = 0.1f;
-					} else {
-						float length = utilBone.bone.Data.Length;
-						var box = utilBone.gameObject.AddComponent<BoxCollider2D>();
-						box.size = new Vector3(length, length / 3f, 0.2f);
-						box.offset = new Vector3(length / 2f, 0, 0);
-					}
-				}
-
-				utilBone.gameObject.AddComponent<Rigidbody2D>();
+			var kinematicParentUtilityBone = utilityBone.transform.parent.GetComponent<SkeletonUtilityBone>();
+			if (kinematicParentUtilityBone == null) {
+				UnityEditor.EditorUtility.DisplayDialog("No parent SkeletonUtilityBone found!", "Please select the first physically moving chain node, having a parent GameObject with a SkeletonUtilityBone component attached.", "OK");
+				return;
 			}
 
-			utilityBone.GetComponent<Rigidbody2D>().isKinematic = true;
+			float mass = 10;
+			const float rotationLimit = 20.0f;
+
+			SetSkeletonUtilityToFlipByRotation();
+
+			kinematicParentUtilityBone.mode = SkeletonUtilityBone.Mode.Follow;
+			kinematicParentUtilityBone.position = kinematicParentUtilityBone.rotation = kinematicParentUtilityBone.scale = kinematicParentUtilityBone.zPosition = true;
+
+			GameObject commonParentObject = new GameObject(skeletonUtility.name + " HingeChain Parent " + utilityBone.name);
+			var commonParentActivateOnFlip = commonParentObject.AddComponent<ActivateBasedOnFlipDirection>();
+			commonParentActivateOnFlip.skeletonRenderer = skeletonUtility.skeletonRenderer;
+
+			// HingeChain Parent
+			// Needs to be on top hierarchy level (not attached to the moving skeleton at least) for physics to apply proper momentum.
+			GameObject normalChainParentObject = new GameObject("HingeChain");
+			normalChainParentObject.transform.SetParent(commonParentObject.transform);
+			commonParentActivateOnFlip.activeOnNormalX = normalChainParentObject;
+
+			//var followRotationComponent = normalChainParentObject.AddComponent<FollowSkeletonUtilityRootRotation>();
+			//followRotationComponent.reference = skeletonUtility.boneRoot;
+
+			// Follower Kinematic Rigidbody
+			GameObject followerKinematicObject = new GameObject(kinematicParentUtilityBone.name + " Follower");
+			followerKinematicObject.transform.parent = normalChainParentObject.transform;
+			var followerRigidbody = followerKinematicObject.AddComponent<Rigidbody2D>();
+			followerRigidbody.mass = mass;
+			followerRigidbody.isKinematic = true;
+			followerKinematicObject.AddComponent<FollowLocationRigidbody2D>().reference = kinematicParentUtilityBone.transform;
+			followerKinematicObject.transform.position = kinematicParentUtilityBone.transform.position;
+			followerKinematicObject.transform.rotation = kinematicParentUtilityBone.transform.rotation;
+
+			// Child Bones
+			var utilityBones = utilityBone.GetComponentsInChildren<SkeletonUtilityBone>();
+			var childBoneParentReference = followerKinematicObject.transform;
+			for (int i = 0; i < utilityBones.Length; ++i) {
+				var childBone = utilityBones[i];
+				mass *= 0.75f;
+				childBone.parentReference = (i == 0) ? kinematicParentUtilityBone.transform : childBoneParentReference;
+				childBone.transform.SetParent(normalChainParentObject.transform, true); // we need a flat hierarchy of all Joint objects in Unity.
+				AttachRigidbodyAndCollider2D(childBone);
+				childBone.mode = SkeletonUtilityBone.Mode.Override;
+				childBone.scale = childBone.position = childBone.zPosition = false;
+
+				HingeJoint2D joint = childBone.gameObject.AddComponent<HingeJoint2D>();
+				joint.connectedBody = childBoneParentReference.GetComponent<Rigidbody2D>();
+				joint.useLimits = true;
+				ApplyJoint2DAngleLimits(joint, rotationLimit, childBoneParentReference, childBone.transform);
 
-			foreach (var utilBone in utilBoneArr) {
-				if (utilBone == utilityBone)
-					continue;
+				childBone.GetComponent<Rigidbody2D>().mass = mass;
+				childBoneParentReference = childBone.transform;
+			}
 
-				utilBone.mode = SkeletonUtilityBone.Mode.Override;
+			Duplicate2DHierarchyForFlippedChains(normalChainParentObject, commonParentActivateOnFlip, skeletonUtility.transform, rotationLimit);
+			UnityEditor.Selection.activeGameObject = commonParentObject;
+		}
 
-				var joint = utilBone.gameObject.AddComponent<HingeJoint2D>();
-				joint.connectedBody = utilBone.transform.parent.GetComponent<Rigidbody2D>();
-				joint.useLimits = true;
-				joint.limits = new JointAngleLimits2D {
-					min = -20,
-					max = 20
-				};
-				utilBone.GetComponent<Rigidbody2D>().mass = utilBone.transform.parent.GetComponent<Rigidbody2D>().mass * 0.75f;
+		void ApplyJoint2DAngleLimits (HingeJoint2D joint, float rotationLimit, Transform parentBone, Transform bone) {
+		#if HINGE_JOINT_NEW_BEHAVIOUR
+			float referenceAngle = (parentBone.eulerAngles.z - bone.eulerAngles.z + 360f) % 360f;
+			float minAngle = referenceAngle - rotationLimit;
+			float maxAngle = referenceAngle + rotationLimit;
+			if (maxAngle > 270f) {
+				minAngle -= 360f;
+				maxAngle -= 360f;
+			}
+			if (minAngle < -90f) {
+				minAngle += 360f;
+				maxAngle += 360f;
 			}
+#else
+			float minAngle = - rotationLimit;
+			float maxAngle = rotationLimit;
+#endif
+			joint.limits = new JointAngleLimits2D {
+				min = minAngle,
+				max = maxAngle
+			};
 		}
 
-		void CreateHingeChain () {
-			var utilBoneArr = utilityBone.GetComponentsInChildren<SkeletonUtilityBone>();
-
-			foreach (var utilBone in utilBoneArr) {
-				AttachRigidbody(utilBone);
+		void Duplicate2DHierarchyForFlippedChains (GameObject normalChainParentObject, ActivateBasedOnFlipDirection commonParentActivateOnFlip,
+													Transform skeletonUtilityRoot, float rotationLimit) {
+
+			GameObject mirroredChain = GameObject.Instantiate(normalChainParentObject, normalChainParentObject.transform.position,
+				normalChainParentObject.transform.rotation, commonParentActivateOnFlip.transform);
+			mirroredChain.name = normalChainParentObject.name + " FlippedX";
+			
+			commonParentActivateOnFlip.activeOnFlippedX = mirroredChain;
+
+			var followerKinematicObject = mirroredChain.GetComponentInChildren<FollowLocationRigidbody2D>();
+			followerKinematicObject.followFlippedX = true;
+			FlipBone2DHorizontal(followerKinematicObject.transform, skeletonUtilityRoot);
+
+			var childBoneJoints = mirroredChain.GetComponentsInChildren<HingeJoint2D>();
+			Transform prevRotatedChild = null;
+			Transform parentTransformForAngles = followerKinematicObject.transform;
+			for (int i = 0; i < childBoneJoints.Length; ++i) {
+				var joint = childBoneJoints[i];
+				FlipBone2DHorizontal(joint.transform, skeletonUtilityRoot);
+				ApplyJoint2DAngleLimits(joint, rotationLimit, parentTransformForAngles, joint.transform);
+				
+				GameObject rotatedChild = GameObject.Instantiate(joint.gameObject, joint.transform, true);
+				rotatedChild.name = joint.name + " rotated";
+				var rotationEulerAngles = rotatedChild.transform.localEulerAngles;
+				rotationEulerAngles.x = 180;
+				rotatedChild.transform.localEulerAngles = rotationEulerAngles;
+				DestroyImmediate(rotatedChild.GetComponent<HingeJoint2D>());
+				DestroyImmediate(rotatedChild.GetComponent<BoxCollider2D>());
+				DestroyImmediate(rotatedChild.GetComponent<Rigidbody2D>());
+
+				DestroyImmediate(joint.gameObject.GetComponent<SkeletonUtilityBone>());
+
+				if (i > 0) {
+					var utilityBone = rotatedChild.GetComponent<SkeletonUtilityBone>();
+					utilityBone.parentReference = prevRotatedChild;
+				}
+				prevRotatedChild = rotatedChild.transform;
+				parentTransformForAngles = joint.transform;
 			}
 
-			utilityBone.GetComponent<Rigidbody>().isKinematic = true;
+			mirroredChain.SetActive(false);
+		}
+
+		void FlipBone2DHorizontal(Transform bone, Transform mirrorPosition) {
+			Vector3 position = bone.position;
+			position.x = 2 * mirrorPosition.position.x - position.x; // = mirrorPosition + (mirrorPosition - bone.position)
+			bone.position = position;
 
-			foreach (var utilBone in utilBoneArr) {
-				if (utilBone == utilityBone)
-					continue;
+			Vector3 boneZ = bone.forward;
+			Vector3 boneX = bone.right;
+			boneX.x *= -1;
 
-				utilBone.mode = SkeletonUtilityBone.Mode.Override;
+			bone.rotation = Quaternion.LookRotation(boneZ, Vector3.Cross(boneZ, boneX));
+		}
 
-				HingeJoint joint = utilBone.gameObject.AddComponent<HingeJoint>();
+		void CreateHingeChain () {
+			var kinematicParentUtilityBone = utilityBone.transform.parent.GetComponent<SkeletonUtilityBone>();
+			if (kinematicParentUtilityBone == null) {
+				UnityEditor.EditorUtility.DisplayDialog("No parent SkeletonUtilityBone found!", "Please select the first physically moving chain node, having a parent GameObject with a SkeletonUtilityBone component attached.", "OK");
+				return;
+			}
+			
+			SetSkeletonUtilityToFlipByRotation();
+			
+			kinematicParentUtilityBone.mode = SkeletonUtilityBone.Mode.Follow;
+			kinematicParentUtilityBone.position = kinematicParentUtilityBone.rotation = kinematicParentUtilityBone.scale = kinematicParentUtilityBone.zPosition = true;
+
+			// HingeChain Parent
+			// Needs to be on top hierarchy level (not attached to the moving skeleton at least) for physics to apply proper momentum.
+			GameObject chainParentObject = new GameObject(skeletonUtility.name + " HingeChain Parent " + utilityBone.name);
+			var followRotationComponent = chainParentObject.AddComponent<FollowSkeletonUtilityRootRotation>();
+			followRotationComponent.reference = skeletonUtility.boneRoot;
+
+			// Follower Kinematic Rigidbody
+			GameObject followerKinematicObject = new GameObject(kinematicParentUtilityBone.name + " Follower");
+			followerKinematicObject.transform.parent = chainParentObject.transform;
+			var followerRigidbody = followerKinematicObject.AddComponent<Rigidbody>();
+			followerRigidbody.mass = 10;
+			followerRigidbody.isKinematic = true;
+			followerKinematicObject.AddComponent<FollowLocationRigidbody>().reference = kinematicParentUtilityBone.transform;
+			followerKinematicObject.transform.position = kinematicParentUtilityBone.transform.position;
+			followerKinematicObject.transform.rotation = kinematicParentUtilityBone.transform.rotation;
+
+			// Child Bones
+			var utilityBones = utilityBone.GetComponentsInChildren<SkeletonUtilityBone>();
+			var childBoneParentReference = followerKinematicObject.transform;
+			foreach (var childBone in utilityBones) {
+				childBone.parentReference = childBoneParentReference;
+				childBone.transform.SetParent(chainParentObject.transform, true); // we need a flat hierarchy of all Joint objects in Unity.
+				AttachRigidbodyAndCollider(childBone);
+				childBone.mode = SkeletonUtilityBone.Mode.Override;
+				
+				HingeJoint joint = childBone.gameObject.AddComponent<HingeJoint>();
 				joint.axis = Vector3.forward;
-				joint.connectedBody = utilBone.transform.parent.GetComponent<Rigidbody>();
+				joint.connectedBody = childBoneParentReference.GetComponent<Rigidbody>();
 				joint.useLimits = true;
 				joint.limits = new JointLimits {
 					min = -20,
 					max = 20
 				};
-				utilBone.GetComponent<Rigidbody>().mass = utilBone.transform.parent.GetComponent<Rigidbody>().mass * 0.75f;
+				childBone.GetComponent<Rigidbody>().mass = childBoneParentReference.transform.GetComponent<Rigidbody>().mass * 0.75f;
+
+				childBoneParentReference = childBone.transform;
 			}
+			UnityEditor.Selection.activeGameObject = chainParentObject;
 		}
 
-		static void AttachRigidbody (SkeletonUtilityBone utilBone) {
+		void SetSkeletonUtilityToFlipByRotation () {
+			if (!skeletonUtility.flipBy180DegreeRotation) {
+				skeletonUtility.flipBy180DegreeRotation = true;
+				Debug.Log("Set SkeletonUtility " + skeletonUtility.name + " to flip by rotation instead of negative scale (required).", skeletonUtility);
+			}
+		}
+
+		static void AttachRigidbodyAndCollider (SkeletonUtilityBone utilBone, bool enableCollider = false) {
 			if (utilBone.GetComponent<Collider>() == null) {
 				if (utilBone.bone.Data.Length == 0) {
 					SphereCollider sphere = utilBone.gameObject.AddComponent<SphereCollider>();
 					sphere.radius = 0.1f;
+					sphere.enabled = enableCollider;
 				} else {
 					float length = utilBone.bone.Data.Length;
 					BoxCollider box = utilBone.gameObject.AddComponent<BoxCollider>();
 					box.size = new Vector3(length, length / 3f, 0.2f);
 					box.center = new Vector3(length / 2f, 0, 0);
+					box.enabled = enableCollider;
 				}
 			}
-
 			utilBone.gameObject.AddComponent<Rigidbody>();
 		}
-	}
 
+		static void AttachRigidbodyAndCollider2D(SkeletonUtilityBone utilBone, bool enableCollider = false) {
+			if (utilBone.GetComponent<Collider2D>() == null) {
+				if (utilBone.bone.Data.Length == 0) {
+					var sphere = utilBone.gameObject.AddComponent<CircleCollider2D>();
+					sphere.radius = 0.1f;
+					sphere.enabled = enableCollider;
+				}
+				else {
+					float length = utilBone.bone.Data.Length;
+					var box = utilBone.gameObject.AddComponent<BoxCollider2D>();
+					box.size = new Vector3(length, length / 3f, 0.2f);
+					box.offset = new Vector3(length / 2f, 0, 0);
+					box.enabled = enableCollider;
+				}
+			}
+			utilBone.gameObject.AddComponent<Rigidbody2D>();
+		}
+	}
 }

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs

@@ -73,6 +73,7 @@ namespace Spine.Unity.Editor {
 		}
 
 		public override void OnInspectorGUI () {
+
 			#if !NEW_PREFAB_SYSTEM
 			if (isPrefab) {
 				GUILayout.Label(new GUIContent("Cannot edit Prefabs", Icons.warning));
@@ -80,12 +81,21 @@ namespace Spine.Unity.Editor {
 			}
 			#endif
 
+			serializedObject.Update();
+
 			if (!skeletonRenderer.valid) {
 				GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", Icons.warning));
 				return;
 			}
 
 			EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root"));
+			EditorGUILayout.PropertyField(serializedObject.FindProperty("flipBy180DegreeRotation"), SpineInspectorUtility.TempContent("Flip by Rotation", null,
+				"If true, Skeleton.ScaleX and Skeleton.ScaleY are followed " +
+				"by 180 degree rotation. If false, negative Transform scale is used. " +
+				"Note that using negative scale is consistent with previous behaviour (hence the default), " +
+				"however causes serious problems with rigidbodies and physics. Therefore, it is recommended to " +
+				"enable this parameter where possible. When creating hinge chains for a chain of skeleton bones " +
+				"via SkeletonUtilityBone, it is mandatory to have this parameter enabled."));
 
 			bool hasRootBone = skeletonUtility.boneRoot != null;
 
@@ -104,6 +114,8 @@ namespace Spine.Unity.Editor {
 					skeletonUtility.boneRoot = null;
 				}
 			}
+
+			serializedObject.ApplyModifiedProperties();
 		}
 
 		void SpawnHierarchyContextMenu () {

+ 89 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/ActivateBasedOnFlipDirection.cs

@@ -0,0 +1,89 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+
+namespace Spine.Unity {
+
+	/// <summary>
+	/// Utility component to support flipping of 2D hinge chains (chains of HingeJoint2D objects) along
+	/// with the parent skeleton by activating the respective mirrored versions of the hinge chain.
+	/// Note: This component is automatically attached when calling "Create Hinge Chain 2D" at <see cref="SkeletonUtilityBone"/>,
+	/// do not attempt to use this component for other purposes.
+	/// </summary>
+	public class ActivateBasedOnFlipDirection : MonoBehaviour {
+	
+		public SkeletonRenderer skeletonRenderer;
+		public GameObject activeOnNormalX;
+		public GameObject activeOnFlippedX;
+		HingeJoint2D[] jointsNormalX;
+		HingeJoint2D[] jointsFlippedX;
+
+		bool wasFlippedXBefore = false;
+
+		private void Start () {
+			jointsNormalX = activeOnNormalX.GetComponentsInChildren<HingeJoint2D>();
+			jointsFlippedX = activeOnFlippedX.GetComponentsInChildren<HingeJoint2D>();
+		}
+
+		private void FixedUpdate () {
+			bool isFlippedX = (skeletonRenderer.Skeleton.ScaleX < 0);
+			if (isFlippedX != wasFlippedXBefore) {
+				HandleFlip(isFlippedX);
+			}
+			wasFlippedXBefore = isFlippedX;
+		}
+
+		void HandleFlip (bool isFlippedX) {
+			GameObject gameObjectToActivate = isFlippedX ? activeOnFlippedX : activeOnNormalX;
+			GameObject gameObjectToDeactivate = isFlippedX ? activeOnNormalX : activeOnFlippedX;
+
+			gameObjectToActivate.SetActive(true);
+			gameObjectToDeactivate.SetActive(false);
+
+			ResetJointPositions(isFlippedX ? jointsFlippedX : jointsNormalX);
+			ResetJointPositions(isFlippedX ? jointsNormalX : jointsFlippedX);
+			CompensateMovementAfterFlipX(gameObjectToActivate.transform, gameObjectToDeactivate.transform);
+		}
+
+		void ResetJointPositions (HingeJoint2D[] joints) {
+			for (int i = 0; i < joints.Length; ++i) {
+				var joint = joints[i];
+				var parent = joint.connectedBody.transform;
+				joint.transform.position = parent.TransformPoint(joint.connectedAnchor);
+			}
+		}
+
+		void CompensateMovementAfterFlipX (Transform toActivate, Transform toDeactivate) {
+			Transform targetLocation = toDeactivate.GetChild(0);
+			Transform currentLocation = toActivate.GetChild(0);
+			toActivate.position += targetLocation.position - currentLocation.position;
+		}
+	}
+}

+ 11 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/ActivateBasedOnFlipDirection.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 70ae96e4f2feb654681a2f16e4effeec
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 54 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/FollowLocationRigidbody.cs

@@ -0,0 +1,54 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+
+namespace Spine.Unity {
+
+	/// <summary>
+	/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
+	/// 
+	/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>.
+	/// </summary>
+	[RequireComponent(typeof(Rigidbody))]
+	public class FollowLocationRigidbody : MonoBehaviour {
+	
+		public Transform reference;
+		Rigidbody ownRigidbody;
+
+		private void Awake () {
+			ownRigidbody = this.GetComponent<Rigidbody>();
+		}
+
+		void FixedUpdate () {
+			ownRigidbody.rotation = reference.rotation;
+			ownRigidbody.position = reference.position;
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/FollowLocationRigidbody.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 9fc20d5e917562341a5007777a9d0db2
+timeCreated: 1571763023
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 59 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/FollowLocationRigidbody2D.cs

@@ -0,0 +1,59 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+
+namespace Spine.Unity {
+
+	/// <summary>
+	/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
+	/// 
+	/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>.
+	/// </summary>
+	[RequireComponent(typeof(Rigidbody2D))]
+	public class FollowLocationRigidbody2D : MonoBehaviour {
+	
+		public Transform reference;
+		public bool followFlippedX;
+		Rigidbody2D ownRigidbody;
+
+		private void Awake () {
+			ownRigidbody = this.GetComponent<Rigidbody2D>();
+		}
+
+		void FixedUpdate () {
+			if (followFlippedX) {
+				ownRigidbody.rotation = ((-reference.rotation.eulerAngles.z + 270f) % 360f) - 90f;
+			}
+			else
+				ownRigidbody.rotation = reference.rotation.eulerAngles.z;
+			ownRigidbody.position = reference.position;
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/FollowLocationRigidbody2D.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 02aae87c39b869548a9051fbdb1975e6
+timeCreated: 1572012493
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 86 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/FollowSkeletonUtilityRootRotation.cs

@@ -0,0 +1,86 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+
+namespace Spine.Unity {
+
+	/// <summary>
+	/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
+	/// Note that flipping needs to be performed by 180 degree rotation at <see cref="SkeletonUtility"/>,
+	/// by setting <see cref="SkeletonUtility.flipBy180DegreeRotation"/> to true, not via negative scale.
+	///
+	/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>,
+	/// do not attempt to use this component for other purposes.
+	/// </summary>
+	public class FollowSkeletonUtilityRootRotation : MonoBehaviour {
+	
+		const float FLIP_ANGLE_THRESHOLD = 100.0f;
+
+		public Transform reference;
+		Vector3 prevLocalEulerAngles;
+
+		private void Start () {
+			prevLocalEulerAngles = this.transform.localEulerAngles;
+		}
+
+		void FixedUpdate () {
+			this.transform.rotation = reference.rotation;
+
+			bool wasFlippedAroundY = Mathf.Abs(this.transform.localEulerAngles.y - prevLocalEulerAngles.y) > FLIP_ANGLE_THRESHOLD;
+			bool wasFlippedAroundX = Mathf.Abs(this.transform.localEulerAngles.x - prevLocalEulerAngles.x) > FLIP_ANGLE_THRESHOLD;
+			if (wasFlippedAroundY)
+				CompensatePositionToYRotation();
+			if (wasFlippedAroundX)
+				CompensatePositionToXRotation();
+
+			prevLocalEulerAngles = this.transform.localEulerAngles;
+		}
+
+		/// <summary>
+		/// Compensates the position so that a child at the reference position remains in the same place,
+		/// to counter any movement that occurred by rotation.
+		/// </summary>
+		void CompensatePositionToYRotation () {
+			Vector3 newPosition = reference.position + (reference.position - this.transform.position);
+			newPosition.y = this.transform.position.y;
+			this.transform.position = newPosition;
+		}
+
+		/// <summary>
+		/// Compensates the position so that a child at the reference position remains in the same place,
+		/// to counter any movement that occurred by rotation.
+		/// </summary>
+		void CompensatePositionToXRotation () {
+			Vector3 newPosition = reference.position + (reference.position - this.transform.position);
+			newPosition.x = this.transform.position.x;
+			this.transform.position = newPosition;
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/FollowSkeletonUtilityRootRotation.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 456a736ebb92ebf4b959fa9c4b704427
+timeCreated: 1571763206
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 3 - 3
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtilityBone.cs

@@ -63,7 +63,7 @@ namespace Spine.Unity {
 		public float overrideAlpha = 1;
 		#endregion
 
-		[System.NonSerialized] public SkeletonUtility hierarchy;
+		public SkeletonUtility hierarchy;
 		[System.NonSerialized] public Bone bone;
 		[System.NonSerialized] public bool transformLerpComplete;
 		[System.NonSerialized] public bool valid;
@@ -85,7 +85,7 @@ namespace Spine.Unity {
 		}
 
 		void OnEnable () {
-			hierarchy = transform.GetComponentInParent<SkeletonUtility>();
+			if (hierarchy == null) hierarchy = transform.GetComponentInParent<SkeletonUtility>();
 			if (hierarchy == null) return;
 
 			hierarchy.RegisterBone(this);
@@ -230,7 +230,7 @@ namespace Spine.Unity {
 			SkeletonUtility.AddBoundingBoxGameObject(bone.skeleton, skinName, slotName, attachmentName, transform);
 		}
 
-		#if UNITY_EDITOR
+#if UNITY_EDITOR
 		void OnDrawGizmos () {
 			if (IncompatibleTransformMode)
 				Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");