SkeletonRagdoll.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated January 1, 2020. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2020, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software
  13. * or otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. // Contributed by: Mitch Thompson
  30. using System.Collections;
  31. using System.Collections.Generic;
  32. using UnityEngine;
  33. namespace Spine.Unity.Examples {
  34. [RequireComponent(typeof(SkeletonRenderer))]
  35. public class SkeletonRagdoll : MonoBehaviour {
  36. static Transform parentSpaceHelper;
  37. #region Inspector
  38. [Header("Hierarchy")]
  39. [SpineBone]
  40. public string startingBoneName = "";
  41. [SpineBone]
  42. public List<string> stopBoneNames = new List<string>();
  43. [Header("Parameters")]
  44. public bool applyOnStart;
  45. [Tooltip("Warning! You will have to re-enable and tune mix values manually if attempting to remove the ragdoll system.")]
  46. public bool disableIK = true;
  47. public bool disableOtherConstraints = false;
  48. [Space(18)]
  49. [Tooltip("Set RootRigidbody IsKinematic to true when Apply is called.")]
  50. public bool pinStartBone;
  51. [Tooltip("Enable Collision between adjacent ragdoll elements (IE: Neck and Head)")]
  52. public bool enableJointCollision;
  53. public bool useGravity = true;
  54. [Tooltip("If no BoundingBox Attachment is attached to a bone, this becomes the default Width or Radius of a Bone's ragdoll Rigidbody")]
  55. public float thickness = 0.125f;
  56. [Tooltip("Default rotational limit value. Min is negative this value, Max is this value.")]
  57. public float rotationLimit = 20;
  58. public float rootMass = 20;
  59. [Tooltip("If your ragdoll seems unstable or uneffected by limits, try lowering this value.")]
  60. [Range(0.01f, 1f)]
  61. public float massFalloffFactor = 0.4f;
  62. [Tooltip("The layer assigned to all of the rigidbody parts.")]
  63. public int colliderLayer = 0;
  64. [Range(0, 1)]
  65. public float mix = 1;
  66. public bool oldRagdollBehaviour = false;
  67. #endregion
  68. ISkeletonAnimation targetSkeletonComponent;
  69. Skeleton skeleton;
  70. struct BoneFlipEntry {
  71. public BoneFlipEntry (bool flipX, bool flipY) {
  72. this.flipX = flipX;
  73. this.flipY = flipY;
  74. }
  75. public bool flipX;
  76. public bool flipY;
  77. }
  78. Dictionary<Bone, Transform> boneTable = new Dictionary<Bone, Transform>();
  79. Dictionary<Bone, BoneFlipEntry> boneFlipTable = new Dictionary<Bone, BoneFlipEntry>();
  80. Transform ragdollRoot;
  81. public Rigidbody RootRigidbody { get; private set; }
  82. public Bone StartingBone { get; private set; }
  83. Vector3 rootOffset;
  84. public Vector3 RootOffset { get { return this.rootOffset; } }
  85. bool isActive;
  86. public bool IsActive { get { return this.isActive; } }
  87. IEnumerator Start () {
  88. if (parentSpaceHelper == null) {
  89. parentSpaceHelper = (new GameObject("Parent Space Helper")).transform;
  90. parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy;
  91. }
  92. targetSkeletonComponent = GetComponent<SkeletonRenderer>() as ISkeletonAnimation;
  93. if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible.");
  94. skeleton = targetSkeletonComponent.Skeleton;
  95. if (applyOnStart) {
  96. yield return null;
  97. Apply();
  98. }
  99. }
  100. #region API
  101. public Rigidbody[] RigidbodyArray {
  102. get {
  103. if (!isActive)
  104. return new Rigidbody[0];
  105. var rigidBodies = new Rigidbody[boneTable.Count];
  106. int i = 0;
  107. foreach (Transform t in boneTable.Values) {
  108. rigidBodies[i] = t.GetComponent<Rigidbody>();
  109. i++;
  110. }
  111. return rigidBodies;
  112. }
  113. }
  114. public Vector3 EstimatedSkeletonPosition {
  115. get { return RootRigidbody.position - rootOffset; }
  116. }
  117. /// <summary>Instantiates the ragdoll simulation and applies its transforms to the skeleton.</summary>
  118. public void Apply () {
  119. isActive = true;
  120. mix = 1;
  121. StartingBone = skeleton.FindBone(startingBoneName);
  122. RecursivelyCreateBoneProxies(StartingBone);
  123. RootRigidbody = boneTable[StartingBone].GetComponent<Rigidbody>();
  124. RootRigidbody.isKinematic = pinStartBone;
  125. RootRigidbody.mass = rootMass;
  126. var boneColliders = new List<Collider>();
  127. foreach (var pair in boneTable) {
  128. var b = pair.Key;
  129. var t = pair.Value;
  130. Transform parentTransform;
  131. boneColliders.Add(t.GetComponent<Collider>());
  132. if (b == StartingBone) {
  133. ragdollRoot = new GameObject("RagdollRoot").transform;
  134. ragdollRoot.SetParent(transform, false);
  135. if (b == skeleton.RootBone) { // RagdollRoot is skeleton root's parent, thus the skeleton's scale and position.
  136. ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0);
  137. ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity;
  138. } else {
  139. ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
  140. ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX);
  141. }
  142. parentTransform = ragdollRoot;
  143. rootOffset = t.position - transform.position;
  144. } else {
  145. parentTransform = boneTable[b.Parent];
  146. }
  147. // Add joint and attach to parent.
  148. var rbParent = parentTransform.GetComponent<Rigidbody>();
  149. if (rbParent != null) {
  150. var joint = t.gameObject.AddComponent<HingeJoint>();
  151. joint.connectedBody = rbParent;
  152. Vector3 localPos = parentTransform.InverseTransformPoint(t.position);
  153. localPos.x *= 1;
  154. joint.connectedAnchor = localPos;
  155. joint.axis = Vector3.forward;
  156. joint.GetComponent<Rigidbody>().mass = joint.connectedBody.mass * massFalloffFactor;
  157. joint.limits = new JointLimits {
  158. min = -rotationLimit,
  159. max = rotationLimit,
  160. };
  161. joint.useLimits = true;
  162. joint.enableCollision = enableJointCollision;
  163. }
  164. }
  165. // Ignore collisions among bones.
  166. for (int x = 0; x < boneColliders.Count; x++) {
  167. for (int y = 0; y < boneColliders.Count; y++) {
  168. if (x == y) continue;
  169. Physics.IgnoreCollision(boneColliders[x], boneColliders[y]);
  170. }
  171. }
  172. // Destroy existing override-mode SkeletonUtilityBones.
  173. var utilityBones = GetComponentsInChildren<SkeletonUtilityBone>();
  174. if (utilityBones.Length > 0) {
  175. var destroyedUtilityBoneNames = new List<string>();
  176. foreach (var ub in utilityBones) {
  177. if (ub.mode == SkeletonUtilityBone.Mode.Override) {
  178. destroyedUtilityBoneNames.Add(ub.gameObject.name);
  179. Destroy(ub.gameObject);
  180. }
  181. }
  182. if (destroyedUtilityBoneNames.Count > 0) {
  183. string msg = "Destroyed Utility Bones: ";
  184. for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) {
  185. msg += destroyedUtilityBoneNames[i];
  186. if (i != destroyedUtilityBoneNames.Count - 1) {
  187. msg += ",";
  188. }
  189. }
  190. Debug.LogWarning(msg);
  191. }
  192. }
  193. // Disable skeleton constraints.
  194. if (disableIK) {
  195. var ikConstraints = skeleton.IkConstraints;
  196. for (int i = 0, n = ikConstraints.Count; i < n; i++)
  197. ikConstraints.Items[i].Mix = 0;
  198. }
  199. if (disableOtherConstraints) {
  200. var transformConstraints = skeleton.TransformConstraints;
  201. for (int i = 0, n = transformConstraints.Count; i < n; i++) {
  202. transformConstraints.Items[i].MixRotate = 0;
  203. transformConstraints.Items[i].MixScaleX = 0;
  204. transformConstraints.Items[i].MixScaleY = 0;
  205. transformConstraints.Items[i].MixShearY = 0;
  206. transformConstraints.Items[i].MixX = 0;
  207. transformConstraints.Items[i].MixY = 0;
  208. }
  209. var pathConstraints = skeleton.PathConstraints;
  210. for (int i = 0, n = pathConstraints.Count; i < n; i++) {
  211. pathConstraints.Items[i].MixRotate = 0;
  212. pathConstraints.Items[i].MixX = 0;
  213. pathConstraints.Items[i].MixY = 0;
  214. }
  215. }
  216. targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton;
  217. }
  218. /// <summary>Transitions the mix value from the current value to a target value.</summary>
  219. public Coroutine SmoothMix (float target, float duration) {
  220. return StartCoroutine(SmoothMixCoroutine(target, duration));
  221. }
  222. IEnumerator SmoothMixCoroutine (float target, float duration) {
  223. float startTime = Time.time;
  224. float startMix = mix;
  225. while (mix > 0) {
  226. skeleton.SetBonesToSetupPose();
  227. mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
  228. yield return null;
  229. }
  230. }
  231. /// <summary>Set the transform world position while preserving the ragdoll parts world position.</summary>
  232. public void SetSkeletonPosition (Vector3 worldPosition) {
  233. if (!isActive) {
  234. Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!");
  235. return;
  236. }
  237. Vector3 offset = worldPosition - transform.position;
  238. transform.position = worldPosition;
  239. foreach (Transform t in boneTable.Values)
  240. t.position -= offset;
  241. UpdateSpineSkeleton(null);
  242. skeleton.UpdateWorldTransform();
  243. }
  244. /// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
  245. public void Remove () {
  246. isActive = false;
  247. foreach (var t in boneTable.Values)
  248. Destroy(t.gameObject);
  249. Destroy(ragdollRoot.gameObject);
  250. boneTable.Clear();
  251. targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton;
  252. }
  253. public Rigidbody GetRigidbody (string boneName) {
  254. var bone = skeleton.FindBone(boneName);
  255. return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent<Rigidbody>() : null;
  256. }
  257. #endregion
  258. void RecursivelyCreateBoneProxies (Bone b) {
  259. string boneName = b.Data.Name;
  260. if (stopBoneNames.Contains(boneName))
  261. return;
  262. var boneGameObject = new GameObject(boneName);
  263. boneGameObject.layer = colliderLayer;
  264. Transform t = boneGameObject.transform;
  265. boneTable.Add(b, t);
  266. t.parent = transform;
  267. t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
  268. t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX);
  269. t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1);
  270. var colliders = AttachBoundingBoxRagdollColliders(b);
  271. if (colliders.Count == 0) {
  272. float length = b.Data.Length;
  273. if (length == 0) {
  274. var ball = boneGameObject.AddComponent<SphereCollider>();
  275. ball.radius = thickness * 0.5f;
  276. } else {
  277. var box = boneGameObject.AddComponent<BoxCollider>();
  278. box.size = new Vector3(length, thickness, thickness);
  279. box.center = new Vector3(length * 0.5f, 0);
  280. }
  281. }
  282. var rb = boneGameObject.AddComponent<Rigidbody>();
  283. rb.constraints = RigidbodyConstraints.FreezePositionZ;
  284. foreach (Bone child in b.Children)
  285. RecursivelyCreateBoneProxies(child);
  286. }
  287. void UpdateSpineSkeleton (ISkeletonAnimation skeletonRenderer) {
  288. bool parentFlipX;
  289. bool parentFlipY;
  290. GetStartBoneParentFlipState(out parentFlipX, out parentFlipY);
  291. foreach (var pair in boneTable) {
  292. var b = pair.Key;
  293. var t = pair.Value;
  294. bool isStartingBone = b == StartingBone;
  295. var parentBone = b.Parent;
  296. Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[parentBone];
  297. if (!isStartingBone) {
  298. var parentBoneFlip = boneFlipTable[parentBone];
  299. parentFlipX = parentBoneFlip.flipX;
  300. parentFlipY = parentBoneFlip.flipY;
  301. }
  302. bool flipX = parentFlipX ^ (b.ScaleX < 0);
  303. bool flipY = parentFlipY ^ (b.ScaleY < 0);
  304. BoneFlipEntry boneFlip;
  305. boneFlipTable.TryGetValue(b, out boneFlip);
  306. boneFlip.flipX = flipX;
  307. boneFlip.flipY = flipY;
  308. boneFlipTable[b] = boneFlip;
  309. bool flipXOR = flipX ^ flipY;
  310. bool parentFlipXOR = parentFlipX ^ parentFlipY;
  311. if (!oldRagdollBehaviour && isStartingBone) {
  312. if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root.
  313. ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0);
  314. ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX);
  315. ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1);
  316. }
  317. }
  318. Vector3 parentTransformWorldPosition = parentTransform.position;
  319. Quaternion parentTransformWorldRotation = parentTransform.rotation;
  320. parentSpaceHelper.position = parentTransformWorldPosition;
  321. parentSpaceHelper.rotation = parentTransformWorldRotation;
  322. parentSpaceHelper.localScale = parentTransform.lossyScale;
  323. if (oldRagdollBehaviour) {
  324. if (isStartingBone && b != skeleton.RootBone) {
  325. Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
  326. parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition);
  327. parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX);
  328. parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1);
  329. }
  330. }
  331. Vector3 boneWorldPosition = t.position;
  332. Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right);
  333. Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition);
  334. float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg;
  335. if (flipXOR) boneLocalPosition.y *= -1f;
  336. if (parentFlipXOR != flipXOR) boneLocalPosition.y *= -1f;
  337. if (parentFlipXOR) boneLocalRotation *= -1f;
  338. if (parentFlipX != flipX) boneLocalRotation += 180;
  339. b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix);
  340. b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix);
  341. b.Rotation = Mathf.Lerp(b.Rotation, boneLocalRotation, mix);
  342. //b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix);
  343. }
  344. }
  345. void GetStartBoneParentFlipState (out bool parentFlipX, out bool parentFlipY) {
  346. parentFlipX = skeleton.ScaleX < 0;
  347. parentFlipY = skeleton.ScaleY < 0;
  348. var parent = this.StartingBone == null ? null : this.StartingBone.Parent;
  349. while (parent != null) {
  350. parentFlipX ^= parent.ScaleX < 0;
  351. parentFlipY ^= parent.ScaleY < 0;
  352. parent = parent.Parent;
  353. }
  354. }
  355. List<Collider> AttachBoundingBoxRagdollColliders (Bone b) {
  356. const string AttachmentNameMarker = "ragdoll";
  357. var colliders = new List<Collider>();
  358. Transform t = boneTable[b];
  359. GameObject go = t.gameObject;
  360. var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
  361. var skinEntries = new List<Skin.SkinEntry>();
  362. foreach (Slot s in skeleton.Slots) {
  363. if (s.Bone == b) {
  364. skin.GetAttachments(skeleton.Slots.IndexOf(s), skinEntries);
  365. foreach (var entry in skinEntries) {
  366. var bbAttachment = entry.Attachment as BoundingBoxAttachment;
  367. if (bbAttachment != null) {
  368. if (!entry.Name.ToLower().Contains(AttachmentNameMarker))
  369. continue;
  370. var bbCollider = go.AddComponent<BoxCollider>();
  371. var bounds = SkeletonUtility.GetBoundingBoxBounds(bbAttachment, thickness);
  372. bbCollider.center = bounds.center;
  373. bbCollider.size = bounds.size;
  374. colliders.Add(bbCollider);
  375. }
  376. }
  377. }
  378. }
  379. return colliders;
  380. }
  381. public class LayerFieldAttribute : PropertyAttribute { }
  382. }
  383. }