#if !UNITY_2019_3_OR_NEWER #define CINEMACHINE_PHYSICS #endif using UnityEngine; namespace Cinemachine { #if CINEMACHINE_PHYSICS /// /// An add-on module for Cinemachine Virtual Camera that forces the LookAt /// point to the center of the screen, based on the Follow target's orientation, /// cancelling noise and other corrections. /// This is useful for third-person style aim cameras that want a dead-accurate /// aim at all times, even in the presence of positional or rotational noise. /// [AddComponentMenu("")] // Hide in menu [ExecuteAlways] [SaveDuringPlay] [DisallowMultipleComponent] public class Cinemachine3rdPersonAim : CinemachineExtension { /// Objects on these layers will be detected. [Header("Aim Target Detection")] [Tooltip("Objects on these layers will be detected")] public LayerMask AimCollisionFilter; /// Objects with this tag will be ignored. /// It is a good idea to set this field to the target's tag. [TagField] [Tooltip("Objects with this tag will be ignored. " + "It is a good idea to set this field to the target's tag")] public string IgnoreTag = string.Empty; /// How far to project the object detection ray. [Tooltip("How far to project the object detection ray")] public float AimDistance; /// This 2D object will be positioned in the game view over the raycast hit point, /// if any, or will remain in the center of the screen if no hit point is /// detected. May be null, in which case no on-screen indicator will appear. [Tooltip("This 2D object will be positioned in the game view over the raycast hit point, if any, " + "or will remain in the center of the screen if no hit point is detected. " + "May be null, in which case no on-screen indicator will appear")] public RectTransform AimTargetReticle; /// World space position of where the player would hit if a projectile were to /// be fired from the player origin. This may be different /// from state.ReferenceLookAt due to camera offset from player origin. private Vector3 AimTarget { get; set; } private void OnValidate() { AimDistance = Mathf.Max(1, AimDistance); } private void Reset() { AimCollisionFilter = 1; IgnoreTag = string.Empty; AimDistance = 200.0f; AimTargetReticle = null; } /// Notification that this virtual camera is going live. /// The camera being deactivated. May be null. /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than or equal to 0) /// True to request a vcam update of internal state public override bool OnTransitionFromCamera( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { CinemachineCore.CameraUpdatedEvent.RemoveListener(DrawReticle); CinemachineCore.CameraUpdatedEvent.AddListener(DrawReticle); return false; } void DrawReticle(CinemachineBrain brain) { if (!brain.IsLive(VirtualCamera) || brain.OutputCamera == null) CinemachineCore.CameraUpdatedEvent.RemoveListener(DrawReticle); else if (AimTargetReticle != null) AimTargetReticle.position = brain.OutputCamera.WorldToScreenPoint(AimTarget); } Vector3 ComputeLookAtPoint(Vector3 camPos, Transform player) { // We don't want to hit targets behind the player var aimDistance = AimDistance; var playerOrientation = player.rotation; var fwd = playerOrientation * Vector3.forward; var playerPos = Quaternion.Inverse(playerOrientation) * (player.position - camPos); if (playerPos.z > 0) { camPos += fwd * playerPos.z; aimDistance -= playerPos.z; } aimDistance = Mathf.Max(1, aimDistance); bool hasHit = RuntimeUtility.RaycastIgnoreTag(new Ray(camPos, fwd), out RaycastHit hitInfo, aimDistance, AimCollisionFilter, IgnoreTag); return hasHit ? hitInfo.point : camPos + fwd * aimDistance; } Vector3 ComputeAimTarget(Vector3 cameraLookAt, Transform player) { // Adjust for actual player aim target (may be different due to offset) var playerPos = player.position; var dir = cameraLookAt - playerPos; if (RuntimeUtility.RaycastIgnoreTag(new Ray(playerPos, dir), out RaycastHit hitInfo, dir.magnitude, AimCollisionFilter, IgnoreTag)) return hitInfo.point; return cameraLookAt; } /// /// Sets the ReferenceLookAt to be the result of a raycast in the direction of camera forward. /// If an object is hit, point is placed there, else it is placed at AimDistance. /// /// The virtual camera being processed /// The current pipeline stage /// The current virtual camera state /// The current applicable deltaTime protected override void PostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime) { if (stage == CinemachineCore.Stage.Body) { // Raycast to establish what we're actually aiming at var player = vcam.Follow; if (player != null) { state.ReferenceLookAt = ComputeLookAtPoint(state.CorrectedPosition, player); AimTarget = ComputeAimTarget(state.ReferenceLookAt, player); } } if (stage == CinemachineCore.Stage.Finalize) { // Stabilize the LookAt point in the center of the screen var dir = state.ReferenceLookAt - state.FinalPosition; if (dir.sqrMagnitude > 0.01f) { state.RawOrientation = Quaternion.LookRotation(dir, state.ReferenceUp); state.OrientationCorrection = Quaternion.identity; } } } } #endif }