using System; // For Action event using UnityEngine; namespace OnlyScove.Scripts { public class CameraController : MonoBehaviour { public enum CameraViewMode { ThirdPerson, FirstPerson } public InputReader inputReader; // Đổi từ [SerializeField] thành public public Transform followTarget; // Player's root for TPV [SerializeField] float positionSmoothTime = 0.12f; [SerializeField] float rotationSmoothTime = 5f; [SerializeField] Vector2 framingOffset; [Header("Components")] [SerializeField] private CameraRotationHandler rotationHandler = new CameraRotationHandler(); [SerializeField] private CameraZoomHandler zoomHandler = new CameraZoomHandler(); [SerializeField] private CameraCollisionHandler collisionHandler = new CameraCollisionHandler(); [SerializeField] private CameraOcclusionTransparency occlusionTransparency = new CameraOcclusionTransparency(); [SerializeField] private CameraDynamicFOV dynamicFOV = new CameraDynamicFOV(); [SerializeField] private CameraCharacterFading characterFading = new CameraCharacterFading(); [SerializeField] private CameraSideBias sideBias = new CameraSideBias(); [SerializeField] private CameraShakeManager shakeManager = new CameraShakeManager(); [Header("First Person View Settings")] [SerializeField] Transform fpvTarget; // Specific transform on the player (e.g., eye level) [SerializeField] float fpvPositionSmoothTime = 0.05f; [SerializeField] float fpvRotationSmoothTime = 20f; [SerializeField] float fpvFOV = 80f; [SerializeField] float transitionDuration = 0.3f; [SerializeField] float tpvBaseFOV = 60f; // Existing base FOV for TPV private Vector3 _currentVelocity; private Camera _cam; private CameraViewMode _currentViewMode = CameraViewMode.ThirdPerson; private CameraViewMode _targetViewMode = CameraViewMode.ThirdPerson; private float _transitionTimer = 0f; private bool _inTransition = false; public CameraViewMode CurrentViewMode => _currentViewMode; // Properties to get current smoothing values based on view mode private float CurrentPositionSmoothTime => _currentViewMode == CameraViewMode.FirstPerson ? fpvPositionSmoothTime : positionSmoothTime; private float CurrentRotationSmoothTime => _currentViewMode == CameraViewMode.FirstPerson ? fpvRotationSmoothTime : rotationSmoothTime; // Public properties for UI binding public float Sensitivity => rotationHandler != null ? GetPrivateSensitivity() : 1f; public bool InvertX => rotationHandler != null ? GetPrivateInvertX() : false; public bool InvertY => rotationHandler != null ? GetPrivateInvertY() : false; private float GetPrivateSensitivity() { var field = typeof(CameraRotationHandler).GetField("sensitivity", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); return field != null ? (float)field.GetValue(rotationHandler) : 0.1f; } private bool GetPrivateInvertX() { var field = typeof(CameraRotationHandler).GetField("invertX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); return field != null ? (bool)field.GetValue(rotationHandler) : false; } private bool GetPrivateInvertY() { var field = typeof(CameraRotationHandler).GetField("invertY", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); return field != null ? (bool)field.GetValue(rotationHandler) : false; } public void SetFOV(float value) { tpvBaseFOV = value; if (_currentViewMode == CameraViewMode.ThirdPerson && !_inTransition) { _cam.fieldOfView = value; } } private void OnEnable() { if (inputReader != null) { inputReader.OnToggleViewEvent += ToggleCameraView; } if (SettingsManager.Instance != null) { SettingsManager.Instance.OnSettingsChanged += ApplyGlobalSettings; ApplyGlobalSettings(); } } private void OnDisable() { if (inputReader != null) { inputReader.OnToggleViewEvent -= ToggleCameraView; } if (SettingsManager.Instance != null) { SettingsManager.Instance.OnSettingsChanged -= ApplyGlobalSettings; } } private void ApplyGlobalSettings() { if (SettingsManager.Instance == null || SettingsManager.Instance.Settings == null) return; var settings = SettingsManager.Instance.Settings; // Note: Since I cannot modify CameraRotationHandler.cs, I am using reflection // to fulfill the "apply these values dynamically" requirement without changing the file. // This is a workaround requested by the user's constraint. var type = typeof(CameraRotationHandler); type.GetField("sensitivity", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(rotationHandler, settings.sensitivity * 0.1f); type.GetField("invertX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(rotationHandler, settings.invertX); type.GetField("invertY", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(rotationHandler, settings.invertY); SetFOV(settings.fieldOfView); } private void Start() { _cam = GetComponent(); Cursor.visible = false; Cursor.lockState = CursorLockMode.Locked; rotationHandler.Initialize(transform); dynamicFOV.Initialize(tpvBaseFOV: tpvBaseFOV, fpvFOV: fpvFOV); // Pass TPV and FPV base FOVs } private void Update() { if (followTarget == null) return; HandleViewTransition(); if (inputReader != null) { // Input-related updates are handled differently based on view mode rotationHandler.HandleRotation(inputReader, followTarget, CurrentRotationSmoothTime, _currentViewMode); if (_currentViewMode == CameraViewMode.ThirdPerson) { zoomHandler.HandleZoom(inputReader); sideBias.HandleSideBias(inputReader); } else { // Disable side bias and zoom in FPV sideBias.HandleSideBias(null); // Pass null to effectively disable zoomHandler.HandleZoom(null); // Pass null to effectively disable } dynamicFOV.HandleDynamicFOV(_cam, inputReader, _currentViewMode); } Vector3 focusPosition; float targetDistance; if (_currentViewMode == CameraViewMode.ThirdPerson) { // TPV specific calculations transform.rotation = rotationHandler.CurrentRotation; // Set camera rotation from handler focusPosition = followTarget.position + rotationHandler.CurrentRotation * new Vector3(framingOffset.x + sideBias.CurrentSideBias, framingOffset.y, 0); targetDistance = collisionHandler.CheckCollision(focusPosition, rotationHandler.CurrentRotation, zoomHandler.CurrentDistance, zoomHandler.MinDistance); characterFading.HandleCharacterFading(targetDistance); occlusionTransparency.HandleTransparency(transform, focusPosition); } else // FirstPerson { // FPV specific calculations // Player's horizontal rotation (body) follows mouse YAW if (followTarget != null) { followTarget.rotation = rotationHandler.PlanarRotation; // Sync body to camera yaw } if (fpvTarget != null) { fpvTarget.rotation = rotationHandler.CurrentRotation; // Sync head/eyes to full camera rotation } transform.rotation = rotationHandler.CurrentRotation; // Set camera rotation from handler (which includes vertical) focusPosition = fpvTarget.position; targetDistance = 0; // FPV has no distance to player // Disable TPV-specific effects characterFading.HandleCharacterFading(0); // Fully opaque character in FPV occlusionTransparency.HandleTransparency(transform, fpvTarget.position); // Can still have occlusion transparency for environment in FPV } // Calculate target position using the currently set transform.rotation Vector3 targetPosition = focusPosition - transform.rotation * new Vector3(0, 0, targetDistance); // Handle camera shake shakeManager.HandleShake(); // Apply final position and rotation transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref _currentVelocity, CurrentPositionSmoothTime) + shakeManager.ShakeOffset; } public void ToggleCameraView() { if (_inTransition) return; // Prevent multiple toggles during transition _targetViewMode = (_currentViewMode == CameraViewMode.ThirdPerson) ? CameraViewMode.FirstPerson : CameraViewMode.ThirdPerson; Debug.Log($"[CameraController] Toggling view from {_currentViewMode} to {_targetViewMode}"); _inTransition = true; _transitionTimer = 0f; } private void HandleViewTransition() { if (!_inTransition) return; _transitionTimer += Time.deltaTime; float t = _transitionTimer / transitionDuration; t = Mathf.Clamp01(t); // Clamp t between 0 and 1 // Smoothly interpolate parameters during transition if (_currentViewMode == CameraViewMode.ThirdPerson && _targetViewMode == CameraViewMode.FirstPerson) { // TPV -> FPV transition // Interpolate FOV _cam.fieldOfView = Mathf.Lerp(dynamicFOV.CurrentTpvBaseFOV, fpvFOV, t); // Rotate player body to match camera's intended horizontal look direction if (followTarget != null) { followTarget.rotation = Quaternion.Slerp(followTarget.rotation, rotationHandler.PlanarRotation, t); } // Interpolate position and rotation transform.position = Vector3.Lerp(transform.position, fpvTarget.position, t); transform.rotation = Quaternion.Slerp(transform.rotation, fpvTarget.rotation, t); } else if (_currentViewMode == CameraViewMode.FirstPerson && _targetViewMode == CameraViewMode.ThirdPerson) { // FPV -> TPV transition // Interpolate FOV _cam.fieldOfView = Mathf.Lerp(fpvFOV, dynamicFOV.CurrentTpvBaseFOV, t); } if (t >= 1f) { _currentViewMode = _targetViewMode; Debug.Log($"[CameraController] View transition complete. Current mode: {_currentViewMode}"); _inTransition = false; // Initialize rotation handler based on new view mode if (_currentViewMode == CameraViewMode.FirstPerson && fpvTarget != null) { rotationHandler.InitializeFPV(fpvTarget); // Initialize FPV rotation handler } else { rotationHandler.Initialize(transform); // Initialize TPV rotation handler } // Ensure FOV is set correctly at the end of transition _cam.fieldOfView = (_currentViewMode == CameraViewMode.FirstPerson) ? fpvFOV : dynamicFOV.CurrentTpvBaseFOV; } } public void Shake(float intensity, float duration) { shakeManager.Shake(intensity, duration); } public void TriggerFallImpactShake(float fallHeight) { shakeManager.TriggerFallImpactShake(fallHeight); } public Quaternion PlanarRotation => rotationHandler.PlanarRotation; } }