using UnityEngine; using UnityEngine.InputSystem; using Unheimlich.Core; using Unheimlich.Features.Player.Data; using Unheimlich.Features.Dialogue; using InputManager = Unheimlich.Core.Input.InputManager; using OctoCore.Components; namespace Unheimlich.Features.Player { /// /// Handles camera effects: aim assist, shake, and FOV kick on sprint. /// public class CameraEffects : MonoBehaviour { [Header("Settings")] public CameraEffectsSettings Settings; private float _shakeTimer = 0f; private float _shakeInterval; private Vector3 _shakeTarget; private Vector3 _shakeOriginalPos; private float _shakeSmoothness; private Vector3 _pendingAimAssistTarget; private bool _hasAimAssistTarget; // FOV kick private float _baseFov = -1f; private float _currentFovOffset = 0f; public bool IsAimLocked { get; private set; } public bool HasActiveLookInput { get; private set; } public bool IsUsingGamepad { get; private set; } private void Start() { if (Settings != null) _shakeInterval = 1f / Settings.shakeFrequency; if (PlayerController.Instance?.PlayerCamera != null) _baseFov = PlayerController.Instance.PlayerCamera.fieldOfView; } private void Update() { DetectInputDevice(); UpdateFovKick(); } private void LateUpdate() { if (_hasAimAssistTarget) ApplyAimAssistInternal(_pendingAimAssistTarget); } // ── FOV Kick ───────────────────────────────────────────────────────────── private void UpdateFovKick() { if (Settings == null || PlayerController.Instance?.PlayerCamera == null) return; // Capture base FOV once (handles cases where FOV is changed externally before Start). if (_baseFov < 0f) _baseFov = PlayerController.Instance.PlayerCamera.fieldOfView; float targetOffset = PlayerController.Instance.IsRunning ? Settings.fovKickAmount : 0f; _currentFovOffset = Mathf.Lerp(_currentFovOffset, targetOffset, Time.deltaTime * Settings.fovKickSpeed); PlayerController.Instance.PlayerCamera.fieldOfView = _baseFov + _currentFovOffset; } /// /// Resets the base FOV reference. Call this if the camera FOV is changed intentionally at runtime. /// public void ResetBaseFov() { if (PlayerController.Instance?.PlayerCamera != null) _baseFov = PlayerController.Instance.PlayerCamera.fieldOfView; } // ── Input device detection ──────────────────────────────────────────────── private void DetectInputDevice() { Vector2 lookInput = InputManager.GetLookInput(); HasActiveLookInput = lookInput.magnitude > 0.1f; if (Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > 0.1f) IsUsingGamepad = true; else if (Mouse.current != null && Mouse.current.delta.ReadValue().magnitude > 0.1f) IsUsingGamepad = false; } // ── Camera Shake ────────────────────────────────────────────────────────── public void ShakeCamera(float amplitude, float smoothness = 0f) { if (PlayerController.Instance == null || PlayerController.Instance.CameraPivot == null || Settings == null) return; PlayerEvents.TriggerCameraShake(amplitude); _shakeSmoothness = smoothness; _shakeTimer -= Time.deltaTime; if (_shakeTimer <= 0f) { _shakeTimer = _shakeInterval * amplitude; _shakeOriginalPos = PlayerController.Instance.CameraPivot.position; float x = Random.Range(-1f, 1f) * amplitude; float y = Random.Range(-1f, 1f) * amplitude; _shakeTarget = _shakeOriginalPos + new Vector3(x, y, 0f); } if (_shakeSmoothness > 0f) { PlayerController.Instance.CameraPivot.position = Vector3.Lerp( PlayerController.Instance.CameraPivot.position, _shakeTarget, Time.deltaTime * _shakeSmoothness); } else { PlayerController.Instance.CameraPivot.position = _shakeTarget; } } // ── Aim Assist ──────────────────────────────────────────────────────────── public void ApplyAimAssist(Vector3 targetCenter) { _pendingAimAssistTarget = targetCenter; _hasAimAssistTarget = true; } private void ApplyAimAssistInternal(Vector3 targetCenter) { if (PlayerController.Instance == null || PlayerController.Instance.CameraTransform == null || Settings == null) return; if (PlayerController.Instance.Movement == null) return; if (IsAimLocked && HasActiveLookInput) IsAimLocked = false; Vector3 directionToTarget = (targetCenter - PlayerController.Instance.CameraTransform.position).normalized; float angleError = Vector3.Angle(PlayerController.Instance.CameraTransform.forward, directionToTarget); if (IsAimLocked || angleError < Settings.aimAssistMaxAngle) { Vector3 targetDirection = directionToTarget; float targetYaw = Mathf.Atan2(targetDirection.x, targetDirection.z) * Mathf.Rad2Deg; float targetPitch = -Mathf.Asin(targetDirection.y) * Mathf.Rad2Deg; float currentYaw = OctoPlayer.Instance.State.Yaw; float currentPitch = OctoPlayer.Instance.State.Pitch; float newYaw = Mathf.LerpAngle(currentYaw, targetYaw, Settings.aimAssistStrength * Time.deltaTime); float newPitch = Mathf.Lerp(currentPitch, targetPitch, Settings.aimAssistStrength * Time.deltaTime); OctoPlayer.Instance.SetYaw(newYaw); OctoPlayer.Instance.SetPitch(newPitch); } _hasAimAssistTarget = false; } public void LockAim(bool locked) { IsAimLocked = locked; } } }