Cinemachine3rdPersonAim.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #endif
  4. using UnityEngine;
  5. namespace Cinemachine
  6. {
  7. #if CINEMACHINE_PHYSICS
  8. /// <summary>
  9. /// An add-on module for Cinemachine Virtual Camera that forces the LookAt
  10. /// point to the center of the screen, based on the Follow target's orientation,
  11. /// cancelling noise and other corrections.
  12. /// This is useful for third-person style aim cameras that want a dead-accurate
  13. /// aim at all times, even in the presence of positional or rotational noise.
  14. /// </summary>
  15. [AddComponentMenu("")] // Hide in menu
  16. [ExecuteAlways]
  17. [SaveDuringPlay]
  18. [DisallowMultipleComponent]
  19. public class Cinemachine3rdPersonAim : CinemachineExtension
  20. {
  21. /// <summary>Objects on these layers will be detected.</summary>
  22. [Header("Aim Target Detection")]
  23. [Tooltip("Objects on these layers will be detected")]
  24. public LayerMask AimCollisionFilter;
  25. /// <summary>Objects with this tag will be ignored.
  26. /// It is a good idea to set this field to the target's tag.</summary>
  27. [TagField]
  28. [Tooltip("Objects with this tag will be ignored. "
  29. + "It is a good idea to set this field to the target's tag")]
  30. public string IgnoreTag = string.Empty;
  31. /// <summary>How far to project the object detection ray.</summary>
  32. [Tooltip("How far to project the object detection ray")]
  33. public float AimDistance;
  34. /// <summary>This 2D object will be positioned in the game view over the raycast hit point,
  35. /// if any, or will remain in the center of the screen if no hit point is
  36. /// detected. May be null, in which case no on-screen indicator will appear.</summary>
  37. [Tooltip("This 2D object will be positioned in the game view over the raycast hit point, if any, "
  38. + "or will remain in the center of the screen if no hit point is detected. "
  39. + "May be null, in which case no on-screen indicator will appear")]
  40. public RectTransform AimTargetReticle;
  41. /// <summary>World space position of where the player would hit if a projectile were to
  42. /// be fired from the player origin. This may be different
  43. /// from state.ReferenceLookAt due to camera offset from player origin.</summary>
  44. public Vector3 AimTarget { get; private set; }
  45. private void OnValidate()
  46. {
  47. AimDistance = Mathf.Max(1, AimDistance);
  48. }
  49. private void Reset()
  50. {
  51. AimCollisionFilter = 1;
  52. IgnoreTag = string.Empty;
  53. AimDistance = 200.0f;
  54. AimTargetReticle = null;
  55. }
  56. /// <summary>Notification that this virtual camera is going live.</summary>
  57. /// <param name="fromCam">The camera being deactivated. May be null.</param>
  58. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  59. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  60. /// <returns>True to request a vcam update of internal state</returns>
  61. public override bool OnTransitionFromCamera(
  62. ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime)
  63. {
  64. CinemachineCore.CameraUpdatedEvent.RemoveListener(DrawReticle);
  65. CinemachineCore.CameraUpdatedEvent.AddListener(DrawReticle);
  66. return false;
  67. }
  68. void DrawReticle(CinemachineBrain brain)
  69. {
  70. if (!brain.IsLive(VirtualCamera) || brain.OutputCamera == null)
  71. CinemachineCore.CameraUpdatedEvent.RemoveListener(DrawReticle);
  72. else if (AimTargetReticle != null)
  73. AimTargetReticle.position = brain.OutputCamera.WorldToScreenPoint(AimTarget);
  74. }
  75. Vector3 ComputeLookAtPoint(Vector3 camPos, Transform player)
  76. {
  77. // We don't want to hit targets behind the player
  78. var aimDistance = AimDistance;
  79. var playerOrientation = player.rotation;
  80. var fwd = playerOrientation * Vector3.forward;
  81. var playerPos = Quaternion.Inverse(playerOrientation) * (player.position - camPos);
  82. if (playerPos.z > 0)
  83. {
  84. camPos += fwd * playerPos.z;
  85. aimDistance -= playerPos.z;
  86. }
  87. aimDistance = Mathf.Max(1, aimDistance);
  88. bool hasHit = RuntimeUtility.RaycastIgnoreTag(new Ray(camPos, fwd),
  89. out RaycastHit hitInfo, aimDistance, AimCollisionFilter, IgnoreTag);
  90. return hasHit ? hitInfo.point : camPos + fwd * aimDistance;
  91. }
  92. Vector3 ComputeAimTarget(Vector3 cameraLookAt, Transform player)
  93. {
  94. // Adjust for actual player aim target (may be different due to offset)
  95. var playerPos = player.position;
  96. var dir = cameraLookAt - playerPos;
  97. if (RuntimeUtility.RaycastIgnoreTag(new Ray(playerPos, dir),
  98. out RaycastHit hitInfo, dir.magnitude, AimCollisionFilter, IgnoreTag))
  99. return hitInfo.point;
  100. return cameraLookAt;
  101. }
  102. /// <summary>
  103. /// Sets the ReferenceLookAt to be the result of a raycast in the direction of camera forward.
  104. /// If an object is hit, point is placed there, else it is placed at AimDistance.
  105. /// </summary>
  106. /// <param name="vcam">The virtual camera being processed</param>
  107. /// <param name="stage">The current pipeline stage</param>
  108. /// <param name="state">The current virtual camera state</param>
  109. /// <param name="deltaTime">The current applicable deltaTime</param>
  110. protected override void PostPipelineStageCallback(
  111. CinemachineVirtualCameraBase vcam,
  112. CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
  113. {
  114. if (stage == CinemachineCore.Stage.Body)
  115. {
  116. // Raycast to establish what we're actually aiming at
  117. var player = vcam.Follow;
  118. if (player != null)
  119. {
  120. state.ReferenceLookAt = ComputeLookAtPoint(state.CorrectedPosition, player);
  121. AimTarget = ComputeAimTarget(state.ReferenceLookAt, player);
  122. }
  123. }
  124. if (stage == CinemachineCore.Stage.Finalize)
  125. {
  126. // Stabilize the LookAt point in the center of the screen
  127. var dir = state.ReferenceLookAt - state.FinalPosition;
  128. if (dir.sqrMagnitude > 0.01f)
  129. {
  130. state.RawOrientation = Quaternion.LookRotation(dir, state.ReferenceUp);
  131. state.OrientationCorrection = Quaternion.identity;
  132. }
  133. }
  134. }
  135. }
  136. #endif
  137. }