HitReaction.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. using UnityEngine;
  2. using System.Collections;
  3. namespace RootMotion.FinalIK {
  4. /// <summary>
  5. /// Class for creating procedural FBBIK hit reactions.
  6. /// </summary>
  7. public class HitReaction : OffsetModifier {
  8. /// <summary>
  9. /// Hit point definition
  10. /// </summary>
  11. [System.Serializable]
  12. public abstract class HitPoint {
  13. [Tooltip("Just for visual clarity, not used at all")]
  14. public string name;
  15. [Tooltip("Linking this hit point to a collider")]
  16. public Collider collider;
  17. [Tooltip("Only used if this hit point gets hit when already processing another hit")]
  18. [SerializeField] float crossFadeTime = 0.1f;
  19. public bool inProgress { get { return timer < length; } }
  20. protected float crossFader { get; private set; }
  21. protected float timer { get; private set; }
  22. protected Vector3 force { get; private set; }
  23. protected Vector3 point { get; private set; }
  24. private float length;
  25. private float crossFadeSpeed;
  26. private float lastTime;
  27. // Start processing the hit
  28. public void Hit(Vector3 force, Vector3 point) {
  29. if (length == 0f) length = GetLength();
  30. if (length <= 0f) {
  31. Debug.LogError("Hit Point WeightCurve length is zero.");
  32. return;
  33. }
  34. // Start crossfading if the last hit has not completed yet
  35. if (timer < 1f) crossFader = 0f;
  36. crossFadeSpeed = crossFadeTime > 0f? 1f / crossFadeTime: 0f;
  37. CrossFadeStart();
  38. // Reset timer
  39. timer = 0f;
  40. // Remember hit direction and point
  41. this.force = force;
  42. this.point = point;
  43. }
  44. // Apply to IKSolverFullBodyBiped
  45. public void Apply(IKSolverFullBodyBiped solver, float weight) {
  46. float deltaTime = Time.time - lastTime;
  47. lastTime = Time.time;
  48. if (timer >= length) {
  49. return;
  50. }
  51. // Advance the timer
  52. timer = Mathf.Clamp(timer + deltaTime, 0f, length);
  53. // Advance the crossFader
  54. if (crossFadeSpeed > 0f) crossFader = Mathf.Clamp(crossFader + (deltaTime * crossFadeSpeed), 0f, 1f);
  55. else crossFader = 1f;
  56. // Pass this on to the hit points
  57. OnApply(solver, weight);
  58. }
  59. protected abstract float GetLength();
  60. protected abstract void CrossFadeStart();
  61. protected abstract void OnApply(IKSolverFullBodyBiped solver, float weight);
  62. }
  63. /// <summary>
  64. /// Hit Point for FBBIK effectors
  65. /// </summary>
  66. [System.Serializable]
  67. public class HitPointEffector: HitPoint {
  68. /// <summary>
  69. /// Linking a FBBIK effector to this effector hit point
  70. /// </summary>
  71. [System.Serializable]
  72. public class EffectorLink {
  73. [Tooltip("The FBBIK effector type")]
  74. public FullBodyBipedEffector effector;
  75. [Tooltip("The weight of this effector (could also be negative)")]
  76. public float weight;
  77. private Vector3 lastValue;
  78. private Vector3 current;
  79. // Apply an offset to this effector
  80. public void Apply(IKSolverFullBodyBiped solver, Vector3 offset, float crossFader) {
  81. current = Vector3.Lerp(lastValue, offset * weight, crossFader);
  82. solver.GetEffector(effector).positionOffset += current;
  83. }
  84. // Remember the current offset value, so we can smoothly crossfade from it
  85. public void CrossFadeStart() {
  86. lastValue = current;
  87. }
  88. }
  89. [Tooltip("Offset magnitude in the direction of the hit force")]
  90. public AnimationCurve offsetInForceDirection; //
  91. [Tooltip("Offset magnitude in the direction of character.up")]
  92. public AnimationCurve offsetInUpDirection; //
  93. [Tooltip("Linking this offset to the FBBIK effectors")]
  94. public EffectorLink[] effectorLinks;
  95. // Returns the length of this hit (last key in the AnimationCurves)
  96. protected override float GetLength() {
  97. float time1 = offsetInForceDirection.keys.Length > 0? offsetInForceDirection.keys[offsetInForceDirection.length - 1].time: 0f;
  98. float time2 = offsetInUpDirection.keys.Length > 0? offsetInUpDirection.keys[offsetInUpDirection.length - 1].time: 0f;
  99. return Mathf.Clamp(time1, time2, time1);
  100. }
  101. // Remember the current offset values for each effector, so we can smoothly crossfade from it
  102. protected override void CrossFadeStart() {
  103. foreach (EffectorLink e in effectorLinks) e.CrossFadeStart();
  104. }
  105. // Calculate offset, apply to FBBIK effectors
  106. protected override void OnApply(IKSolverFullBodyBiped solver, float weight) {
  107. Vector3 up = solver.GetRoot().up * force.magnitude;
  108. Vector3 offset = (offsetInForceDirection.Evaluate(timer) * force) + (offsetInUpDirection.Evaluate(timer) * up);
  109. offset *= weight;
  110. foreach (EffectorLink e in effectorLinks) e.Apply(solver, offset, crossFader);
  111. }
  112. }
  113. /// <summary>
  114. /// Hit Point for simple bone Transforms that don't have a FBBIK effector
  115. /// </summary>
  116. [System.Serializable]
  117. public class HitPointBone: HitPoint {
  118. /// <summary>
  119. /// Linking a bone Transform to this bone hit point
  120. /// </summary>
  121. [System.Serializable]
  122. public class BoneLink {
  123. [Tooltip("Reference to the bone that this hit point rotates")]
  124. public Transform bone;
  125. [Tooltip("Weight of rotating the bone")]
  126. [Range(0f, 1f)] public float weight;
  127. private Quaternion lastValue = Quaternion.identity;
  128. private Quaternion current = Quaternion.identity;
  129. // Apply a rotational offset to this effector
  130. public void Apply(IKSolverFullBodyBiped solver, Quaternion offset, float crossFader) {
  131. current = Quaternion.Lerp(lastValue, Quaternion.Lerp(Quaternion.identity, offset, weight), crossFader);
  132. bone.rotation = current * bone.rotation;
  133. }
  134. // Remember the current offset value, so we can smoothly crossfade from it
  135. public void CrossFadeStart() {
  136. lastValue = current;
  137. }
  138. }
  139. [Tooltip("The angle to rotate the bone around it's rigidbody's world center of mass")]
  140. public AnimationCurve aroundCenterOfMass;
  141. [Tooltip("Linking this hit point to bone(s)")]
  142. public BoneLink[] boneLinks;
  143. private Rigidbody rigidbody;
  144. // Returns the length of this hit (last key in the AnimationCurves)
  145. protected override float GetLength() {
  146. return aroundCenterOfMass.keys.Length > 0? aroundCenterOfMass.keys[aroundCenterOfMass.length - 1].time: 0f;
  147. }
  148. // Remember the current offset values for each bone, so we can smoothly crossfade from it
  149. protected override void CrossFadeStart() {
  150. foreach (BoneLink b in boneLinks) b.CrossFadeStart();
  151. }
  152. // Calculate offset, apply to the bones
  153. protected override void OnApply(IKSolverFullBodyBiped solver, float weight) {
  154. if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
  155. if (rigidbody != null) {
  156. Vector3 comAxis = Vector3.Cross(force, point - rigidbody.worldCenterOfMass);
  157. float comValue = aroundCenterOfMass.Evaluate(timer) * weight;
  158. Quaternion offset = Quaternion.AngleAxis(comValue, comAxis);
  159. foreach (BoneLink b in boneLinks) b.Apply(solver, offset, crossFader);
  160. }
  161. }
  162. }
  163. [Tooltip("Hit points for the FBBIK effectors")]
  164. public HitPointEffector[] effectorHitPoints;
  165. [Tooltip(" Hit points for bones without an effector, such as the head")]
  166. public HitPointBone[] boneHitPoints;
  167. /// <summary>
  168. /// Returns true if any of the hits are being processed.
  169. /// </summary>
  170. public bool inProgress {
  171. get {
  172. foreach (HitPointEffector h in effectorHitPoints) {
  173. if (h.inProgress) return true;
  174. }
  175. foreach (HitPointBone h in boneHitPoints) {
  176. if (h.inProgress) return true;
  177. }
  178. return false;
  179. }
  180. }
  181. // Called by IKSolverFullBody before updating
  182. protected override void OnModifyOffset() {
  183. foreach (HitPointEffector e in effectorHitPoints) e.Apply(ik.solver, weight);
  184. foreach (HitPointBone b in boneHitPoints) b.Apply(ik.solver, weight);
  185. }
  186. // Hit one of the hit points (defined by hit.collider)
  187. public void Hit(Collider collider, Vector3 force, Vector3 point) {
  188. if (ik == null) {
  189. Debug.LogError("No IK assigned in HitReaction");
  190. return;
  191. }
  192. foreach (HitPointEffector e in effectorHitPoints) {
  193. if (e.collider == collider) e.Hit(force, point);
  194. }
  195. foreach (HitPointBone b in boneHitPoints) {
  196. if (b.collider == collider) b.Hit(force, point);
  197. }
  198. }
  199. }
  200. }