FPSAiming.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using UnityEngine;
  2. using System.Collections;
  3. using RootMotion.FinalIK;
  4. namespace RootMotion.Demos {
  5. /// <summary>
  6. /// Basic full body FPS IK controller.
  7. ///
  8. /// If aimWeight is weighed in, the character will simply use AimIK to aim his gun towards the camera forward direction.
  9. /// If sightWeight is weighed in, the character will also use FBBIK to pose the gun to a predefined position relative to the camera so it stays fixed in view.
  10. /// That position was simply defined by making a copy of the gun (gunTarget), parenting it to the camera and positioning it so that the camera would look down it's sights.
  11. /// </summary>
  12. public class FPSAiming : MonoBehaviour {
  13. [Range(0f, 1f)] public float aimWeight = 1f; // The weight of aiming the gun towards camera forward
  14. [Range(0f, 1f)] public float sightWeight = 1f; // the weight of aiming down the sight (multiplied by aimWeight)
  15. [Range(0f, 180f)] public float maxAngle = 80f; // The maximum angular offset of the aiming direction from the character forward. Character will be rotated to comply.
  16. public Vector3 aimOffset; // Can be used to adjust the aiming angle
  17. public bool animatePhysics; // Is Animate Physiscs turned on for the character?
  18. public Transform gun; // The gun that the character is holding
  19. public Transform gunTarget; // The copy of the gun that has been parented to the camera
  20. public FullBodyBipedIK ik; // Reference to the FBBIK component
  21. public AimIK gunAim; // Reference to the AimIK component
  22. public AimIK headAim; // AimIK solver used for look-at when aimWeight < 1
  23. public CameraControllerFPS cam; // Reference to the FPS camera
  24. public Recoil recoil; // The recoil component (optional)
  25. [Range(0f, 1f)] public float cameraRecoilWeight = 0.5f; // How much of the recoil motion is added to the camera?
  26. private Vector3 gunTargetDefaultLocalPosition;
  27. private Vector3 gunTargetDefaultLocalRotation;
  28. private Vector3 camDefaultLocalPosition;
  29. private Vector3 camRelativeToGunTarget;
  30. private bool updateFrame;
  31. void Start() {
  32. // Remember some default local positions
  33. gunTargetDefaultLocalPosition = gunTarget.localPosition;
  34. gunTargetDefaultLocalRotation = gunTarget.localEulerAngles;
  35. camDefaultLocalPosition = cam.transform.localPosition;
  36. // Disable the camera and IK components so we can handle their execution order
  37. cam.enabled = false;
  38. gunAim.enabled = false;
  39. if (headAim != null) headAim.enabled = false;
  40. ik.enabled = false;
  41. if (recoil != null && ik.solver.iterations == 0) Debug.LogWarning("FPSAiming with Recoil needs FBBIK solver iteration count to be at least 1 to maintain accuracy.");
  42. }
  43. void FixedUpdate() {
  44. // Making sure this works with Animate Physics
  45. updateFrame = true;
  46. }
  47. void LateUpdate() {
  48. // Making sure this works with Animate Physics
  49. if (!animatePhysics) updateFrame = true;
  50. if (!updateFrame) return;
  51. updateFrame = false;
  52. // Put the camera back to it's default local position relative to the head
  53. cam.transform.localPosition = camDefaultLocalPosition;
  54. // Remember the camera's position relative to the gun target
  55. camRelativeToGunTarget = gunTarget.InverseTransformPoint(cam.transform.position);
  56. // Update the camera
  57. cam.LateUpdate();
  58. // Rotating the root of the character if it is past maxAngle from the camera forward
  59. RotateCharacter();
  60. Aiming();
  61. LookDownTheSight();
  62. }
  63. private void Aiming()
  64. {
  65. if (headAim == null && aimWeight <= 0f) return;
  66. // Remember the rotation of the camera because we need to reset it later so the IK would not interfere with the rotating of the camera
  67. Quaternion camRotation = cam.transform.rotation;
  68. // Aim head towards camera forward
  69. headAim.solver.IKPosition = cam.transform.position + cam.transform.forward * 10f;
  70. headAim.solver.IKPositionWeight = 1f - aimWeight;
  71. headAim.solver.Update();
  72. // Aim the gun towards camera forward
  73. gunAim.solver.IKPosition = cam.transform.position + cam.transform.forward * 10f + cam.transform.rotation * aimOffset;
  74. gunAim.solver.IKPositionWeight = aimWeight;
  75. gunAim.solver.Update();
  76. cam.transform.rotation = camRotation;
  77. }
  78. private void LookDownTheSight() {
  79. float sW = aimWeight * sightWeight;
  80. //if (sW <= 0f && recoil == null) return;
  81. // Interpolate the gunTarget from the current animated position of the gun to the position fixed to the camera
  82. gunTarget.position = Vector3.Lerp(gun.position, gunTarget.parent.TransformPoint(gunTargetDefaultLocalPosition), sW);
  83. gunTarget.rotation = Quaternion.Lerp(gun.rotation, gunTarget.parent.rotation * Quaternion.Euler(gunTargetDefaultLocalRotation), sW);
  84. // Get the current positions of the hands relative to the gun
  85. Vector3 leftHandRelativePosition = gun.InverseTransformPoint(ik.solver.leftHandEffector.bone.position);
  86. Vector3 rightHandRelativePosition = gun.InverseTransformPoint(ik.solver.rightHandEffector.bone.position);
  87. // Get the current rotations of the hands relative to the gun
  88. Quaternion leftHandRelativeRotation = Quaternion.Inverse(gun.rotation) * ik.solver.leftHandEffector.bone.rotation;
  89. Quaternion rightHandRelativeRotation = Quaternion.Inverse(gun.rotation) * ik.solver.rightHandEffector.bone.rotation;
  90. //float handWeight = aimWeight > 0 && sightWeight > 0? aimWeight * sightWeight: 0f;
  91. float handWeight = 1f;//aimWeight * sightWeight;
  92. ik.solver.leftHandEffector.positionOffset += (gunTarget.TransformPoint(leftHandRelativePosition) - (ik.solver.leftHandEffector.bone.position + ik.solver.leftHandEffector.positionOffset)) * handWeight;
  93. ik.solver.rightHandEffector.positionOffset += (gunTarget.TransformPoint(rightHandRelativePosition) - (ik.solver.rightHandEffector.bone.position + ik.solver.rightHandEffector.positionOffset)) * handWeight;
  94. // Make sure the head does not rotate
  95. ik.solver.headMapping.maintainRotationWeight = 1f;
  96. if (recoil != null) recoil.SetHandRotations(gunTarget.rotation * leftHandRelativeRotation, gunTarget.rotation * rightHandRelativeRotation);
  97. // Update FBBIK
  98. ik.solver.Update();
  99. // Rotate the hand bones relative to the gun target the same way they are rotated relative to the gun
  100. if (recoil != null) {
  101. ik.references.leftHand.rotation = recoil.rotationOffset * (gunTarget.rotation * leftHandRelativeRotation);
  102. ik.references.rightHand.rotation = recoil.rotationOffset * (gunTarget.rotation * rightHandRelativeRotation);
  103. } else {
  104. ik.references.leftHand.rotation = gunTarget.rotation * leftHandRelativeRotation;
  105. ik.references.rightHand.rotation = gunTarget.rotation * rightHandRelativeRotation;
  106. }
  107. // Position the camera to where it was before FBBIK relative to the gun
  108. cam.transform.position = Vector3.Lerp(cam.transform.position, Vector3.Lerp(gunTarget.TransformPoint(camRelativeToGunTarget), gun.transform.TransformPoint(camRelativeToGunTarget), cameraRecoilWeight), sW);
  109. }
  110. // Rotating the root of the character if it is past maxAngle from the camera forward
  111. private void RotateCharacter() {
  112. if (maxAngle >= 180f) return;
  113. // If no angular difference is allowed, just rotate the character to the flattened camera forward
  114. if (maxAngle <= 0f) {
  115. transform.rotation = Quaternion.LookRotation(new Vector3(cam.transform.forward.x, 0f, cam.transform.forward.z));
  116. return;
  117. }
  118. // Get camera forward in the character's rotation space
  119. Vector3 camRelative = transform.InverseTransformDirection(cam.transform.forward);
  120. // Get the angle of the camera forward relative to the character forward
  121. float angle = Mathf.Atan2(camRelative.x, camRelative.z) * Mathf.Rad2Deg;
  122. // Making sure the angle does not exceed maxangle
  123. if (Mathf.Abs(angle) > Mathf.Abs(maxAngle)) {
  124. float a = angle - maxAngle;
  125. if (angle < 0f) a = angle + maxAngle;
  126. transform.rotation = Quaternion.AngleAxis(a, transform.up) * transform.rotation;
  127. }
  128. }
  129. }
  130. }