using System; using System.Collections; using UnityEngine; using UHFPS.Input; using UHFPS.Tools; namespace UHFPS.Runtime { public class LookController : PlayerComponent { public bool LockCursor; public bool SmoothLook; public float SensitivityX = 2f; public float SensitivityY = 2f; public float MultiplierX = 1f; public float MultiplierY = 1f; public float SmoothTime = 5f; public float SmoothMultiplier = 2f; public MinMax HorizontalLimits = new(-360, 360); public MinMax VerticalLimits = new(-80, 90); public Vector2 LookOffset; public Vector2 LookRotation; private bool blockLook; private MinMax horizontalLimitsOrig; private MinMax verticalLimitsOrig; private Vector2 targetLook; private Vector2 startingLook; private bool customLerp; public Vector2 DeltaInput { get; set; } public Quaternion RotationX { get; private set; } public Quaternion RotationY { get; private set; } public Quaternion RotationFinal { get; private set; } public bool LookLocked { get => blockLook; set => blockLook = value; } void Start() { verticalLimitsOrig = VerticalLimits; horizontalLimitsOrig = HorizontalLimits; if (LockCursor) GameTools.ShowCursor(true, false); OptionsManager.ObserveOption("sensitivity", (obj) => { SensitivityX = (float)obj; SensitivityY = (float)obj; }); OptionsManager.ObserveOption("smoothing", (obj) => SmoothLook = (bool)obj); OptionsManager.ObserveOption("smoothing_speed", (obj) => SmoothTime = (float)obj); } void Update() { if (Cursor.lockState != CursorLockMode.None && !blockLook && isEnabled) { DeltaInput = InputManager.ReadInput(Controls.LOOK); } else { DeltaInput = Vector2.zero; } LookRotation.x += DeltaInput.x * (SensitivityX * MultiplierX) / 30 * MainCamera.fieldOfView + LookOffset.x; LookRotation.y += DeltaInput.y * (SensitivityY * MultiplierY) / 30 * MainCamera.fieldOfView + LookOffset.y; LookRotation.x = ClampAngle(LookRotation.x, HorizontalLimits.RealMin, HorizontalLimits.RealMax); LookRotation.y = ClampAngle(LookRotation.y, VerticalLimits.RealMin, VerticalLimits.RealMax); RotationX = Quaternion.AngleAxis(LookRotation.x, Vector3.up); RotationY = Quaternion.AngleAxis(LookRotation.y, Vector3.left); RotationFinal = RotationX * RotationY; transform.localRotation = SmoothLook ? Quaternion.Slerp(transform.localRotation, RotationFinal, SmoothTime * SmoothMultiplier * Time.deltaTime) : RotationFinal; LookOffset.y = 0F; LookOffset.x = 0F; } /// /// Lerp look rotation to a specific target rotation. /// public void LerpRotation(Vector2 target, float duration = 0.5f) { target.x = ClampAngle(target.x); target.y = ClampAngle(target.y); float xDiff = FixDiff(target.x - LookRotation.x); float yDiff = FixDiff(target.y - LookRotation.y); StartCoroutine(DoLerpRotation(new Vector2(xDiff, yDiff), null, duration)); } /// /// Lerp look rotation to a specific target rotation. /// public void LerpRotation(Vector2 target, Action onLerpComplete, float duration = 0.5f) { target.x = ClampAngle(target.x); target.y = ClampAngle(target.y); float xDiff = FixDiff(target.x - LookRotation.x); float yDiff = FixDiff(target.y - LookRotation.y); StartCoroutine(DoLerpRotation(new Vector2(xDiff, yDiff), onLerpComplete, duration)); } /// /// Lerp look rotation to a specific target transform. /// public void LerpRotation(Transform target, float duration = 0.5f, bool keepLookLocked = false) { Vector3 directionToTarget = target.position - transform.position; Quaternion rotationToTarget = Quaternion.LookRotation(directionToTarget); Vector3 eulerRotation = rotationToTarget.eulerAngles; Vector2 targetRotation = new Vector2(eulerRotation.y, eulerRotation.x); // Clamp the target rotation angles. targetRotation.x = ClampAngle(targetRotation.x); targetRotation.y = ClampAngle(-targetRotation.y); // Calculate the differences in each axis. float xDiff = FixDiff(targetRotation.x - LookRotation.x); float yDiff = FixDiff(targetRotation.y - LookRotation.y); // Start the lerp process. StartCoroutine(DoLerpRotation(new Vector2(xDiff, yDiff), null, duration, keepLookLocked)); } /// /// Lerp look rotation to a specific target transform. /// public void LerpRotation(Transform target, Action onLerpComplete, float duration = 0.5f, bool keepLookLocked = false) { Vector3 directionToTarget = target.position - transform.position; Quaternion rotationToTarget = Quaternion.LookRotation(directionToTarget); Vector3 eulerRotation = rotationToTarget.eulerAngles; Vector2 targetRotation = new Vector2(eulerRotation.y, eulerRotation.x); // Clamp the target rotation angles. targetRotation.x = ClampAngle(targetRotation.x); targetRotation.y = ClampAngle(targetRotation.y); // Calculate the differences in each axis. float xDiff = FixDiff(targetRotation.x - LookRotation.x); float yDiff = FixDiff(targetRotation.y - LookRotation.y); // Start the lerp process. StartCoroutine(DoLerpRotation(new Vector2(xDiff, yDiff), onLerpComplete, duration, keepLookLocked)); } /// /// Lerp the look rotation and clamp the look rotation within limits relative to the rotation. /// /// Relative target rotation. /// Vertical Limits [Up, Down] /// Horizontal Limits [Left, Right] public void LerpClampRotation(Vector3 relative, MinMax vLimits, MinMax hLimits, float duration = 0.5f) { float toAngle = ClampAngle(relative.y); float remainder = FixDiff(toAngle - LookRotation.x); float targetAngle = LookRotation.x + remainder; float min = targetAngle - Mathf.Abs(hLimits.RealMin); float max = targetAngle + Mathf.Abs(hLimits.RealMax); if (min < -360) { min += 360; max += 360; targetAngle += 360; } else if (max > 360) { min -= 360; max -= 360; targetAngle -= 360; } hLimits = new MinMax(min, max); StartCoroutine(DoLerpClampRotation(targetAngle, vLimits, hLimits, duration)); } /// /// Lerp the look rotation manually. This function should only be used in the Update() function. /// public void CustomLerp(Vector2 target, float t) { if (!customLerp) { targetLook.x = ClampAngle(target.x); targetLook.y = ClampAngle(target.y); startingLook = LookRotation; customLerp = true; blockLook = true; } if ((t = Mathf.Clamp01(t)) < 1) { LookRotation.x = Mathf.LerpAngle(startingLook.x, targetLook.x, t); LookRotation.y = Mathf.LerpAngle(startingLook.y, targetLook.y, t); } } /// /// Reset lerp parameters. /// public void ResetCustomLerp() { StopAllCoroutines(); targetLook = Vector2.zero; startingLook = Vector2.zero; customLerp = false; blockLook = false; } /// /// Set look rotation limits. /// /// Relative target rotation. /// Vertical Limits [Up, Down] /// Horizontal Limits [Left, Right] public void SetLookLimits(Vector3 relative, MinMax vLimits, MinMax hLimits) { if (hLimits.HasValue) { float toAngle = ClampAngle(relative.y); float remainder = FixDiff(toAngle - LookRotation.x); float targetAngle = LookRotation.x + remainder; float min = targetAngle - Mathf.Abs(hLimits.RealMin); float max = targetAngle + Mathf.Abs(hLimits.RealMax); if (min < -360) { min += 360; max += 360; } else if (max > 360) { min -= 360; max -= 360; } if (Mathf.Abs(targetAngle - LookRotation.x) > 180) { if (LookRotation.x > 0) LookRotation.x -= 360; else if (LookRotation.x < 0) LookRotation.x += 360; } hLimits = new MinMax(min, max); HorizontalLimits = hLimits; } VerticalLimits = vLimits; } /// /// Set vertical look rotation limits. /// /// Vertical Limits [Up, Down] public void SetVerticalLimits(MinMax vLimits) { VerticalLimits = vLimits; } /// /// Set horizontal look rotation limits. /// /// Relative target rotation. /// Horizontal Limits [Left, Right] public void SetHorizontalLimits(Vector3 relative, MinMax hLimits) { float toAngle = ClampAngle(relative.y); float remainder = FixDiff(toAngle - LookRotation.x); float targetAngle = LookRotation.x + remainder; float min = targetAngle - Mathf.Abs(hLimits.RealMin); float max = targetAngle + Mathf.Abs(hLimits.RealMax); if (min < -360) { min += 360; max += 360; } else if (max > 360) { min -= 360; max -= 360; } if (Mathf.Abs(targetAngle - LookRotation.x) > 180) { if (LookRotation.x > 0) LookRotation.x -= 360; else if (LookRotation.x < 0) LookRotation.x += 360; } hLimits = new MinMax(min, max); HorizontalLimits = hLimits; } /// /// Reset look rotation to default limits. /// public void ResetLookLimits() { StopAllCoroutines(); HorizontalLimits = horizontalLimitsOrig; VerticalLimits = verticalLimitsOrig; } /// /// Apply look rotation using a euler angles vector. /// /// Good to use when you want to set the look rotation from a custom camera. public void ApplyEulerLook(Vector2 eulerAngles) { // Clamp the target rotation angles. eulerAngles.x = ClampAngle(eulerAngles.x); eulerAngles.y = ClampAngle(eulerAngles.y); // Calculate the differences in each axis. float xDiff = FixDiff(eulerAngles.x - LookRotation.x); float yDiff = FixDiff(eulerAngles.y - LookRotation.y); LookRotation = new(LookRotation.x + xDiff, LookRotation.y + yDiff); } private IEnumerator DoLerpRotation(Vector2 target, Action onLerpComplete, float duration, bool keepLookLocked = false) { blockLook = true; target = new Vector2(LookRotation.x + target.x, LookRotation.y + target.y); Vector2 current = LookRotation; float elapsedTime = 0; while (elapsedTime < duration) { elapsedTime += Time.deltaTime; float t = GameTools.SmootherStep(0f, 1f, elapsedTime / duration); LookRotation.x = Mathf.LerpAngle(current.x, target.x, t); LookRotation.y = Mathf.LerpAngle(current.y, target.y, t); yield return null; } LookRotation = target; onLerpComplete?.Invoke(); blockLook = keepLookLocked; } private IEnumerator DoLerpClampRotation(float newX, Vector2 vLimit, Vector2 hLimit, float duration, bool keepLookLocked = false) { blockLook = true; float newY = LookRotation.y < vLimit.x ? vLimit.x : LookRotation.y > vLimit.y ? vLimit.y : LookRotation.y; Vector2 target = new Vector2(newX, newY); Vector2 current = LookRotation; float elapsedTime = 0; while (elapsedTime < duration) { elapsedTime += Time.deltaTime; float t = GameTools.SmootherStep(0f, 1f, elapsedTime / duration); LookRotation.x = Mathf.LerpAngle(current.x, target.x, t); LookRotation.y = Mathf.LerpAngle(current.y, target.y, t); yield return null; } LookRotation = target; HorizontalLimits = hLimit; VerticalLimits = vLimit; blockLook = keepLookLocked; } private float ClampAngle(float angle, float min, float max) { float newAngle = angle.FixAngle(); return Mathf.Clamp(newAngle, min, max); } private float ClampAngle(float angle) { angle %= 360f; if (angle < 0f) angle += 360f; return angle; } private float FixDiff(float angleDiff) { if (angleDiff > 180f) { angleDiff -= 360f; } else if (angleDiff < -180f) { angleDiff += 360f; } return angleDiff; } } }