using UnityEngine; using Unheimlich.Interactions.Core; using Unheimlich.Data.CameraSettings; namespace Unheimlich.Gameplay.Player { /// /// Simulates camera head bobbing motion during player movement and idle states. /// Applies subtle oscillation based on player grounded and movement state. /// public class CameraHeadBob : MonoBehaviour { [Header("Bob Settings - Idle")] [SerializeField] private float _idleBobSpeed = 1f; [SerializeField] private float _idleBobAmplitude = 0.005f; [Header("Bob Settings - Walk")] [SerializeField] private float _walkBobSpeed = 12f; [SerializeField] private float _walkBobAmplitude = 0.02f; [SerializeField] private float _resetSpeed = 5f; private PlayerMovement _playerMovement; private float _timer = 0f; private Vector3 _startLocalPos; private HeadBobMode _overrideMode = HeadBobMode.Normal; private bool _hasOverride = false; private Coroutine _resetCoroutine; void Start() { _playerMovement = PlayerMovement.Instance; _startLocalPos = transform.localPosition; } void Update() { if (_playerMovement == null || _playerMovement.CharacterController == null) return; HeadBobMode currentMode = _hasOverride ? _overrideMode : HeadBobMode.Normal; if (currentMode == HeadBobMode.Disabled) { ResetPosition(); return; } if (currentMode == HeadBobMode.Idle || (PlayerInspectionHandler.Instance != null && PlayerInspectionHandler.Instance.IsInspecting)) { ApplyBob(_idleBobSpeed, _idleBobAmplitude); return; } bool isMoving = _playerMovement.IsMoving; bool isGrounded = _playerMovement.CharacterController.isGrounded; if (isGrounded) { if (isMoving) ApplyBob(_walkBobSpeed, _walkBobAmplitude); else ApplyBob(_idleBobSpeed, _idleBobAmplitude); } else ResetPosition(); } /// /// Sets the head bob mode override. /// public void SetMode(HeadBobMode mode) { _overrideMode = mode; _hasOverride = true; } /// /// Clears the override and returns to normal behavior. /// public void ClearOverride() { _hasOverride = false; if (_resetCoroutine != null) { StopCoroutine(_resetCoroutine); _resetCoroutine = null; } } /// /// Smoothly lerps the head bob position to zero over the specified duration. /// public void LerpToZero(float duration) { if (_resetCoroutine != null) StopCoroutine(_resetCoroutine); _resetCoroutine = StartCoroutine(LerpToZeroCoroutine(duration)); } private System.Collections.IEnumerator LerpToZeroCoroutine(float duration) { Vector3 startPos = transform.localPosition; float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / duration); transform.localPosition = Vector3.Lerp(startPos, Vector3.zero, t); yield return null; } transform.localPosition = Vector3.zero; _resetCoroutine = null; } private void ApplyBob(float speed, float amplitude) { _timer += Time.deltaTime * speed; float x = Mathf.Sin(_timer * 0.5f) * amplitude; float y = Mathf.Cos(_timer) * amplitude; Vector3 newPos = _startLocalPos; newPos.x += x; newPos.y += y; transform.localPosition = newPos; } private void ResetPosition() { transform.localPosition = Vector3.Lerp(transform.localPosition, _startLocalPos, Time.deltaTime * _resetSpeed); if (Vector3.Distance(transform.localPosition, _startLocalPos) < 0.001f) _timer = 0f; } } }