SkeletonRootMotionBase.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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. using UnityEngine;
  30. using System.Collections.Generic;
  31. using Spine.Unity.AnimationTools;
  32. using System;
  33. namespace Spine.Unity {
  34. /// <summary>
  35. /// Base class for skeleton root motion components.
  36. /// </summary>
  37. abstract public class SkeletonRootMotionBase : MonoBehaviour {
  38. #region Inspector
  39. [SpineBone]
  40. [SerializeField]
  41. protected string rootMotionBoneName = "root";
  42. public bool transformPositionX = true;
  43. public bool transformPositionY = true;
  44. public float rootMotionScaleX = 1;
  45. public float rootMotionScaleY = 1;
  46. /// <summary>Skeleton space X translation per skeleton space Y translation root motion.</summary>
  47. public float rootMotionTranslateXPerY = 0;
  48. /// <summary>Skeleton space Y translation per skeleton space X translation root motion.</summary>
  49. public float rootMotionTranslateYPerX = 0;
  50. [Header("Optional")]
  51. public Rigidbody2D rigidBody2D;
  52. public Rigidbody rigidBody;
  53. public bool UsesRigidbody {
  54. get { return rigidBody != null || rigidBody2D != null; }
  55. }
  56. #endregion
  57. protected ISkeletonComponent skeletonComponent;
  58. protected Bone rootMotionBone;
  59. protected int rootMotionBoneIndex;
  60. protected List<Bone> topLevelBones = new List<Bone>();
  61. protected Vector2 initialOffset = Vector2.zero;
  62. protected Vector2 tempSkeletonDisplacement;
  63. protected Vector2 rigidbodyDisplacement;
  64. protected virtual void Reset () {
  65. FindRigidbodyComponent();
  66. }
  67. protected virtual void Start () {
  68. skeletonComponent = GetComponent<ISkeletonComponent>();
  69. GatherTopLevelBones();
  70. SetRootMotionBone(rootMotionBoneName);
  71. if (rootMotionBone != null)
  72. initialOffset = new Vector2(rootMotionBone.x, rootMotionBone.y);
  73. var skeletonAnimation = skeletonComponent as ISkeletonAnimation;
  74. if (skeletonAnimation != null) {
  75. skeletonAnimation.UpdateLocal -= HandleUpdateLocal;
  76. skeletonAnimation.UpdateLocal += HandleUpdateLocal;
  77. }
  78. }
  79. protected virtual void FixedUpdate () {
  80. if (!this.isActiveAndEnabled)
  81. return; // Root motion is only applied when component is enabled.
  82. if (rigidBody2D != null) {
  83. rigidBody2D.MovePosition(new Vector2(transform.position.x, transform.position.y)
  84. + rigidbodyDisplacement);
  85. }
  86. if (rigidBody != null) {
  87. rigidBody.MovePosition(transform.position
  88. + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0));
  89. }
  90. Vector2 parentBoneScale;
  91. GetScaleAffectingRootMotion(out parentBoneScale);
  92. ClearEffectiveBoneOffsets(parentBoneScale);
  93. rigidbodyDisplacement = Vector2.zero;
  94. tempSkeletonDisplacement = Vector2.zero;
  95. }
  96. protected virtual void OnDisable () {
  97. rigidbodyDisplacement = Vector2.zero;
  98. tempSkeletonDisplacement = Vector2.zero;
  99. }
  100. protected void FindRigidbodyComponent () {
  101. rigidBody2D = this.GetComponent<Rigidbody2D>();
  102. if (!rigidBody2D)
  103. rigidBody = this.GetComponent<Rigidbody>();
  104. if (!rigidBody2D && !rigidBody) {
  105. rigidBody2D = this.GetComponentInParent<Rigidbody2D>();
  106. if (!rigidBody2D)
  107. rigidBody = this.GetComponentInParent<Rigidbody>();
  108. }
  109. }
  110. protected virtual float AdditionalScale { get { return 1.0f; } }
  111. abstract protected Vector2 CalculateAnimationsMovementDelta ();
  112. abstract public Vector2 GetRemainingRootMotion (int trackIndex = 0);
  113. public struct RootMotionInfo {
  114. public Vector2 start;
  115. public Vector2 current;
  116. public Vector2 mid;
  117. public Vector2 end;
  118. public bool timeIsPastMid;
  119. };
  120. abstract public RootMotionInfo GetRootMotionInfo (int trackIndex = 0);
  121. public void SetRootMotionBone (string name) {
  122. var skeleton = skeletonComponent.Skeleton;
  123. int index = skeleton.FindBoneIndex(name);
  124. if (index >= 0) {
  125. this.rootMotionBoneIndex = index;
  126. this.rootMotionBone = skeleton.bones.Items[index];
  127. }
  128. else {
  129. Debug.Log("Bone named \"" + name + "\" could not be found.");
  130. this.rootMotionBoneIndex = 0;
  131. this.rootMotionBone = skeleton.RootBone;
  132. }
  133. }
  134. public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0, bool adjustX = true, bool adjustY = true,
  135. float minX = 0, float maxX = float.MaxValue, float minY = 0, float maxY = float.MaxValue,
  136. bool allowXTranslation = false, bool allowYTranslation = false) {
  137. Vector2 distanceToTargetSkeletonSpace = (Vector2)transform.InverseTransformVector(distanceToTarget);
  138. Vector2 scaleAffectingRootMotion = GetScaleAffectingRootMotion();
  139. if (UsesRigidbody)
  140. distanceToTargetSkeletonSpace -= tempSkeletonDisplacement;
  141. Vector2 remainingRootMotionSkeletonSpace = GetRemainingRootMotion(trackIndex);
  142. remainingRootMotionSkeletonSpace.Scale(scaleAffectingRootMotion);
  143. if (remainingRootMotionSkeletonSpace.x == 0)
  144. remainingRootMotionSkeletonSpace.x = 0.0001f;
  145. if (remainingRootMotionSkeletonSpace.y == 0)
  146. remainingRootMotionSkeletonSpace.y = 0.0001f;
  147. if (adjustX)
  148. rootMotionScaleX = Math.Min(maxX, Math.Max(minX, distanceToTargetSkeletonSpace.x / remainingRootMotionSkeletonSpace.x));
  149. if (adjustY)
  150. rootMotionScaleY = Math.Min(maxY, Math.Max(minY, distanceToTargetSkeletonSpace.y / remainingRootMotionSkeletonSpace.y));
  151. if (allowXTranslation)
  152. rootMotionTranslateXPerY = (distanceToTargetSkeletonSpace.x - remainingRootMotionSkeletonSpace.x * rootMotionScaleX) / remainingRootMotionSkeletonSpace.y;
  153. if (allowYTranslation)
  154. rootMotionTranslateYPerX = (distanceToTargetSkeletonSpace.y - remainingRootMotionSkeletonSpace.y * rootMotionScaleY) / remainingRootMotionSkeletonSpace.x;
  155. }
  156. public Vector2 GetAnimationRootMotion (Animation animation) {
  157. return GetAnimationRootMotion(0, animation.duration, animation);
  158. }
  159. public Vector2 GetAnimationRootMotion (float startTime, float endTime,
  160. Animation animation) {
  161. var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
  162. if (timeline != null) {
  163. return GetTimelineMovementDelta(startTime, endTime, timeline, animation);
  164. }
  165. return Vector2.zero;
  166. }
  167. public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
  168. RootMotionInfo rootMotion = new RootMotionInfo();
  169. var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
  170. if (timeline != null) {
  171. float duration = animation.duration;
  172. float mid = duration * 0.5f;
  173. rootMotion.start = timeline.Evaluate(0);
  174. rootMotion.current = timeline.Evaluate(currentTime);
  175. rootMotion.mid = timeline.Evaluate(mid);
  176. rootMotion.end = timeline.Evaluate(duration);
  177. rootMotion.timeIsPastMid = currentTime > mid;
  178. }
  179. return rootMotion;
  180. }
  181. Vector2 GetTimelineMovementDelta (float startTime, float endTime,
  182. TranslateTimeline timeline, Animation animation) {
  183. Vector2 currentDelta;
  184. if (startTime > endTime) // Looped
  185. currentDelta = (timeline.Evaluate(animation.duration) - timeline.Evaluate(startTime))
  186. + (timeline.Evaluate(endTime) - timeline.Evaluate(0));
  187. else if (startTime != endTime) // Non-looped
  188. currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime);
  189. else
  190. currentDelta = Vector2.zero;
  191. return currentDelta;
  192. }
  193. void GatherTopLevelBones () {
  194. topLevelBones.Clear();
  195. var skeleton = skeletonComponent.Skeleton;
  196. foreach (var bone in skeleton.Bones) {
  197. if (bone.Parent == null)
  198. topLevelBones.Add(bone);
  199. }
  200. }
  201. void HandleUpdateLocal (ISkeletonAnimation animatedSkeletonComponent) {
  202. if (!this.isActiveAndEnabled)
  203. return; // Root motion is only applied when component is enabled.
  204. var boneLocalDelta = CalculateAnimationsMovementDelta();
  205. Vector2 parentBoneScale;
  206. Vector2 skeletonDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale);
  207. ApplyRootMotion(skeletonDelta, parentBoneScale);
  208. }
  209. void ApplyRootMotion (Vector2 skeletonDelta, Vector2 parentBoneScale) {
  210. // Apply root motion to Transform or RigidBody;
  211. if (UsesRigidbody) {
  212. rigidbodyDisplacement += (Vector2)transform.TransformVector(skeletonDelta);
  213. // Accumulated displacement is applied on the next Physics update in FixedUpdate.
  214. // Until the next Physics update, tempBoneDisplacement is offsetting bone locations
  215. // to prevent stutter which would otherwise occur if we don't move every Update.
  216. tempSkeletonDisplacement += skeletonDelta;
  217. SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, parentBoneScale);
  218. }
  219. else {
  220. transform.position += transform.TransformVector(skeletonDelta);
  221. ClearEffectiveBoneOffsets(parentBoneScale);
  222. }
  223. }
  224. Vector2 GetScaleAffectingRootMotion () {
  225. Vector2 parentBoneScale;
  226. return GetScaleAffectingRootMotion(out parentBoneScale);
  227. }
  228. Vector2 GetScaleAffectingRootMotion (out Vector2 parentBoneScale) {
  229. var skeleton = skeletonComponent.Skeleton;
  230. Vector2 totalScale = Vector2.one;
  231. totalScale.x *= skeleton.ScaleX;
  232. totalScale.y *= skeleton.ScaleY;
  233. parentBoneScale = Vector2.one;
  234. Bone scaleBone = rootMotionBone;
  235. while ((scaleBone = scaleBone.parent) != null) {
  236. parentBoneScale.x *= scaleBone.ScaleX;
  237. parentBoneScale.y *= scaleBone.ScaleY;
  238. }
  239. totalScale = Vector2.Scale(totalScale, parentBoneScale);
  240. totalScale *= AdditionalScale;
  241. return totalScale;
  242. }
  243. Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale) {
  244. Vector2 skeletonDelta = boneLocalDelta;
  245. Vector2 totalScale = GetScaleAffectingRootMotion(out parentBoneScale);
  246. skeletonDelta.Scale(totalScale);
  247. Vector2 rootMotionTranslation = new Vector2(
  248. rootMotionTranslateXPerY * skeletonDelta.y,
  249. rootMotionTranslateYPerX * skeletonDelta.x);
  250. skeletonDelta.x *= rootMotionScaleX;
  251. skeletonDelta.y *= rootMotionScaleY;
  252. skeletonDelta.x += rootMotionTranslation.x;
  253. skeletonDelta.y += rootMotionTranslation.y;
  254. if (!transformPositionX) skeletonDelta.x = 0f;
  255. if (!transformPositionY) skeletonDelta.y = 0f;
  256. return skeletonDelta;
  257. }
  258. void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) {
  259. // Move top level bones in opposite direction of the root motion bone
  260. var skeleton = skeletonComponent.Skeleton;
  261. foreach (var topLevelBone in topLevelBones) {
  262. if (topLevelBone == rootMotionBone) {
  263. if (transformPositionX) topLevelBone.x = displacementSkeletonSpace.x / skeleton.ScaleX;
  264. if (transformPositionY) topLevelBone.y = displacementSkeletonSpace.y / skeleton.ScaleY;
  265. }
  266. else {
  267. float offsetX = (initialOffset.x - rootMotionBone.x) * parentBoneScale.x;
  268. float offsetY = (initialOffset.y - rootMotionBone.y) * parentBoneScale.y;
  269. if (transformPositionX) topLevelBone.x = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
  270. if (transformPositionY) topLevelBone.y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
  271. }
  272. }
  273. }
  274. void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) {
  275. SetEffectiveBoneOffsetsTo(Vector2.zero, parentBoneScale);
  276. }
  277. }
  278. }