Recoil.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. using UnityEngine;
  2. using System.Collections;
  3. namespace RootMotion.FinalIK {
  4. /// <summary>
  5. /// Procedural recoil using FBBIK.
  6. /// </summary>
  7. public class Recoil : OffsetModifier {
  8. [System.Serializable]
  9. public class RecoilOffset {
  10. [Tooltip("Offset vector for the associated effector when doing recoil.")]
  11. public Vector3 offset;
  12. [Tooltip("When firing before the last recoil has faded, how much of the current recoil offset will be maintained?")]
  13. [Range(0f, 1f)] public float additivity = 1f;
  14. [Tooltip("Max additive recoil for automatic fire.")]
  15. public float maxAdditiveOffsetMag = 0.2f;
  16. // Linking this to an effector
  17. [System.Serializable]
  18. public class EffectorLink {
  19. [Tooltip("Type of the FBBIK effector to use")]
  20. public FullBodyBipedEffector effector;
  21. [Tooltip("Weight of using this effector")]
  22. public float weight;
  23. }
  24. [Tooltip("Linking this recoil offset to FBBIK effectors.")]
  25. public EffectorLink[] effectorLinks;
  26. private Vector3 additiveOffset;
  27. private Vector3 lastOffset;
  28. // Start recoil
  29. public void Start() {
  30. if (additivity <= 0f) return;
  31. additiveOffset = Vector3.ClampMagnitude(lastOffset * additivity, maxAdditiveOffsetMag);
  32. }
  33. // Apply offset to FBBIK effectors
  34. public void Apply(IKSolverFullBodyBiped solver, Quaternion rotation, float masterWeight, float length, float timeLeft) {
  35. additiveOffset = Vector3.Lerp(Vector3.zero, additiveOffset, timeLeft / length);
  36. lastOffset = (rotation * (offset * masterWeight)) + (rotation * additiveOffset);
  37. foreach (EffectorLink e in effectorLinks) {
  38. solver.GetEffector(e.effector).positionOffset += lastOffset * e.weight;
  39. }
  40. }
  41. }
  42. [System.Serializable]
  43. public enum Handedness {
  44. Right,
  45. Left
  46. }
  47. [Tooltip("Reference to the AimIK component. Optional, only used to getting the aiming direction.")]
  48. public AimIK aimIK;
  49. [Tooltip("Set this true if you are using IKExecutionOrder.cs or a custom script to force AimIK solve after FBBIK.")]
  50. public bool aimIKSolvedLast;
  51. [Tooltip("Which hand is holding the weapon?")]
  52. public Handedness handedness;
  53. [Tooltip("Check for 2-handed weapons.")]
  54. public bool twoHanded = true;
  55. [Tooltip("Weight curve for the recoil offsets. Recoil procedure is as long as this curve.")]
  56. public AnimationCurve recoilWeight;
  57. [Tooltip("How much is the magnitude randomized each time Recoil is called?")]
  58. public float magnitudeRandom = 0.1f;
  59. [Tooltip("How much is the rotation randomized each time Recoil is called?")]
  60. public Vector3 rotationRandom;
  61. [Tooltip("Rotating the primary hand bone for the recoil (in local space).")]
  62. public Vector3 handRotationOffset;
  63. [Tooltip("Time of blending in another recoil when doing automatic fire.")]
  64. public float blendTime;
  65. [Space(10)]
  66. [Tooltip("FBBIK effector position offsets for the recoil (in aiming direction space).")]
  67. public RecoilOffset[] offsets;
  68. [HideInInspector] public Quaternion rotationOffset = Quaternion.identity;
  69. private float magnitudeMlp = 1f;
  70. private float endTime = -1f;
  71. private Quaternion handRotation, secondaryHandRelativeRotation, randomRotation;
  72. private float length = 1f;
  73. private bool initiated;
  74. private float blendWeight;
  75. private float w;
  76. private Quaternion primaryHandRotation = Quaternion.identity;
  77. //private Quaternion secondaryHandRotation = Quaternion.identity;
  78. private bool handRotationsSet;
  79. private Vector3 aimIKAxis;
  80. /// <summary>
  81. /// Returns true if recoil has finished or has not been called at all.
  82. /// </summary>
  83. public bool isFinished {
  84. get {
  85. return Time.time > endTime;
  86. }
  87. }
  88. /// <summary>
  89. /// Sets the starting rotations for the hands for 1 frame. Use this if the final rotation of the hands will not be the same as before FBBIK solves.
  90. /// </summary>
  91. public void SetHandRotations(Quaternion leftHandRotation, Quaternion rightHandRotation) {
  92. if (handedness == Handedness.Left) {
  93. primaryHandRotation = leftHandRotation;
  94. //secondaryHandRotation = rightHandRotation;
  95. } else {
  96. primaryHandRotation = rightHandRotation;
  97. //secondaryHandRotation = leftHandRotation;
  98. }
  99. handRotationsSet = true;
  100. }
  101. /// <summary>
  102. /// Starts the recoil procedure.
  103. /// </summary>
  104. public void Fire(float magnitude) {
  105. float rnd = magnitude * UnityEngine.Random.value * magnitudeRandom;
  106. magnitudeMlp = magnitude + rnd;
  107. randomRotation = Quaternion.Euler(rotationRandom * UnityEngine.Random.value);
  108. foreach (RecoilOffset offset in offsets) {
  109. offset.Start();
  110. }
  111. if (Time.time < endTime) blendWeight = 0f;
  112. else blendWeight = 1f;
  113. Keyframe[] keys = recoilWeight.keys;
  114. length = keys[keys.Length - 1].time;
  115. endTime = Time.time + length;
  116. }
  117. protected override void OnModifyOffset() {
  118. if (aimIK != null) aimIKAxis = aimIK.solver.axis;
  119. if (Time.time >= endTime) {
  120. rotationOffset = Quaternion.identity;
  121. return;
  122. }
  123. if (!initiated && ik != null) {
  124. initiated = true;
  125. ik.solver.OnPostUpdate += AfterFBBIK;
  126. if (aimIK != null) aimIK.solver.OnPostUpdate += AfterAimIK;
  127. }
  128. blendTime = Mathf.Max(blendTime, 0f);
  129. if (blendTime > 0f) blendWeight = Mathf.Min(blendWeight + Time.deltaTime * (1f / blendTime), 1f);
  130. else blendWeight = 1f;
  131. // Current weight of offset
  132. float wTarget = recoilWeight.Evaluate(length - (endTime - Time.time)) * magnitudeMlp;
  133. w = Mathf.Lerp(w, wTarget, blendWeight);
  134. // Find the rotation space of the recoil
  135. Quaternion lookRotation = aimIK != null && aimIK.solver.transform != null && !aimIKSolvedLast? Quaternion.LookRotation(aimIK.solver.IKPosition - aimIK.solver.transform.position, ik.references.root.up): ik.references.root.rotation;
  136. lookRotation = randomRotation * lookRotation;
  137. // Apply FBBIK effector positionOffsets
  138. foreach (RecoilOffset offset in offsets) {
  139. offset.Apply(ik.solver, lookRotation, w, length, endTime - Time.time);
  140. }
  141. if (!handRotationsSet) {
  142. primaryHandRotation = primaryHand.rotation;
  143. //if (twoHanded) secondaryHandRotation = secondaryHand.rotation;
  144. }
  145. handRotationsSet = false;
  146. // Rotation offset of the primary hand
  147. rotationOffset = Quaternion.Lerp(Quaternion.identity, Quaternion.Euler(randomRotation * primaryHandRotation * handRotationOffset), w);
  148. handRotation = rotationOffset * primaryHandRotation;
  149. // Fix the secondary hand relative to the primary hand
  150. if (twoHanded) {
  151. Vector3 secondaryHandRelativePosition = Quaternion.Inverse(primaryHand.rotation) * (secondaryHand.position - primaryHand.position);
  152. secondaryHandRelativeRotation = Quaternion.Inverse(primaryHand.rotation) * secondaryHand.rotation;
  153. Vector3 primaryHandPosition = primaryHand.position + primaryHandEffector.positionOffset;
  154. Vector3 secondaryHandPosition = primaryHandPosition + handRotation * secondaryHandRelativePosition;
  155. secondaryHandEffector.positionOffset += secondaryHandPosition - (secondaryHand.position + secondaryHandEffector.positionOffset);
  156. }
  157. if (aimIK != null && aimIKSolvedLast) aimIK.solver.axis = Quaternion.Inverse(ik.references.root.rotation) * Quaternion.Inverse(rotationOffset) * aimIKAxis;
  158. }
  159. private void AfterFBBIK() {
  160. if (Time.time >= endTime) return;
  161. // Rotate the hand bones
  162. primaryHand.rotation = handRotation;
  163. if (twoHanded) secondaryHand.rotation = primaryHand.rotation * secondaryHandRelativeRotation;
  164. }
  165. private void AfterAimIK() {
  166. if (aimIKSolvedLast) aimIK.solver.axis = aimIKAxis;
  167. }
  168. // Shortcuts
  169. private IKEffector primaryHandEffector {
  170. get {
  171. if (handedness == Handedness.Right) return ik.solver.rightHandEffector;
  172. return ik.solver.leftHandEffector;
  173. }
  174. }
  175. private IKEffector secondaryHandEffector {
  176. get {
  177. if (handedness == Handedness.Right) return ik.solver.leftHandEffector;
  178. return ik.solver.rightHandEffector;
  179. }
  180. }
  181. private Transform primaryHand {
  182. get {
  183. return primaryHandEffector.bone;
  184. }
  185. }
  186. private Transform secondaryHand {
  187. get {
  188. return secondaryHandEffector.bone;
  189. }
  190. }
  191. protected override void OnDestroy() {
  192. base.OnDestroy();
  193. if (ik != null && initiated) {
  194. ik.solver.OnPostUpdate -= AfterFBBIK;
  195. if (aimIK != null) aimIK.solver.OnPostUpdate -= AfterAimIK;
  196. }
  197. }
  198. }
  199. }