diff --git a/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdoll2DInspector.cs b/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdoll2DInspector.cs index 649345d0e..a5495946e 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdoll2DInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdoll2DInspector.cs @@ -7,47 +7,5 @@ using UnityEngine; using UnityEditor; namespace Spine.Unity.Modules { - [CustomEditor(typeof(SkeletonRagdoll2D))] - public class SkeletonRagdoll2DInspector : UnityEditor.Editor { - SerializedProperty startingBoneName, stopBoneNames, applyOnStart, pinStartBone, enableJointCollision, gravityScale, disableIK, thickness, rotationLimit, colliderLayer, mix, rootMass, massFalloffFactor; - - void OnEnable () { - startingBoneName = serializedObject.FindProperty("startingBoneName"); - stopBoneNames = serializedObject.FindProperty("stopBoneNames"); - applyOnStart = serializedObject.FindProperty("applyOnStart"); - pinStartBone = serializedObject.FindProperty("pinStartBone"); - gravityScale = serializedObject.FindProperty("gravityScale"); - disableIK = serializedObject.FindProperty("disableIK"); - thickness = serializedObject.FindProperty("thickness"); - rotationLimit = serializedObject.FindProperty("rotationLimit"); - colliderLayer = serializedObject.FindProperty("colliderLayer"); - mix = serializedObject.FindProperty("mix"); - rootMass = serializedObject.FindProperty("rootMass"); - massFalloffFactor = serializedObject.FindProperty("massFalloffFactor"); - } - - public override void OnInspectorGUI () { - EditorGUILayout.PropertyField(startingBoneName); - EditorGUILayout.PropertyField(stopBoneNames, true); - EditorGUILayout.PropertyField(applyOnStart); - EditorGUILayout.PropertyField(pinStartBone); - EditorGUILayout.PropertyField(gravityScale); - EditorGUILayout.PropertyField(disableIK); - EditorGUILayout.PropertyField(thickness); - EditorGUILayout.PropertyField(rotationLimit); - EditorGUILayout.PropertyField(rootMass); - EditorGUILayout.PropertyField(massFalloffFactor); - colliderLayer.intValue = EditorGUILayout.LayerField(colliderLayer.displayName, colliderLayer.intValue); - EditorGUILayout.PropertyField(mix); - - - serializedObject.ApplyModifiedProperties(); - } - - void Header (string name) { - GUILayout.Space(20); - EditorGUILayout.LabelField(name, EditorStyles.boldLabel); - } - } - + public class SkeletonRagdoll2DInspector {} } diff --git a/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdollInspector.cs b/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdollInspector.cs index bac20be1b..ee2828665 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdollInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdollInspector.cs @@ -5,51 +5,15 @@ using UnityEngine; using UnityEditor; -using System.Collections; -using System.Collections.Generic; namespace Spine.Unity.Modules { - [CustomEditor(typeof(SkeletonRagdoll))] + public class SkeletonRagdollInspector : UnityEditor.Editor { - SerializedProperty startingBoneName, stopBoneNames, applyOnStart, pinStartBone, enableJointCollision, useGravity, disableIK, thickness, rotationLimit, colliderLayer, mix, rootMass, massFalloffFactor; - - void OnEnable () { - startingBoneName = serializedObject.FindProperty("startingBoneName"); - stopBoneNames = serializedObject.FindProperty("stopBoneNames"); - applyOnStart = serializedObject.FindProperty("applyOnStart"); - pinStartBone = serializedObject.FindProperty("pinStartBone"); - enableJointCollision = serializedObject.FindProperty("enableJointCollision"); - useGravity = serializedObject.FindProperty("useGravity"); - disableIK = serializedObject.FindProperty("disableIK"); - thickness = serializedObject.FindProperty("thickness"); - rotationLimit = serializedObject.FindProperty("rotationLimit"); - colliderLayer = serializedObject.FindProperty("colliderLayer"); - mix = serializedObject.FindProperty("mix"); - rootMass = serializedObject.FindProperty("rootMass"); - massFalloffFactor = serializedObject.FindProperty("massFalloffFactor"); - } - - public override void OnInspectorGUI () { - EditorGUILayout.PropertyField(startingBoneName); - EditorGUILayout.PropertyField(stopBoneNames, true); - EditorGUILayout.PropertyField(applyOnStart); - EditorGUILayout.PropertyField(pinStartBone); - EditorGUILayout.PropertyField(enableJointCollision); - EditorGUILayout.PropertyField(useGravity); - EditorGUILayout.PropertyField(disableIK); - EditorGUILayout.PropertyField(thickness); - EditorGUILayout.PropertyField(rotationLimit); - EditorGUILayout.PropertyField(rootMass); - EditorGUILayout.PropertyField(massFalloffFactor); - colliderLayer.intValue = EditorGUILayout.LayerField(colliderLayer.displayName, colliderLayer.intValue); - EditorGUILayout.PropertyField(mix); - - serializedObject.ApplyModifiedProperties(); - } - - void Header (string name) { - GUILayout.Space(20); - EditorGUILayout.LabelField(name, EditorStyles.boldLabel); + [CustomPropertyDrawer(typeof(SkeletonRagdoll.LayerFieldAttribute))] + public class LayerFieldPropertyDrawer : PropertyDrawer { + public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { + property.intValue = EditorGUI.LayerField(position, label, property.intValue); + } } } diff --git a/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs b/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs index 1275edb25..b8674180b 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs +++ b/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs @@ -10,8 +10,9 @@ using System.Collections.Generic; namespace Spine.Unity.Modules { [RequireComponent(typeof(SkeletonRenderer))] public class SkeletonRagdoll : MonoBehaviour { - private static Transform helper; + static Transform parentSpaceHelper; + #region Inspector [Header("Hierarchy")] [SpineBone] public string startingBoneName = ""; @@ -39,53 +40,153 @@ namespace Spine.Unity.Modules { public int colliderLayer = 0; [Range(0, 1)] public float mix = 1; + #endregion - public Rigidbody RootRigidbody { - get { - return this.rootRigidbody; - } - } - - public Vector3 RootOffset { - get { - return this.rootOffset; - } - } - - public Vector3 EstimatedSkeletonPosition { - get { - return rootRigidbody.position - rootOffset; - } - } - - public bool IsActive { - get { - return this.isActive; - } - } - - private Rigidbody rootRigidbody; - private ISkeletonAnimation skeletonAnim; - private Skeleton skeleton; - private Dictionary boneTable = new Dictionary(); - private Bone startingBone; - private Transform ragdollRoot; - private Vector3 rootOffset; - private bool isActive; + ISkeletonAnimation targetSkeletonComponent; + Skeleton skeleton; + Dictionary boneTable = new Dictionary(); + Transform ragdollRoot; + public Rigidbody RootRigidbody { get; private set; } + public Bone StartingBone { get; private set; } + Vector3 rootOffset; + public Vector3 RootOffset { get { return this.rootOffset; } } + bool isActive; + public bool IsActive { get { return this.isActive; } } IEnumerator Start () { - skeletonAnim = (ISkeletonAnimation)GetComponent(); - if (helper == null) { - helper = (Transform)(new GameObject("Helper")).transform; - helper.hideFlags = HideFlags.HideInHierarchy; + if (parentSpaceHelper == null) { + parentSpaceHelper = (new GameObject("Parent Space Helper")).transform; + parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy; } + targetSkeletonComponent = GetComponent() as ISkeletonAnimation; + if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible."); + skeleton = targetSkeletonComponent.Skeleton; + if (applyOnStart) { yield return null; Apply(); } } + #region API + public Rigidbody[] RigidbodyArray { + get { + if (!isActive) + return new Rigidbody[0]; + + var rigidBodies = new Rigidbody[boneTable.Count]; + int i = 0; + foreach (Transform t in boneTable.Values) { + rigidBodies[i] = t.GetComponent(); + i++; + } + + return rigidBodies; + } + } + + public Vector3 EstimatedSkeletonPosition { + get { return RootRigidbody.position - rootOffset; } + } + + /// Instantiates the ragdoll simulation and applies its transforms to the skeleton. + public void Apply () { + isActive = true; + mix = 1; + + StartingBone = skeleton.FindBone(startingBoneName); + RecursivelyCreateBoneProxies(StartingBone); + + RootRigidbody = boneTable[StartingBone].GetComponent(); + RootRigidbody.isKinematic = pinStartBone; + RootRigidbody.mass = rootMass; + var boneColliders = new List(); + foreach (var pair in boneTable) { + var b = pair.Key; + var t = pair.Value; + Transform parentTransform; + boneColliders.Add(t.GetComponent()); + if (b == StartingBone) { +// skeletonSpaceTransform = new GameObject("Spine World Space Transform").transform; +// skeletonSpaceTransform.hideFlags = HideFlags.NotEditable; +// skeletonSpaceTransform.localScale = FlipScale(skeleton.flipX, skeleton.flipY); +// skeletonSpaceTransform.SetParent(this.transform, false); + ragdollRoot = new GameObject("RagdollRoot").transform; + ragdollRoot.SetParent(transform, false); + if (b == skeleton.RootBone) { // RagdollRoot is skeleton root. + ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b)); + } else { + ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent)); + } + parentTransform = ragdollRoot; + rootOffset = t.position - transform.position; + } else { + parentTransform = boneTable[b.Parent]; + } + + // Add joint and attach to parent. + var rbParent = parentTransform.GetComponent(); + if (rbParent != null) { + var joint = t.gameObject.AddComponent(); + joint.connectedBody = rbParent; + Vector3 localPos = parentTransform.InverseTransformPoint(t.position); + localPos.x *= 1; + joint.connectedAnchor = localPos; + joint.axis = Vector3.forward; + + joint.GetComponent().mass = joint.connectedBody.mass * massFalloffFactor; + joint.limits = new JointLimits { + min = -rotationLimit, + max = rotationLimit, + }; + joint.useLimits = true; + joint.enableCollision = enableJointCollision; + } + } + + // Ignore collisions among bones. + for (int x = 0; x < boneColliders.Count; x++) { + for (int y = 0; y < boneColliders.Count; y++) { + if (x == y) continue; + Physics.IgnoreCollision(boneColliders[x], boneColliders[y]); + } + } + + // Destroy existing override-mode SkeletonUtilityBones. + var utilityBones = GetComponentsInChildren(); + if (utilityBones.Length > 0) { + var destroyedUtilityBoneNames = new List(); + foreach (var ub in utilityBones) { + if (ub.mode == SkeletonUtilityBone.Mode.Override) { + destroyedUtilityBoneNames.Add(ub.gameObject.name); + Destroy(ub.gameObject); + } + } + if (destroyedUtilityBoneNames.Count > 0) { + string msg = "Destroyed Utility Bones: "; + for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) { + msg += destroyedUtilityBoneNames[i]; + if (i != destroyedUtilityBoneNames.Count - 1) { + msg += ","; + } + } + Debug.LogWarning(msg); + } + } + + // Disable IK constraints. + if (disableIK) { + foreach (IkConstraint ik in skeleton.IkConstraints) + ik.Mix = 0; + } + + targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton; + } + + /// Transitions the mix value from the current value to a target value. public Coroutine SmoothMix (float target, float duration) { return StartCoroutine(SmoothMixCoroutine(target, duration)); } @@ -99,6 +200,7 @@ namespace Spine.Unity.Modules { } } + /// Set the transform world position while preserving the ragdoll parts world position. public void SetSkeletonPosition (Vector3 worldPosition) { if (!isActive) { Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!"); @@ -111,35 +213,11 @@ namespace Spine.Unity.Modules { t.position -= offset; } - UpdateWorld(null); + UpdateSpineSkeleton(null); skeleton.UpdateWorldTransform(); } - public Rigidbody[] GetRigidbodyArray () { - if (!isActive) - return new Rigidbody[0]; - - Rigidbody[] arr = new Rigidbody[boneTable.Count]; - int i = 0; - foreach (Transform t in boneTable.Values) { - arr[i] = t.GetComponent(); - i++; - } - - return arr; - } - - public Rigidbody GetRigidbody (string boneName) { - var bone = skeleton.FindBone(boneName); - if (bone == null) - return null; - - if (boneTable.ContainsKey(bone)) - return boneTable[bone].GetComponent(); - - return null; - } - + /// Removes the ragdoll instance and effect from the animated skeleton. public void Remove () { isActive = false; foreach (var t in boneTable.Values) { @@ -148,198 +226,120 @@ namespace Spine.Unity.Modules { Destroy(ragdollRoot.gameObject); boneTable.Clear(); - skeletonAnim.UpdateWorld -= UpdateWorld; + targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton; } - public void Apply () { - isActive = true; - skeleton = skeletonAnim.Skeleton; - mix = 1; - - var ragdollRootBone = skeleton.FindBone(startingBoneName); - startingBone = ragdollRootBone; - RecursivelyCreateBoneProxies(ragdollRootBone); - - rootRigidbody = boneTable[ragdollRootBone].GetComponent(); - rootRigidbody.isKinematic = pinStartBone; - - rootRigidbody.mass = rootMass; - - List boneColliders = new List(); - - foreach (var pair in boneTable) { - var b = pair.Key; - var t = pair.Value; - Bone parentBone = null; - Transform parentTransform = transform; - - boneColliders.Add(t.GetComponent()); - - if (b != startingBone) { - parentBone = b.Parent; - parentTransform = boneTable[parentBone]; - } else { - ragdollRoot = new GameObject("RagdollRoot").transform; - ragdollRoot.parent = transform; - - if (b == skeleton.RootBone) { - ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetCompensatedRotationIK(b)); - parentTransform = ragdollRoot; - } else { - ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetCompensatedRotationIK(b.Parent)); - parentTransform = ragdollRoot; - } - - rootOffset = t.position - transform.position; - } - - var rbParent = parentTransform.GetComponent(); - - if (rbParent != null) { - var joint = t.gameObject.AddComponent(); - joint.connectedBody = rbParent; - Vector3 localPos = parentTransform.InverseTransformPoint(t.position); - localPos.x *= 1; - joint.connectedAnchor = localPos; - joint.axis = Vector3.forward; - joint.GetComponent().mass = joint.connectedBody.mass * massFalloffFactor; - JointLimits limits = new JointLimits(); - limits.min = -rotationLimit; - limits.max = rotationLimit; - joint.limits = limits; - joint.useLimits = true; - joint.enableCollision = enableJointCollision; - } - } - - for (int x = 0; x < boneColliders.Count; x++) { - for (int y = 0; y < boneColliders.Count; y++) { - if (x == y) continue; - Physics.IgnoreCollision(boneColliders[x], boneColliders[y]); - } - } - - var utilityBones = GetComponentsInChildren(); - if (utilityBones.Length > 0) { - List destroyedUtilityBoneNames = new List(); - foreach (var ub in utilityBones) { - if (ub.mode == SkeletonUtilityBone.Mode.Override) { - destroyedUtilityBoneNames.Add(ub.gameObject.name); - Destroy(ub.gameObject); - } - } - - if (destroyedUtilityBoneNames.Count > 0) { - string msg = "Destroyed Utility Bones: "; - for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) { - msg += destroyedUtilityBoneNames[i]; - if (i != destroyedUtilityBoneNames.Count - 1) { - msg += ","; - } - } - Debug.LogWarning(msg); - } - } - - if (disableIK) { - foreach (IkConstraint ik in skeleton.IkConstraints) { - ik.Mix = 0; - } - } - - skeletonAnim.UpdateWorld += UpdateWorld; + public Rigidbody GetRigidbody (string boneName) { + var bone = skeleton.FindBone(boneName); + return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent() : null; } + #endregion void RecursivelyCreateBoneProxies (Bone b) { - if (stopBoneNames.Contains(b.Data.Name)) + string boneName = b.data.name; + if (stopBoneNames.Contains(boneName)) return; - GameObject go = new GameObject(b.Data.Name); - go.layer = colliderLayer; - Transform t = go.transform; + var boneGameObject = new GameObject(boneName); + boneGameObject.layer = colliderLayer; + Transform t = boneGameObject.transform; boneTable.Add(b, t); t.parent = transform; - t.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - // MITCH - // t.localRotation = Quaternion.Euler(0, 0, b.WorldFlipX ^ b.WorldFlipY ? -b.WorldRotation : b.WorldRotation); t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX); t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1); - float length = b.Data.Length; - + // MITCH: You left "todo: proper ragdoll branching" var colliders = AttachBoundingBoxRagdollColliders(b); - - if (length == 0) { - //physics - if (colliders.Count == 0) { - var ball = go.AddComponent(); - ball.radius = thickness / 2f; - } - } else { - //physics - if (colliders.Count == 0) { - var box = go.AddComponent(); + if (colliders.Count == 0) { + float length = b.Data.Length; + if (length == 0) { + var ball = boneGameObject.AddComponent(); + ball.radius = thickness * 0.5f; + } else { + var box = boneGameObject.AddComponent(); box.size = new Vector3(length, thickness, thickness); - // MITCH - // box.center = new Vector3((b.WorldFlipX ? -length : length) / 2, 0); - box.center = new Vector3(length / 2, 0); + box.center = new Vector3(length * 0.5f, 0); } } - - var rb = go.AddComponent(); + var rb = boneGameObject.AddComponent(); rb.constraints = RigidbodyConstraints.FreezePositionZ; + foreach (Bone child in b.Children) { RecursivelyCreateBoneProxies(child); } } + void UpdateSpineSkeleton (ISkeletonAnimation skeletonRenderer) { + bool flipX = skeleton.flipX; + bool flipY = skeleton.flipY; + bool flipXOR = flipX ^ flipY; + bool flipOR = flipX || flipY; + + foreach (var pair in boneTable) { + var b = pair.Key; + var t = pair.Value; + bool isStartingBone = b == StartingBone; + Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent]; + Vector3 parentTransformWorldPosition = parentTransform.position; + Quaternion parentTransformWorldRotation = parentTransform.rotation; + + parentSpaceHelper.position = parentTransformWorldPosition; + parentSpaceHelper.rotation = parentTransformWorldRotation; + parentSpaceHelper.localScale = parentTransform.localScale; + + Vector3 boneWorldPosition = t.position; + Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right); + + Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition); + float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; + + if (flipOR) { + if (isStartingBone) { + if (flipX) boneLocalPosition.x *= -1f; + if (flipY) boneLocalPosition.y *= -1f; + + boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f); + if (flipX) boneLocalRotation += 180; + } else { + if (flipXOR) { + boneLocalRotation *= -1f; + boneLocalPosition.y *= -1f; // wtf?? + } + } + } + + b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix); + b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix); + b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix); + b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix); + } + } + List AttachBoundingBoxRagdollColliders (Bone b) { - List colliders = new List(); + const string AttachmentNameMarker = "ragdoll"; + var colliders = new List(); Transform t = boneTable[b]; GameObject go = t.gameObject; - var skin = skeleton.Skin; - if (skin == null) - skin = skeleton.Data.DefaultSkin; + var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin; - // MITCH - // bool flipX = b.WorldFlipX; - // bool flipY = b.WorldFlipY; - bool flipX = false; - bool flipY = false; - - List attachments = new List(); + var attachments = new List(); foreach (Slot s in skeleton.Slots) { if (s.Bone == b) { skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments); foreach (var a in attachments) { - if (a is BoundingBoxAttachment) { - if (!a.Name.ToLower().Contains("ragdoll")) + var bbAttachment = a as BoundingBoxAttachment; + if (bbAttachment != null) { + if (!a.Name.ToLower().Contains(AttachmentNameMarker)) continue; - var collider = go.AddComponent(); - var bounds = SkeletonUtility.GetBoundingBoxBounds((BoundingBoxAttachment)a, thickness); - - collider.center = bounds.center; - collider.size = bounds.size; - - if (flipX || flipY) { - Vector3 center = collider.center; - - if (flipX) - center.x *= -1; - - if (flipY) - center.y *= -1; - - collider.center = center; - } - - colliders.Add(collider); + var bbCollider = go.AddComponent(); + var bounds = SkeletonUtility.GetBoundingBoxBounds(bbAttachment, thickness); + bbCollider.center = bounds.center; + bbCollider.size = bounds.size; + colliders.Add(bbCollider); } } } @@ -348,81 +348,17 @@ namespace Spine.Unity.Modules { return colliders; } - void UpdateWorld (ISkeletonAnimation skeletonRenderer) { - foreach (var pair in boneTable) { - var b = pair.Key; - var t = pair.Value; - // bool flip = false; - bool flipX = false; //TODO: deal with negative scale instead of Flip Key for Spine 3.0 - bool flipY = false; //TODO: deal with negative scale instead of Flip Key for Spine 3.0 - Bone parentBone = null; - Transform parentTransform = transform; - - if (b != startingBone) { - parentBone = b.Parent; - parentTransform = boneTable[parentBone]; - // MITCH - // flipX = parentBone.WorldFlipX; - // flipY = parentBone.WorldFlipY; - - } else { - parentBone = b.Parent; - parentTransform = ragdollRoot; - if (b.Parent != null) { - // MITCH - // flipX = b.worldFlipX; - // flipY = b.WorldFlipY; - } else { - flipX = b.Skeleton.FlipX; - flipY = b.Skeleton.FlipY; - } - } - - //flip = flipX ^ flipY; - - helper.position = parentTransform.position; - helper.rotation = parentTransform.rotation; - helper.localScale = new Vector3(flipX ? -parentTransform.localScale.x : parentTransform.localScale.x, flipY ? -parentTransform.localScale.y : parentTransform.localScale.y, 1); - - - Vector3 pos = t.position; - pos = helper.InverseTransformPoint(pos); - b.X = Mathf.Lerp(b.X, pos.x, mix); - b.Y = Mathf.Lerp(b.Y, pos.y, mix); - - Vector3 right = helper.InverseTransformDirection(t.right); - - float a = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; - - // MITCH - //if (b.WorldFlipX ^ b.WorldFlipY) { - // a *= -1; - //} - - if (parentBone != null) { - // MITCH - //if ((b.WorldFlipX ^ b.WorldFlipY) != flip) { - // a -= GetCompensatedRotationIK(parentBone) * 2; - //} - } - - b.Rotation = Mathf.Lerp(b.Rotation, a, mix); - // MITCH - // b.RotationIK = Mathf.Lerp(b.rotationIK, a, mix); - } - } - - float GetCompensatedRotationIK (Bone b) { + static float GetPropagatedRotation (Bone b) { Bone parent = b.Parent; - // MITCH float a = b.AppliedRotation; while (parent != null) { a += parent.AppliedRotation; parent = parent.parent; } - return a; } + + public class LayerFieldAttribute : PropertyAttribute {} } } diff --git a/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs b/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs index 79a3d6f6a..2972d3fae 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs +++ b/spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs @@ -2,16 +2,27 @@ * SkeletonRagdoll2D added by Mitch Thompson * Full irrevocable rights and permissions granted to Esoteric Software *****************************************************************************/ +//#define FLIPDEBUG using UnityEngine; using System.Collections; using System.Collections.Generic; using Spine.Unity; +using UnityEngine.Assertions; namespace Spine.Unity.Modules { [RequireComponent(typeof(SkeletonRenderer))] public class SkeletonRagdoll2D : MonoBehaviour { - private static Transform helper; + static Transform parentSpaceHelper; + + #region Inspector + #if FLIPDEBUG + [Header("DEBUG")] + public bool flipXInitially; + public bool flipYInitially; + public bool spawnKinematic; + public bool disableUpdateBones; + #endif [Header("Hierarchy")] [SpineBone] @@ -35,49 +46,163 @@ namespace Spine.Unity.Modules { [Range(0.01f, 1f)] public float massFalloffFactor = 0.4f; [Tooltip("The layer assigned to all of the rigidbody parts.")] + [SkeletonRagdoll.LayerField] public int colliderLayer = 0; [Range(0, 1)] public float mix = 1; + #endregion - public Rigidbody2D RootRigidbody { - get { return this.rootRigidbody; } - } - - public Vector3 RootOffset { - get { return this.rootOffset; } - } - - public Vector3 EstimatedSkeletonPosition { - get { return this.rootRigidbody.position - rootOffset; } - } - - public bool IsActive { - get { return this.isActive; } - } - - private Rigidbody2D rootRigidbody; - private ISkeletonAnimation skeletonAnim; - private Skeleton skeleton; - private Dictionary boneTable = new Dictionary(); - private Bone startingBone; - private Transform ragdollRoot; - private Vector2 rootOffset; - private bool isActive; - + ISkeletonAnimation targetSkeletonComponent; + Skeleton skeleton; + Dictionary boneTable = new Dictionary(); + Transform ragdollRoot; + public Rigidbody2D RootRigidbody { get; private set; } + public Bone StartingBone { get; private set; } + Vector2 rootOffset; + public Vector3 RootOffset { get { return this.rootOffset; } } + bool isActive; + public bool IsActive { get { return this.isActive; } } +// public Transform skeletonSpaceTransform; IEnumerator Start () { - skeletonAnim = (ISkeletonAnimation)GetComponent(); - if (helper == null) { - helper = (Transform)(new GameObject("Helper")).transform; - helper.hideFlags = HideFlags.HideInHierarchy; + if (parentSpaceHelper == null) { + parentSpaceHelper = (new GameObject("Parent Space Helper")).transform; + #if !FLIPDEBUG + parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy; + #endif } + targetSkeletonComponent = GetComponent() as ISkeletonAnimation; + if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible."); + skeleton = targetSkeletonComponent.Skeleton; + + #if FLIPDEBUG + skeleton.flipX = flipXInitially; + skeleton.flipY = flipYInitially; + #endif + if (applyOnStart) { yield return null; Apply(); } } + #region API + public Rigidbody2D[] RigidbodyArray { + get { + if (!isActive) + return new Rigidbody2D[0]; + + var rigidBodies = new Rigidbody2D[boneTable.Count]; + int i = 0; + foreach (Transform t in boneTable.Values) { + rigidBodies[i] = t.GetComponent(); + i++; + } + + return rigidBodies; + } + } + + public Vector3 EstimatedSkeletonPosition { + get { return this.RootRigidbody.position - rootOffset; } + } + + /// Instantiates the ragdoll simulation and applies its transforms to the skeleton. + public void Apply () { + isActive = true; + mix = 1; + + Bone startingBone = this.StartingBone = skeleton.FindBone(startingBoneName); + RecursivelyCreateBoneProxies(startingBone); + + RootRigidbody = boneTable[startingBone].GetComponent(); + #if FLIPDEBUG + if (!RootRigidbody.isKinematic) + #endif + RootRigidbody.isKinematic = pinStartBone; + RootRigidbody.mass = rootMass; + var boneColliders = new List(); + foreach (var pair in boneTable) { + var b = pair.Key; + var t = pair.Value; + Transform parentTransform; + boneColliders.Add(t.GetComponent()); + if (b == startingBone) { +// skeletonSpaceTransform = new GameObject("Spine World Space Transform").transform; +// skeletonSpaceTransform.hideFlags = HideFlags.NotEditable; +// skeletonSpaceTransform.localScale = FlipScale(skeleton.flipX, skeleton.flipY); +// skeletonSpaceTransform.SetParent(this.transform, false); + ragdollRoot = new GameObject("RagdollRoot").transform; + ragdollRoot.SetParent(transform, false); + if (b == skeleton.RootBone) { // RagdollRoot is skeleton root. + ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b)); + } else { + ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent)); + } + parentTransform = ragdollRoot; + rootOffset = t.position - transform.position; + } else { + parentTransform = boneTable[b.Parent]; + } + + // Add joint and attach to parent. + var rbParent = parentTransform.GetComponent(); + if (rbParent != null) { + var joint = t.gameObject.AddComponent(); + joint.connectedBody = rbParent; + Vector3 localPos = parentTransform.InverseTransformPoint(t.position); + joint.connectedAnchor = localPos; + + joint.GetComponent().mass = joint.connectedBody.mass * massFalloffFactor; + joint.limits = new JointAngleLimits2D { + min = -rotationLimit, + max = rotationLimit + }; + joint.useLimits = true; + } + } + + // Ignore collisions among bones. + for (int x = 0; x < boneColliders.Count; x++) { + for (int y = 0; y < boneColliders.Count; y++) { + if (x == y) continue; + Physics2D.IgnoreCollision(boneColliders[x], boneColliders[y]); + } + } + + // Destroy existing override-mode SkeletonUtility bones. + var utilityBones = GetComponentsInChildren(); + if (utilityBones.Length > 0) { + var destroyedUtilityBoneNames = new List(); + foreach (var ub in utilityBones) { + if (ub.mode == SkeletonUtilityBone.Mode.Override) { + destroyedUtilityBoneNames.Add(ub.gameObject.name); + Destroy(ub.gameObject); + } + } + if (destroyedUtilityBoneNames.Count > 0) { + string msg = "Destroyed Utility Bones: "; + for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) { + msg += destroyedUtilityBoneNames[i]; + if (i != destroyedUtilityBoneNames.Count - 1) { + msg += ","; + } + } + Debug.LogWarning(msg); + } + } + // Disable IK constraints. + if (disableIK) { + foreach (IkConstraint ik in skeleton.IkConstraints) + ik.Mix = 0; + } + targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton; + } + + /// Transitions the mix value from the current value to a target value. public Coroutine SmoothMix (float target, float duration) { return StartCoroutine(SmoothMixCoroutine(target, duration)); } @@ -91,249 +216,160 @@ namespace Spine.Unity.Modules { } } + /// Set the transform world position while preserving the ragdoll parts world position. public void SetSkeletonPosition (Vector3 worldPosition) { if (!isActive) { Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!"); return; } - Vector3 offset = worldPosition - transform.position; transform.position = worldPosition; foreach (Transform t in boneTable.Values) { t.position -= offset; } - - UpdateWorld(null); + UpdateSpineSkeleton(null); skeleton.UpdateWorldTransform(); } - public Rigidbody2D[] GetRigidbodyArray () { - if (!isActive) - return new Rigidbody2D[0]; - - Rigidbody2D[] arr = new Rigidbody2D[boneTable.Count]; - int i = 0; - foreach (Transform t in boneTable.Values) { - arr[i] = t.GetComponent(); - i++; - } - - return arr; - } - - public Rigidbody2D GetRigidbody (string boneName) { - var bone = skeleton.FindBone(boneName); - if (bone == null) - return null; - - if (boneTable.ContainsKey(bone)) - return boneTable[bone].GetComponent(); - - return null; - } - + /// Removes the ragdoll instance and effect from the animated skeleton. public void Remove () { isActive = false; foreach (var t in boneTable.Values) { Destroy(t.gameObject); } Destroy(ragdollRoot.gameObject); - boneTable.Clear(); - skeletonAnim.UpdateWorld -= UpdateWorld; + targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton; } - public void Apply () { - isActive = true; - skeleton = skeletonAnim.Skeleton; - mix = 1; - - var ragdollRootBone = skeleton.FindBone(startingBoneName); - startingBone = ragdollRootBone; - RecursivelyCreateBoneProxies(ragdollRootBone); - - rootRigidbody = boneTable[ragdollRootBone].GetComponent(); - rootRigidbody.isKinematic = pinStartBone; - rootRigidbody.mass = rootMass; - - List boneColliders = new List(); - - foreach (var pair in boneTable) { - var b = pair.Key; - var t = pair.Value; - Bone parentBone = null; - Transform parentTransform = transform; - - boneColliders.Add(t.GetComponent()); - - if (b != startingBone) { - parentBone = b.Parent; - parentTransform = boneTable[parentBone]; - } else { - ragdollRoot = new GameObject("RagdollRoot").transform; - ragdollRoot.parent = transform; - - if (b == skeleton.RootBone) { - ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetCompensatedRotationIK(b)); - parentTransform = ragdollRoot; - } else { - ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetCompensatedRotationIK(b.Parent)); - parentTransform = ragdollRoot; - } - - rootOffset = t.position - transform.position; - } - - var rbParent = parentTransform.GetComponent(); - - if (rbParent != null) { - var joint = t.gameObject.AddComponent(); - joint.connectedBody = rbParent; - Vector3 localPos = parentTransform.InverseTransformPoint(t.position); - localPos.x *= 1; - joint.connectedAnchor = localPos; - joint.GetComponent().mass = joint.connectedBody.mass * massFalloffFactor; - JointAngleLimits2D limits = new JointAngleLimits2D(); - limits.min = -rotationLimit; - limits.max = rotationLimit; - joint.limits = limits; - joint.useLimits = true; - } - } - - for (int x = 0; x < boneColliders.Count; x++) { - for (int y = 0; y < boneColliders.Count; y++) { - if (x == y) continue; - Physics2D.IgnoreCollision(boneColliders[x], boneColliders[y]); - } - } - - var utilityBones = GetComponentsInChildren(); - if (utilityBones.Length > 0) { - List destroyedUtilityBoneNames = new List(); - foreach (var ub in utilityBones) { - if (ub.mode == SkeletonUtilityBone.Mode.Override) { - destroyedUtilityBoneNames.Add(ub.gameObject.name); - Destroy(ub.gameObject); - } - } - - if (destroyedUtilityBoneNames.Count > 0) { - string msg = "Destroyed Utility Bones: "; - for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) { - msg += destroyedUtilityBoneNames[i]; - if (i != destroyedUtilityBoneNames.Count - 1) { - msg += ","; - } - } - Debug.LogWarning(msg); - } - } - - if (disableIK) { - foreach (IkConstraint ik in skeleton.IkConstraints) { - ik.Mix = 0; - } - } - - skeletonAnim.UpdateWorld += UpdateWorld; + public Rigidbody2D GetRigidbody (string boneName) { + var bone = skeleton.FindBone(boneName); + return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent() : null; } + #endregion + /// Generates the ragdoll simulation's Transform and joint setup. void RecursivelyCreateBoneProxies (Bone b) { - if (stopBoneNames.Contains(b.Data.Name)) + string boneName = b.data.name; + if (stopBoneNames.Contains(boneName)) return; - GameObject go = new GameObject(b.Data.Name); - go.layer = colliderLayer; - Transform t = go.transform; + var boneGameObject = new GameObject(boneName); + boneGameObject.layer = this.colliderLayer; + Transform t = boneGameObject.transform; boneTable.Add(b, t); t.parent = transform; - t.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - //TODO: deal with WorldFlipY - // MITCH - // t.localRotation = Quaternion.Euler(0, 0, b.WorldFlipX ? -b.WorldRotation : b.WorldRotation); t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX); t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 0); - float length = b.Data.Length; - - //TODO proper ragdoll branching - var colliders = AttachBoundingBoxRagdollColliders(b); - - if (length == 0) { - //physics - if (colliders.Count == 0) { - var circle = go.AddComponent(); - circle.radius = thickness / 2f; - } - } else { - //physics - if (colliders.Count == 0) { - var box = go.AddComponent(); + // MITCH: You left "todo: proper ragdoll branching" + var colliders = AttachBoundingBoxRagdollColliders(b, boneGameObject, skeleton); + if (colliders.Count == 0) { + float length = b.data.length; + if (length == 0) { + var circle = boneGameObject.AddComponent(); + circle.radius = thickness * 0.5f; + } else { + var box = boneGameObject.AddComponent(); box.size = new Vector2(length, thickness); - #if UNITY_5 - // MITCH - // box.offset = new Vector2((b.WorldFlipX ? -length : length) / 2, 0); - box.offset = new Vector2(length / 2, 0); - #else - //box.center = new Vector2((b.WorldFlipX ? -length : length) / 2, 0); - box.center = new Vector2(length/2, 0); - #endif + box.offset = new Vector2(length * 0.5f, 0); // box.center in UNITY_4 } } + var rb = boneGameObject.AddComponent(); + rb.gravityScale = this.gravityScale; - var rb = go.AddComponent(); - rb.gravityScale = gravityScale; + #if FLIPDEBUG + rb.isKinematic = spawnKinematic; + #endif foreach (Bone child in b.Children) { RecursivelyCreateBoneProxies(child); } } - List AttachBoundingBoxRagdollColliders (Bone b) { - List colliders = new List(); - Transform t = boneTable[b]; - GameObject go = t.gameObject; - var skin = skeleton.Skin; - if (skin == null) - skin = skeleton.Data.DefaultSkin; + /// Performed every skeleton animation update to translate Unity Transforms positions into Spine bone transforms. + void UpdateSpineSkeleton (ISkeletonAnimation animatedSkeleton) { + #if FLIPDEBUG + if (disableUpdateBones) return; + #endif - // MITCH - // bool flipX = b.WorldFlipX; - // bool flipY = b.WorldFlipY; - bool flipX = false; - bool flipY = false; + bool flipX = skeleton.flipX; + bool flipY = skeleton.flipY; + bool flipXOR = flipX ^ flipY; + bool flipOR = flipX || flipY; + var startingBone = this.StartingBone; - List attachments = new List(); + foreach (var pair in boneTable) { + var b = pair.Key; + var t = pair.Value; + bool isStartingBone = (b == startingBone); + Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent]; + Vector3 parentTransformWorldPosition = parentTransform.position; + Quaternion parentTransformWorldRotation = parentTransform.rotation; + + parentSpaceHelper.position = parentTransformWorldPosition; + parentSpaceHelper.rotation = parentTransformWorldRotation; + parentSpaceHelper.localScale = parentTransform.localScale; + + Vector3 boneWorldPosition = t.position; + Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right); + + Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition); + float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; + if (flipOR) { + if (isStartingBone) { + if (flipX) boneLocalPosition.x *= -1f; + if (flipY) boneLocalPosition.y *= -1f; + + boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f); + if (flipX) boneLocalRotation += 180; + } else { + if (flipXOR) { + boneLocalRotation *= -1f; + boneLocalPosition.y *= -1f; // wtf?? + } + } + } + + b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix); + b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix); + b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix); + b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix); + +// Mitch Original Code: +// Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right); +// float a = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; +// if (b.worldSignX ^ b.worldSignY) { +// a *= -1; +// } +// if (parentBone != null) { +// if ((b.WorldFlipX ^ b.WorldFlipY) != flip) { +// a -= GetCompensatedRotationIK(parentBone) * 2; +// } +// } +// b.Rotation = Mathf.Lerp(b.Rotation, a, mix); + } + } + + static List AttachBoundingBoxRagdollColliders (Bone b, GameObject go, Skeleton skeleton) { + const string AttachmentNameMarker = "ragdoll"; + var colliders = new List(); + var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin; + + var attachments = new List(); foreach (Slot s in skeleton.Slots) { - if (s.Bone == b) { + if (s.bone == b) { skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments); foreach (var a in attachments) { - if (a is BoundingBoxAttachment) { - if (!a.Name.ToLower().Contains("ragdoll")) + var bbAttachment = a as BoundingBoxAttachment; + if (bbAttachment != null) { + if (!a.Name.ToLower().Contains(AttachmentNameMarker)) continue; - var collider = SkeletonUtility.AddBoundingBoxAsComponent((BoundingBoxAttachment)a, go, false); - - if (flipX || flipY) { - Vector2[] points = collider.points; - - for (int i = 0; i < points.Length; i++) { - if (flipX) - points[i].x *= -1; - - if (flipY) - points[i].y *= -1; - } - - collider.points = points; - } - - colliders.Add(collider); + var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, go, false); + colliders.Add(bbCollider); } } } @@ -341,92 +377,31 @@ namespace Spine.Unity.Modules { return colliders; } - - void UpdateWorld (ISkeletonAnimation skeletonRenderer) { - foreach (var pair in boneTable) { - var b = pair.Key; - var t = pair.Value; - //bool flip = false; - bool flipX = false; //TODO: deal with negative scale instead of Flip Key - bool flipY = false; //TODO: deal with negative scale instead of Flip Key - Bone parentBone = null; - Transform parentTransform = transform; - - if (b != startingBone) { - parentBone = b.Parent; - parentTransform = boneTable[parentBone]; - // MITCH - // flipX = parentBone.WorldFlipX; - // flipY = parentBone.WorldFlipY; - - } else { - parentBone = b.Parent; - parentTransform = ragdollRoot; - if (b.Parent != null) { - // MITCH - // flipX = b.worldFlipX; - // flipY = b.WorldFlipY; - } else { - flipX = b.Skeleton.FlipX; - flipY = b.Skeleton.FlipY; - } - } - - //flip = flipX ^ flipY; - - helper.position = parentTransform.position; - helper.rotation = parentTransform.rotation; - helper.localScale = new Vector3(flipX ? -parentTransform.localScale.x : parentTransform.localScale.x, flipY ? -parentTransform.localScale.y : parentTransform.localScale.y, 1); - - - Vector3 pos = t.position; - pos = helper.InverseTransformPoint(pos); - b.X = Mathf.Lerp(b.X, pos.x, mix); - b.Y = Mathf.Lerp(b.Y, pos.y, mix); - - Vector3 right = helper.InverseTransformDirection(t.right); - - float a = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; - - // MITCH - //if (b.WorldFlipX ^ b.WorldFlipY) { - // a *= -1; - //} - - if (parentBone != null) { - // MITCH - //if ((b.WorldFlipX ^ b.WorldFlipY) != flip) { - // a -= GetCompensatedRotationIK(parentBone) * 2; - //} - } - - b.Rotation = Mathf.Lerp(b.Rotation, a, mix); - // MITCH - // b.RotationIK = Mathf.Lerp(b.rotationIK, a, mix); - } - } - - float GetCompensatedRotationIK (Bone b) { + + static float GetPropagatedRotation (Bone b) { Bone parent = b.Parent; - // MITCH float a = b.AppliedRotation; while (parent != null) { a += parent.AppliedRotation; parent = parent.parent; } - return a; } + static Vector3 FlipScale (bool flipX, bool flipY) { + return new Vector3(flipX ? -1f : 1f, flipY ? -1f : 1f, 1f); + } + + #if UNITY_EDITOR void OnDrawGizmosSelected () { if (isActive) { Gizmos.DrawWireSphere(transform.position, thickness * 1.2f); - Vector3 newTransformPos = rootRigidbody.position - rootOffset; + Vector3 newTransformPos = RootRigidbody.position - rootOffset; Gizmos.DrawLine(transform.position, newTransformPos); Gizmos.DrawWireSphere(newTransformPos, thickness * 1.2f); } } - + #endif } }