HitReactionVRIK.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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 HitReactionVRIK : OffsetModifierVRIK {
  8. public AnimationCurve[] offsetCurves;
  9. /// <summary>
  10. /// Hit point definition
  11. /// </summary>
  12. [System.Serializable]
  13. public abstract class Offset {
  14. [Tooltip("Just for visual clarity, not used at all")]
  15. public string name;
  16. [Tooltip("Linking this hit point to a collider")]
  17. public Collider collider;
  18. [Tooltip("Only used if this hit point gets hit when already processing another hit")]
  19. [SerializeField] float crossFadeTime = 0.1f;
  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, AnimationCurve[] curves, Vector3 point) {
  29. if (length == 0f) length = GetLength(curves);
  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(VRIK ik, AnimationCurve[] curves, 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(ik, curves, weight);
  58. }
  59. protected abstract float GetLength(AnimationCurve[] curves);
  60. protected abstract void CrossFadeStart();
  61. protected abstract void OnApply(VRIK ik, AnimationCurve[] curves, float weight);
  62. }
  63. /// <summary>
  64. /// Hit Point for FBBIK effectors
  65. /// </summary>
  66. [System.Serializable]
  67. public class PositionOffset: Offset {
  68. /// <summary>
  69. /// Linking a FBBIK effector to this effector hit point
  70. /// </summary>
  71. [System.Serializable]
  72. public class PositionOffsetLink {
  73. [Tooltip("The FBBIK effector type")]
  74. public IKSolverVR.PositionOffset positionOffset;
  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(VRIK ik, Vector3 offset, float crossFader) {
  81. current = Vector3.Lerp(lastValue, offset * weight, crossFader);
  82. ik.solver.AddPositionOffset(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 int forceDirCurveIndex;
  91. [Tooltip("Offset magnitude in the direction of character.up")]
  92. public int upDirCurveIndex = 1;
  93. [Tooltip("Linking this offset to the VRIK position offsets")]
  94. public PositionOffsetLink[] offsetLinks;
  95. // Returns the length of this hit (last key in the AnimationCurves)
  96. protected override float GetLength(AnimationCurve[] curves) {
  97. float time1 = curves[forceDirCurveIndex].keys.Length > 0? curves[forceDirCurveIndex].keys[curves[forceDirCurveIndex].length - 1].time: 0f;
  98. float time2 = curves[upDirCurveIndex].keys.Length > 0? curves[upDirCurveIndex].keys[curves[upDirCurveIndex].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 (PositionOffsetLink l in offsetLinks) l.CrossFadeStart();
  104. }
  105. // Calculate offset, apply to FBBIK effectors
  106. protected override void OnApply(VRIK ik, AnimationCurve[] curves, float weight) {
  107. Vector3 up = ik.transform.up * force.magnitude;
  108. Vector3 offset = (curves[forceDirCurveIndex].Evaluate(timer) * force) + (curves[upDirCurveIndex].Evaluate(timer) * up);
  109. offset *= weight;
  110. foreach (PositionOffsetLink l in offsetLinks) l.Apply(ik, 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 RotationOffset: Offset {
  118. /// <summary>
  119. /// Linking a bone Transform to this bone hit point
  120. /// </summary>
  121. [System.Serializable]
  122. public class RotationOffsetLink {
  123. [Tooltip("Reference to the bone that this hit point rotates")]
  124. public IKSolverVR.RotationOffset rotationOffset;
  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(VRIK ik, Quaternion offset, float crossFader) {
  131. current = Quaternion.Lerp(lastValue, Quaternion.Lerp(Quaternion.identity, offset, weight), crossFader);
  132. ik.solver.AddRotationOffset(rotationOffset, current);
  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 int curveIndex;
  141. [Tooltip("Linking this hit point to bone(s)")]
  142. public RotationOffsetLink[] offsetLinks;
  143. private Rigidbody rigidbody;
  144. // Returns the length of this hit (last key in the AnimationCurves)
  145. protected override float GetLength(AnimationCurve[] curves) {
  146. return curves[curveIndex].keys.Length > 0? curves[curveIndex].keys[ curves[curveIndex].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 (RotationOffsetLink l in offsetLinks) l.CrossFadeStart();
  151. }
  152. // Calculate offset, apply to the bones
  153. protected override void OnApply(VRIK ik, AnimationCurve[] curves, float weight) {
  154. if (collider == null) {
  155. Debug.LogError ("No collider assigned for a HitPointBone in the HitReaction component.");
  156. return;
  157. }
  158. if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
  159. if (rigidbody != null) {
  160. Vector3 comAxis = Vector3.Cross(force, point - rigidbody.worldCenterOfMass);
  161. float comValue = curves[curveIndex].Evaluate(timer) * weight;
  162. Quaternion offset = Quaternion.AngleAxis(comValue, comAxis);
  163. foreach (RotationOffsetLink l in offsetLinks) l.Apply(ik, offset, crossFader);
  164. }
  165. }
  166. }
  167. [Tooltip("Hit points for the FBBIK effectors")]
  168. public PositionOffset[] positionOffsets;
  169. [Tooltip(" Hit points for bones without an effector, such as the head")]
  170. public RotationOffset[] rotationOffsets;
  171. // Called by IKSolverFullBody before updating
  172. protected override void OnModifyOffset() {
  173. foreach (PositionOffset p in positionOffsets) p.Apply(ik, offsetCurves, weight);
  174. foreach (RotationOffset r in rotationOffsets) r.Apply(ik, offsetCurves, weight);
  175. }
  176. // Hit one of the hit points (defined by hit.collider)
  177. public void Hit(Collider collider, Vector3 force, Vector3 point) {
  178. if (ik == null) {
  179. Debug.LogError("No IK assigned in HitReaction");
  180. return;
  181. }
  182. foreach (PositionOffset p in positionOffsets) {
  183. if (p.collider == collider) p.Hit(force, offsetCurves, point);
  184. }
  185. foreach (RotationOffset r in rotationOffsets) {
  186. if (r.collider == collider) r.Hit(force, offsetCurves, point);
  187. }
  188. }
  189. }
  190. }