SkeletonRootMotionBase.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, 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 or
  13. * 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 THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. // In order to respect TransformConstraints modifying the scale of parent bones,
  30. // GetScaleAffectingRootMotion() now uses parentBone.AScaleX and AScaleY instead
  31. // of previously used ScaleX and ScaleY. If you require the previous behaviour,
  32. // comment out the define below.
  33. #define USE_APPLIED_PARENT_SCALE
  34. using Spine.Unity.AnimationTools;
  35. using System;
  36. using System.Collections.Generic;
  37. using UnityEngine;
  38. namespace Spine.Unity {
  39. /// <summary>
  40. /// Base class for skeleton root motion components.
  41. /// </summary>
  42. [DefaultExecutionOrder(1)]
  43. abstract public class SkeletonRootMotionBase : MonoBehaviour {
  44. #region Inspector
  45. [SpineBone]
  46. public string rootMotionBoneName = "root";
  47. public bool transformPositionX = true;
  48. public bool transformPositionY = true;
  49. public bool transformRotation = false;
  50. public float rootMotionScaleX = 1;
  51. public float rootMotionScaleY = 1;
  52. public float rootMotionScaleRotation = 1;
  53. /// <summary>Skeleton space X translation per skeleton space Y translation root motion.</summary>
  54. public float rootMotionTranslateXPerY = 0;
  55. /// <summary>Skeleton space Y translation per skeleton space X translation root motion.</summary>
  56. public float rootMotionTranslateYPerX = 0;
  57. [Header("Optional")]
  58. public Rigidbody2D rigidBody2D;
  59. public bool applyRigidbody2DGravity = false;
  60. public Rigidbody rigidBody;
  61. /// <summary>Delegate type for customizing application of rootmotion.
  62. public delegate void RootMotionDelegate (SkeletonRootMotionBase component, Vector2 translation, float rotation);
  63. /// <summary>This callback can be used to apply root-motion in a custom way. It is raised after evaluating
  64. /// this animation frame's root-motion, before it is potentially applied (see <see cref="disableOnOverride"/>)
  65. /// to either Transform or Rigidbody.
  66. /// When <see cref="SkeletonAnimation.UpdateTiming"/> is set to <see cref="UpdateTiming.InUpdate"/>, multiple
  67. /// animation frames might take place before <c>FixedUpdate</c> is called once.
  68. /// The callback parameters <c>translation</c> and <c>rotation</c> are filled out with
  69. /// this animation frame's skeleton-space root-motion (not cumulated). You can use
  70. /// e.g. <c>transform.TransformVector()</c> to transform skeleton-space root-motion to world space.
  71. /// </summary>
  72. /// <seealso cref="PhysicsUpdateRootMotionOverride"/>
  73. public event RootMotionDelegate ProcessRootMotionOverride;
  74. /// <summary>This callback can be used to apply root-motion in a custom way. It is raised in FixedUpdate
  75. /// after (when <see cref="disableOnOverride"/> is set to false) or instead of when root-motion
  76. /// would be applied at the Rigidbody.
  77. /// When <see cref="SkeletonAnimation.UpdateTiming"/> is set to <see cref="UpdateTiming.InUpdate"/>, multiple
  78. /// animation frames might take place before before <c>FixedUpdate</c> is called once.
  79. /// The callback parameters <c>translation</c> and <c>rotation</c> are filled out with the
  80. /// (cumulated) skeleton-space root-motion since the the last <c>FixedUpdate</c> call. You can use
  81. /// e.g. <c>transform.TransformVector()</c> to transform skeleton-space root-motion to world space.
  82. /// </summary>
  83. /// <seealso cref="ProcessRootMotionOverride"/>
  84. public event RootMotionDelegate PhysicsUpdateRootMotionOverride;
  85. /// <summary>When true, root-motion is not applied to the Transform or Rigidbody.
  86. /// Otherwise the delegate callbacks are issued additionally.</summary>
  87. public bool disableOnOverride = true;
  88. public Bone RootMotionBone { get { return rootMotionBone; } }
  89. public bool UsesRigidbody {
  90. get { return rigidBody != null || rigidBody2D != null; }
  91. }
  92. /// <summary>Root motion translation that has been applied in the preceding <c>FixedUpdate</c> call
  93. /// if a rigidbody is assigned at either <c>rigidbody</c> or <c>rigidbody2D</c>.
  94. /// Returns <c>Vector2.zero</c> when <c>rigidbody</c> and <c>rigidbody2D</c> are null.
  95. /// This can be necessary when multiple scripts call <c>Rigidbody2D.MovePosition</c>,
  96. /// where the last call overwrites the effect of preceding ones.</summary>
  97. public Vector2 PreviousRigidbodyRootMotion2D {
  98. get { return new Vector2(previousRigidbodyRootMotion.x, previousRigidbodyRootMotion.y); }
  99. }
  100. /// <summary>Root motion translation that has been applied in the preceding <c>FixedUpdate</c> call
  101. /// if a rigidbody is assigned at either <c>rigidbody</c> or <c>rigidbody2D</c>.
  102. /// Returns <c>Vector3.zero</c> when <c>rigidbody</c> and <c>rigidbody2D</c> are null.</summary>
  103. public Vector3 PreviousRigidbodyRootMotion3D {
  104. get { return previousRigidbodyRootMotion; }
  105. }
  106. /// <summary>Additional translation to add to <c>Rigidbody2D.MovePosition</c>
  107. /// called in FixedUpdate. This can be necessary when multiple scripts call
  108. /// <c>MovePosition</c>, where the last call overwrites the effect of preceding ones.
  109. /// Has no effect if <c>rigidBody2D</c> is null.</summary>
  110. public Vector2 AdditionalRigidbody2DMovement {
  111. get { return additionalRigidbody2DMovement; }
  112. set { additionalRigidbody2DMovement = value; }
  113. }
  114. #endregion
  115. protected bool SkeletonAnimationUsesFixedUpdate {
  116. get {
  117. ISkeletonAnimation skeletonAnimation = skeletonComponent as ISkeletonAnimation;
  118. if (skeletonAnimation != null) {
  119. return skeletonAnimation.UpdateTiming == UpdateTiming.InFixedUpdate;
  120. }
  121. return false;
  122. }
  123. }
  124. protected ISkeletonComponent skeletonComponent;
  125. protected Bone rootMotionBone;
  126. protected int rootMotionBoneIndex;
  127. protected List<int> transformConstraintIndices = new List<int>();
  128. protected List<Vector2> transformConstraintLastPos = new List<Vector2>();
  129. protected List<float> transformConstraintLastRotation = new List<float>();
  130. protected List<Bone> topLevelBones = new List<Bone>();
  131. protected Vector2 initialOffset = Vector2.zero;
  132. protected bool accumulatedUntilFixedUpdate = false;
  133. protected Vector2 tempSkeletonDisplacement;
  134. protected Vector3 rigidbodyDisplacement;
  135. protected Vector3 previousRigidbodyRootMotion = Vector2.zero;
  136. protected Vector2 additionalRigidbody2DMovement = Vector2.zero;
  137. protected Quaternion rigidbodyLocalRotation = Quaternion.identity;
  138. protected float rigidbody2DRotation;
  139. protected float initialOffsetRotation;
  140. protected float tempSkeletonRotation;
  141. protected virtual void Reset () {
  142. FindRigidbodyComponent();
  143. }
  144. protected virtual void Start () {
  145. Initialize();
  146. }
  147. protected void InitializeOnRebuild (ISkeletonAnimation animatedSkeletonComponent) {
  148. Initialize();
  149. }
  150. public virtual void Initialize () {
  151. skeletonComponent = GetComponent<ISkeletonComponent>();
  152. GatherTopLevelBones();
  153. SetRootMotionBone(rootMotionBoneName);
  154. if (rootMotionBone != null) {
  155. initialOffset = new Vector2(rootMotionBone.X, rootMotionBone.Y);
  156. initialOffsetRotation = rootMotionBone.Rotation;
  157. }
  158. ISkeletonAnimation skeletonAnimation = skeletonComponent as ISkeletonAnimation;
  159. if (skeletonAnimation != null) {
  160. skeletonAnimation.UpdateLocal -= HandleUpdateLocal;
  161. skeletonAnimation.UpdateLocal += HandleUpdateLocal;
  162. skeletonAnimation.OnAnimationRebuild -= InitializeOnRebuild;
  163. skeletonAnimation.OnAnimationRebuild += InitializeOnRebuild;
  164. SkeletonUtility skeletonUtility = GetComponent<SkeletonUtility>();
  165. if (skeletonUtility != null) {
  166. // SkeletonUtilityBone shall receive UpdateLocal callbacks for bone-following after root motion
  167. // clears the root-bone position.
  168. skeletonUtility.ResubscribeEvents();
  169. }
  170. }
  171. }
  172. protected virtual void FixedUpdate () {
  173. // Root motion is only applied when component is enabled.
  174. if (!this.isActiveAndEnabled)
  175. return;
  176. // When SkeletonAnimation component uses UpdateTiming.InFixedUpdate,
  177. // we directly call PhysicsUpdate in HandleUpdateLocal instead of here.
  178. if (!SkeletonAnimationUsesFixedUpdate)
  179. PhysicsUpdate(false);
  180. }
  181. protected virtual void PhysicsUpdate (bool skeletonAnimationUsesFixedUpdate) {
  182. Vector2 callbackDisplacement = tempSkeletonDisplacement;
  183. float callbackRotation = tempSkeletonRotation;
  184. bool isApplyAtRigidbodyAllowed = PhysicsUpdateRootMotionOverride == null || !disableOnOverride;
  185. if (isApplyAtRigidbodyAllowed) {
  186. if (rigidBody2D != null) {
  187. Vector2 gravityAndVelocityMovement = Vector2.zero;
  188. if (applyRigidbody2DGravity) {
  189. float deltaTime = Time.fixedDeltaTime;
  190. float deltaTimeSquared = (deltaTime * deltaTime);
  191. rigidBody2D.velocity += rigidBody2D.gravityScale * Physics2D.gravity * deltaTime;
  192. gravityAndVelocityMovement = 0.5f * rigidBody2D.gravityScale * Physics2D.gravity * deltaTimeSquared +
  193. rigidBody2D.velocity * deltaTime;
  194. }
  195. Vector2 rigidbodyDisplacement2D = new Vector2(rigidbodyDisplacement.x, rigidbodyDisplacement.y);
  196. // Note: MovePosition seems to be the only precise and reliable way to set movement delta,
  197. // for both 2D and 3D rigidbodies.
  198. // Setting velocity like "rigidBody2D.velocity = movement/deltaTime" works perfectly in mid-air
  199. // without gravity and ground collision, unfortunately when on the ground, friction causes severe
  200. // slowdown. Using a zero-friction PhysicsMaterial leads to sliding endlessly along the ground as
  201. // soon as forces are applied. Additionally, there is no rigidBody2D.isGrounded, requiring our own
  202. // checks.
  203. rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(rigidBody2D.position.x, rigidBody2D.position.y)
  204. + rigidbodyDisplacement2D + additionalRigidbody2DMovement);
  205. rigidBody2D.MoveRotation(rigidbody2DRotation + rigidBody2D.rotation);
  206. } else if (rigidBody != null) {
  207. rigidBody.MovePosition(rigidBody.position
  208. + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, rigidbodyDisplacement.z));
  209. rigidBody.MoveRotation(rigidBody.rotation * rigidbodyLocalRotation);
  210. }
  211. }
  212. previousRigidbodyRootMotion = rigidbodyDisplacement;
  213. if (accumulatedUntilFixedUpdate) {
  214. Vector2 parentBoneScale;
  215. GetScaleAffectingRootMotion(out parentBoneScale);
  216. ClearEffectiveBoneOffsets(parentBoneScale);
  217. skeletonComponent.Skeleton.UpdateWorldTransform(Skeleton.Physics.Pose);
  218. }
  219. ClearRigidbodyTempMovement();
  220. if (PhysicsUpdateRootMotionOverride != null)
  221. PhysicsUpdateRootMotionOverride(this, callbackDisplacement, callbackRotation);
  222. }
  223. protected virtual void OnDisable () {
  224. ClearRigidbodyTempMovement();
  225. }
  226. protected void FindRigidbodyComponent () {
  227. rigidBody2D = this.GetComponent<Rigidbody2D>();
  228. if (!rigidBody2D)
  229. rigidBody = this.GetComponent<Rigidbody>();
  230. if (!rigidBody2D && !rigidBody) {
  231. rigidBody2D = this.GetComponentInParent<Rigidbody2D>();
  232. if (!rigidBody2D)
  233. rigidBody = this.GetComponentInParent<Rigidbody>();
  234. }
  235. }
  236. protected virtual float AdditionalScale { get { return 1.0f; } }
  237. abstract protected Vector2 CalculateAnimationsMovementDelta ();
  238. protected virtual float CalculateAnimationsRotationDelta () { return 0; }
  239. abstract public Vector2 GetRemainingRootMotion (int trackIndex = 0);
  240. public struct RootMotionInfo {
  241. public Vector2 start;
  242. public Vector2 current;
  243. public Vector2 mid;
  244. public Vector2 end;
  245. public bool timeIsPastMid;
  246. };
  247. abstract public RootMotionInfo GetRootMotionInfo (int trackIndex = 0);
  248. public ISkeletonComponent TargetSkeletonComponent {
  249. get {
  250. if (skeletonComponent == null)
  251. skeletonComponent = GetComponent<ISkeletonComponent>();
  252. return skeletonComponent;
  253. }
  254. }
  255. public ISkeletonAnimation TargetSkeletonAnimationComponent {
  256. get { return TargetSkeletonComponent as ISkeletonAnimation; }
  257. }
  258. public void SetRootMotionBone (string name) {
  259. Skeleton skeleton = skeletonComponent.Skeleton;
  260. Bone bone = skeleton.FindBone(name);
  261. if (bone != null) {
  262. this.rootMotionBoneIndex = bone.Data.Index;
  263. this.rootMotionBone = bone;
  264. FindTransformConstraintsAffectingBone();
  265. } else {
  266. Debug.Log("Bone named \"" + name + "\" could not be found. " +
  267. "Set 'skeletonRootMotion.rootMotionBoneName' before calling 'skeletonAnimation.Initialize(true)'.");
  268. this.rootMotionBoneIndex = 0;
  269. this.rootMotionBone = skeleton.RootBone;
  270. }
  271. }
  272. public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0, bool adjustX = true, bool adjustY = true,
  273. float minX = 0, float maxX = float.MaxValue, float minY = 0, float maxY = float.MaxValue,
  274. bool allowXTranslation = false, bool allowYTranslation = false) {
  275. Vector2 distanceToTargetSkeletonSpace = (Vector2)transform.InverseTransformVector(distanceToTarget);
  276. Vector2 scaleAffectingRootMotion = GetScaleAffectingRootMotion();
  277. if (UsesRigidbody)
  278. distanceToTargetSkeletonSpace -= tempSkeletonDisplacement;
  279. Vector2 remainingRootMotionSkeletonSpace = GetRemainingRootMotion(trackIndex);
  280. remainingRootMotionSkeletonSpace.Scale(scaleAffectingRootMotion);
  281. if (remainingRootMotionSkeletonSpace.x == 0)
  282. remainingRootMotionSkeletonSpace.x = 0.0001f;
  283. if (remainingRootMotionSkeletonSpace.y == 0)
  284. remainingRootMotionSkeletonSpace.y = 0.0001f;
  285. if (adjustX)
  286. rootMotionScaleX = Math.Min(maxX, Math.Max(minX, distanceToTargetSkeletonSpace.x / remainingRootMotionSkeletonSpace.x));
  287. if (adjustY)
  288. rootMotionScaleY = Math.Min(maxY, Math.Max(minY, distanceToTargetSkeletonSpace.y / remainingRootMotionSkeletonSpace.y));
  289. if (allowXTranslation)
  290. rootMotionTranslateXPerY = (distanceToTargetSkeletonSpace.x - remainingRootMotionSkeletonSpace.x * rootMotionScaleX) / remainingRootMotionSkeletonSpace.y;
  291. if (allowYTranslation)
  292. rootMotionTranslateYPerX = (distanceToTargetSkeletonSpace.y - remainingRootMotionSkeletonSpace.y * rootMotionScaleY) / remainingRootMotionSkeletonSpace.x;
  293. }
  294. public Vector2 GetAnimationRootMotion (Animation animation) {
  295. return GetAnimationRootMotion(0, animation.Duration, animation);
  296. }
  297. public Vector2 GetAnimationRootMotion (float startTime, float endTime,
  298. Animation animation) {
  299. if (startTime == endTime)
  300. return Vector2.zero;
  301. TranslateTimeline translateTimeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
  302. TranslateXTimeline xTimeline = animation.FindTimelineForBone<TranslateXTimeline>(rootMotionBoneIndex);
  303. TranslateYTimeline yTimeline = animation.FindTimelineForBone<TranslateYTimeline>(rootMotionBoneIndex);
  304. // Non-looped base
  305. Vector2 endPos = Vector2.zero;
  306. Vector2 startPos = Vector2.zero;
  307. if (translateTimeline != null) {
  308. endPos = translateTimeline.Evaluate(endTime);
  309. startPos = translateTimeline.Evaluate(startTime);
  310. } else if (xTimeline != null || yTimeline != null) {
  311. endPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime);
  312. startPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime);
  313. }
  314. TransformConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items;
  315. foreach (int constraintIndex in this.transformConstraintIndices) {
  316. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  317. ApplyConstraintToPos(animation, constraint, constraintIndex, endTime, false, ref endPos);
  318. ApplyConstraintToPos(animation, constraint, constraintIndex, startTime, true, ref startPos);
  319. }
  320. Vector2 currentDelta = endPos - startPos;
  321. // Looped additions
  322. if (startTime > endTime) {
  323. Vector2 loopPos = Vector2.zero;
  324. Vector2 zeroPos = Vector2.zero;
  325. if (translateTimeline != null) {
  326. loopPos = translateTimeline.Evaluate(animation.Duration);
  327. zeroPos = translateTimeline.Evaluate(0);
  328. } else if (xTimeline != null || yTimeline != null) {
  329. loopPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, animation.Duration);
  330. zeroPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, 0);
  331. }
  332. foreach (int constraintIndex in this.transformConstraintIndices) {
  333. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  334. ApplyConstraintToPos(animation, constraint, constraintIndex, animation.Duration, false, ref loopPos);
  335. ApplyConstraintToPos(animation, constraint, constraintIndex, 0, false, ref zeroPos);
  336. }
  337. currentDelta += loopPos - zeroPos;
  338. }
  339. UpdateLastConstraintPos(transformConstraintsItems);
  340. return currentDelta;
  341. }
  342. public float GetAnimationRootMotionRotation (Animation animation) {
  343. return GetAnimationRootMotionRotation(0, animation.Duration, animation);
  344. }
  345. public float GetAnimationRootMotionRotation (float startTime, float endTime,
  346. Animation animation) {
  347. if (startTime == endTime)
  348. return 0;
  349. RotateTimeline rotateTimeline = animation.FindTimelineForBone<RotateTimeline>(rootMotionBoneIndex);
  350. // Non-looped base
  351. float endRotation = 0;
  352. float startRotation = 0;
  353. if (rotateTimeline != null) {
  354. endRotation = rotateTimeline.Evaluate(endTime);
  355. startRotation = rotateTimeline.Evaluate(startTime);
  356. }
  357. TransformConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items;
  358. foreach (int constraintIndex in this.transformConstraintIndices) {
  359. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  360. ApplyConstraintToRotation(animation, constraint, constraintIndex, endTime, false, ref endRotation);
  361. ApplyConstraintToRotation(animation, constraint, constraintIndex, startTime, true, ref startRotation);
  362. }
  363. float currentDelta = endRotation - startRotation;
  364. // Looped additions
  365. if (startTime > endTime) {
  366. float loopRotation = 0;
  367. float zeroPos = 0;
  368. if (rotateTimeline != null) {
  369. loopRotation = rotateTimeline.Evaluate(animation.Duration);
  370. zeroPos = rotateTimeline.Evaluate(0);
  371. }
  372. foreach (int constraintIndex in this.transformConstraintIndices) {
  373. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  374. ApplyConstraintToRotation(animation, constraint, constraintIndex, animation.Duration, false, ref loopRotation);
  375. ApplyConstraintToRotation(animation, constraint, constraintIndex, 0, false, ref zeroPos);
  376. }
  377. currentDelta += loopRotation - zeroPos;
  378. }
  379. UpdateLastConstraintRotation(transformConstraintsItems);
  380. return currentDelta;
  381. }
  382. void ApplyConstraintToPos (Animation animation, TransformConstraint constraint,
  383. int constraintIndex, float time, bool useLastConstraintPos, ref Vector2 pos) {
  384. TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
  385. if (timeline == null)
  386. return;
  387. Vector2 mixXY = timeline.EvaluateTranslateXYMix(time);
  388. Vector2 invMixXY = timeline.EvaluateTranslateXYMix(time);
  389. Vector2 constraintPos;
  390. if (useLastConstraintPos)
  391. constraintPos = transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)];
  392. else {
  393. Bone targetBone = constraint.Target;
  394. constraintPos = new Vector2(targetBone.X, targetBone.Y);
  395. }
  396. pos = new Vector2(
  397. pos.x * invMixXY.x + constraintPos.x * mixXY.x,
  398. pos.y * invMixXY.y + constraintPos.y * mixXY.y);
  399. }
  400. void ApplyConstraintToRotation (Animation animation, TransformConstraint constraint,
  401. int constraintIndex, float time, bool useLastConstraintRotation, ref float rotation) {
  402. TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
  403. if (timeline == null)
  404. return;
  405. float mixRotate = timeline.EvaluateRotateMix(time);
  406. float invMixRotate = timeline.EvaluateRotateMix(time);
  407. float constraintRotation;
  408. if (useLastConstraintRotation)
  409. constraintRotation = transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)];
  410. else {
  411. Bone targetBone = constraint.Target;
  412. constraintRotation = targetBone.Rotation;
  413. }
  414. rotation = rotation * invMixRotate + constraintRotation * mixRotate;
  415. }
  416. void UpdateLastConstraintPos (TransformConstraint[] transformConstraintsItems) {
  417. foreach (int constraintIndex in this.transformConstraintIndices) {
  418. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  419. Bone targetBone = constraint.Target;
  420. transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)] = new Vector2(targetBone.X, targetBone.Y);
  421. }
  422. }
  423. void UpdateLastConstraintRotation (TransformConstraint[] transformConstraintsItems) {
  424. foreach (int constraintIndex in this.transformConstraintIndices) {
  425. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  426. Bone targetBone = constraint.Target;
  427. transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)] = targetBone.Rotation;
  428. }
  429. }
  430. public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
  431. RootMotionInfo rootMotion = new RootMotionInfo();
  432. float duration = animation.Duration;
  433. float mid = duration * 0.5f;
  434. rootMotion.timeIsPastMid = currentTime > mid;
  435. TranslateTimeline timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
  436. if (timeline != null) {
  437. rootMotion.start = timeline.Evaluate(0);
  438. rootMotion.current = timeline.Evaluate(currentTime);
  439. rootMotion.mid = timeline.Evaluate(mid);
  440. rootMotion.end = timeline.Evaluate(duration);
  441. return rootMotion;
  442. }
  443. TranslateXTimeline xTimeline = animation.FindTimelineForBone<TranslateXTimeline>(rootMotionBoneIndex);
  444. TranslateYTimeline yTimeline = animation.FindTimelineForBone<TranslateYTimeline>(rootMotionBoneIndex);
  445. if (xTimeline != null || yTimeline != null) {
  446. rootMotion.start = TimelineExtensions.Evaluate(xTimeline, yTimeline, 0);
  447. rootMotion.current = TimelineExtensions.Evaluate(xTimeline, yTimeline, currentTime);
  448. rootMotion.mid = TimelineExtensions.Evaluate(xTimeline, yTimeline, mid);
  449. rootMotion.end = TimelineExtensions.Evaluate(xTimeline, yTimeline, duration);
  450. return rootMotion;
  451. }
  452. return rootMotion;
  453. }
  454. int GetConstraintLastPosIndex (int constraintIndex) {
  455. ExposedList<TransformConstraint> constraints = skeletonComponent.Skeleton.TransformConstraints;
  456. return transformConstraintIndices.FindIndex(addedIndex => addedIndex == constraintIndex);
  457. }
  458. void FindTransformConstraintsAffectingBone () {
  459. ExposedList<TransformConstraint> constraints = skeletonComponent.Skeleton.TransformConstraints;
  460. TransformConstraint[] constraintsItems = constraints.Items;
  461. for (int i = 0, n = constraints.Count; i < n; ++i) {
  462. TransformConstraint constraint = constraintsItems[i];
  463. if (constraint.Bones.Contains(rootMotionBone)) {
  464. transformConstraintIndices.Add(i);
  465. Bone targetBone = constraint.Target;
  466. Vector2 constraintPos = new Vector2(targetBone.X, targetBone.Y);
  467. transformConstraintLastPos.Add(constraintPos);
  468. transformConstraintLastRotation.Add(targetBone.Rotation);
  469. }
  470. }
  471. }
  472. Vector2 GetTimelineMovementDelta (float startTime, float endTime,
  473. TranslateXTimeline xTimeline, TranslateYTimeline yTimeline, Animation animation) {
  474. Vector2 currentDelta;
  475. if (startTime > endTime) // Looped
  476. currentDelta =
  477. (TimelineExtensions.Evaluate(xTimeline, yTimeline, animation.Duration)
  478. - TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime))
  479. + (TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime)
  480. - TimelineExtensions.Evaluate(xTimeline, yTimeline, 0));
  481. else if (startTime != endTime) // Non-looped
  482. currentDelta = TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime)
  483. - TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime);
  484. else
  485. currentDelta = Vector2.zero;
  486. return currentDelta;
  487. }
  488. void GatherTopLevelBones () {
  489. topLevelBones.Clear();
  490. Skeleton skeleton = skeletonComponent.Skeleton;
  491. foreach (Bone bone in skeleton.Bones) {
  492. if (bone.Parent == null)
  493. topLevelBones.Add(bone);
  494. }
  495. }
  496. void HandleUpdateLocal (ISkeletonAnimation animatedSkeletonComponent) {
  497. if (!this.isActiveAndEnabled)
  498. return; // Root motion is only applied when component is enabled.
  499. Vector2 boneLocalDelta = CalculateAnimationsMovementDelta();
  500. Vector2 parentBoneScale;
  501. Vector2 totalScale;
  502. Vector2 skeletonTranslationDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale, out totalScale);
  503. float skeletonRotationDelta = 0;
  504. if (transformRotation) {
  505. float boneLocalDeltaRotation = CalculateAnimationsRotationDelta();
  506. boneLocalDeltaRotation *= rootMotionScaleRotation;
  507. skeletonRotationDelta = GetSkeletonSpaceRotationDelta(boneLocalDeltaRotation, totalScale);
  508. }
  509. bool usesFixedUpdate = SkeletonAnimationUsesFixedUpdate;
  510. ApplyRootMotion(skeletonTranslationDelta, skeletonRotationDelta, parentBoneScale, usesFixedUpdate);
  511. if (usesFixedUpdate)
  512. PhysicsUpdate(usesFixedUpdate);
  513. }
  514. void ApplyRootMotion (Vector2 skeletonTranslationDelta, float skeletonRotationDelta, Vector2 parentBoneScale,
  515. bool skeletonAnimationUsesFixedUpdate) {
  516. // Accumulated displacement is applied on the next Physics update in FixedUpdate.
  517. // Until the next Physics update, tempSkeletonDisplacement and tempSkeletonRotation
  518. // are offsetting bone locations to prevent stutter which would otherwise occur if
  519. // we don't move every Update.
  520. bool usesRigidbody = this.UsesRigidbody;
  521. bool applyToTransform = !usesRigidbody && (ProcessRootMotionOverride == null || !disableOnOverride);
  522. accumulatedUntilFixedUpdate = !applyToTransform && !skeletonAnimationUsesFixedUpdate;
  523. if (ProcessRootMotionOverride != null)
  524. ProcessRootMotionOverride(this, skeletonTranslationDelta, skeletonRotationDelta);
  525. // Apply root motion to Transform or update values applied to RigidBody later (must happen in FixedUpdate).
  526. if (usesRigidbody) {
  527. rigidbodyDisplacement += transform.TransformVector(skeletonTranslationDelta);
  528. if (skeletonRotationDelta != 0.0f) {
  529. if (rigidBody != null) {
  530. Quaternion addedWorldRotation = Quaternion.Euler(0, 0, skeletonRotationDelta);
  531. rigidbodyLocalRotation = rigidbodyLocalRotation * addedWorldRotation;
  532. } else if (rigidBody2D != null) {
  533. Vector3 lossyScale = transform.lossyScale;
  534. float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
  535. rigidbody2DRotation += rotationSign * skeletonRotationDelta;
  536. }
  537. }
  538. } else if (applyToTransform) {
  539. transform.position += transform.TransformVector(skeletonTranslationDelta);
  540. if (skeletonRotationDelta != 0.0f) {
  541. Vector3 lossyScale = transform.lossyScale;
  542. float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
  543. transform.Rotate(0, 0, rotationSign * skeletonRotationDelta);
  544. }
  545. }
  546. tempSkeletonDisplacement += skeletonTranslationDelta;
  547. tempSkeletonRotation += skeletonRotationDelta;
  548. if (accumulatedUntilFixedUpdate) {
  549. SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, tempSkeletonRotation, parentBoneScale);
  550. } else {
  551. ClearEffectiveBoneOffsets(parentBoneScale);
  552. }
  553. }
  554. void ApplyTransformConstraints () {
  555. rootMotionBone.AX = rootMotionBone.X;
  556. rootMotionBone.AY = rootMotionBone.Y;
  557. rootMotionBone.AppliedRotation = rootMotionBone.Rotation;
  558. TransformConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items;
  559. foreach (int constraintIndex in this.transformConstraintIndices) {
  560. TransformConstraint constraint = transformConstraintsItems[constraintIndex];
  561. // apply the constraint and sets Bone.ax, Bone.ay and Bone.arotation values.
  562. /// Update is based on Bone.x, Bone.y and Bone.rotation, so skeleton.UpdateWorldTransform()
  563. /// can be called afterwards without having a different starting point.
  564. constraint.Update(Skeleton.Physics.None);
  565. }
  566. }
  567. Vector2 GetScaleAffectingRootMotion () {
  568. Vector2 parentBoneScale;
  569. return GetScaleAffectingRootMotion(out parentBoneScale);
  570. }
  571. Vector2 GetScaleAffectingRootMotion (out Vector2 parentBoneScale) {
  572. Skeleton skeleton = skeletonComponent.Skeleton;
  573. Vector2 totalScale = Vector2.one;
  574. totalScale.x *= skeleton.ScaleX;
  575. totalScale.y *= skeleton.ScaleY;
  576. parentBoneScale = Vector2.one;
  577. Bone scaleBone = rootMotionBone;
  578. while ((scaleBone = scaleBone.Parent) != null) {
  579. #if USE_APPLIED_PARENT_SCALE
  580. parentBoneScale.x *= scaleBone.AScaleX;
  581. parentBoneScale.y *= scaleBone.AScaleY;
  582. #else
  583. parentBoneScale.x *= scaleBone.ScaleX;
  584. parentBoneScale.y *= scaleBone.ScaleY;
  585. #endif
  586. }
  587. totalScale = Vector2.Scale(totalScale, parentBoneScale);
  588. totalScale *= AdditionalScale;
  589. return totalScale;
  590. }
  591. Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale, out Vector2 totalScale) {
  592. Vector2 skeletonDelta = boneLocalDelta;
  593. totalScale = GetScaleAffectingRootMotion(out parentBoneScale);
  594. skeletonDelta.Scale(totalScale);
  595. Vector2 rootMotionTranslation = new Vector2(
  596. rootMotionTranslateXPerY * skeletonDelta.y,
  597. rootMotionTranslateYPerX * skeletonDelta.x);
  598. skeletonDelta.x *= rootMotionScaleX;
  599. skeletonDelta.y *= rootMotionScaleY;
  600. skeletonDelta.x += rootMotionTranslation.x;
  601. skeletonDelta.y += rootMotionTranslation.y;
  602. if (!transformPositionX) skeletonDelta.x = 0f;
  603. if (!transformPositionY) skeletonDelta.y = 0f;
  604. return skeletonDelta;
  605. }
  606. float GetSkeletonSpaceRotationDelta (float boneLocalDelta, Vector2 totalScaleAffectingRootMotion) {
  607. float rotationSign = totalScaleAffectingRootMotion.x * totalScaleAffectingRootMotion.y > 0 ? 1 : -1;
  608. return rotationSign * boneLocalDelta;
  609. }
  610. void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, float rotationSkeletonSpace, Vector2 parentBoneScale) {
  611. ApplyTransformConstraints();
  612. // Move top level bones in opposite direction of the root motion bone
  613. Skeleton skeleton = skeletonComponent.Skeleton;
  614. foreach (Bone topLevelBone in topLevelBones) {
  615. if (topLevelBone == rootMotionBone) {
  616. if (transformPositionX) topLevelBone.X = displacementSkeletonSpace.x / skeleton.ScaleX;
  617. if (transformPositionY) topLevelBone.Y = displacementSkeletonSpace.y / skeleton.ScaleY;
  618. if (transformRotation) {
  619. float rotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1;
  620. topLevelBone.Rotation = rotationSign * rotationSkeletonSpace;
  621. }
  622. } else {
  623. bool useAppliedTransform = transformConstraintIndices.Count > 0;
  624. float rootMotionBoneX = useAppliedTransform ? rootMotionBone.AX : rootMotionBone.X;
  625. float rootMotionBoneY = useAppliedTransform ? rootMotionBone.AY : rootMotionBone.Y;
  626. float offsetX = (initialOffset.x - rootMotionBoneX) * parentBoneScale.x;
  627. float offsetY = (initialOffset.y - rootMotionBoneY) * parentBoneScale.y;
  628. if (transformPositionX) topLevelBone.X = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
  629. if (transformPositionY) topLevelBone.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
  630. if (transformRotation) {
  631. float rootMotionBoneRotation = useAppliedTransform ? rootMotionBone.AppliedRotation : rootMotionBone.Rotation;
  632. float parentBoneRotationSign = (parentBoneScale.x * parentBoneScale.y > 0 ? 1 : -1);
  633. float offsetRotation = (initialOffsetRotation - rootMotionBoneRotation) * parentBoneRotationSign;
  634. float skeletonRotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1;
  635. topLevelBone.Rotation = (rotationSkeletonSpace * skeletonRotationSign) + offsetRotation;
  636. }
  637. }
  638. }
  639. }
  640. void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) {
  641. SetEffectiveBoneOffsetsTo(Vector2.zero, 0, parentBoneScale);
  642. }
  643. void ClearRigidbodyTempMovement () {
  644. rigidbodyDisplacement = Vector2.zero;
  645. tempSkeletonDisplacement = Vector2.zero;
  646. rigidbodyLocalRotation = Quaternion.identity;
  647. rigidbody2DRotation = 0;
  648. tempSkeletonRotation = 0;
  649. }
  650. }
  651. }