using System; using UnityEngine; namespace OnlyScove.Scripts { public class CameraController : MonoBehaviour { [SerializeField] InputReader inputReader; // Kéo thả Object chứa InputReader vào đây [SerializeField] Transform followTarget; [SerializeField] float distance = 5; [SerializeField] float minDistance = 2f; [SerializeField] float maxDistance = 15f; [SerializeField] float zoomSensitivity = 1f; [SerializeField] float sensitivity = 0.1f; // Độ nhạy (chỉnh trong Inspector) [SerializeField] LayerMask collisionLayers; [SerializeField] float cameraRadius = 0.2f; [SerializeField] float positionSmoothTime = 0.12f; // Độ trễ đuổi theo nhân vật [SerializeField] float rotationSmoothTime = 5f; // Tốc độ làm mượt vòng xoay chuột [Header("Auto Rotation")] [SerializeField] bool useAutoRotation = true; [SerializeField] float autoRotateDelay = 2.5f; // Sau bao lâu không chạm chuột thì xoay [SerializeField] float autoRotateSpeed = 2f; // Tốc độ xoay về sau lưng [Header("Occlusion Transparency")] [SerializeField] bool useTransparency = true; [SerializeField] LayerMask transparencyLayers; [SerializeField] float fadeAlpha = 0.3f; // Độ trong suốt (0 là biến mất, 1 là hiện rõ) [Header("Dynamic FOV")] [SerializeField] bool useDynamicFOV = true; [SerializeField] float baseFOV = 60f; [SerializeField] float sprintFOV = 70f; [SerializeField] float fovSmoothTime = 5f; [Header("Character Fading")] [SerializeField] bool useCharacterFading = true; [SerializeField] float minVisibleDistance = 1.2f; // Khoảng cách bắt đầu mờ [SerializeField] float fullyHiddenDistance = 0.6f; // Khoảng cách biến mất hẳn [SerializeField] Renderer[] characterRenderers; // Kéo các Mesh của nhân vật vào đây [Header("Side Bias")] [SerializeField] bool useSideBias = true; [SerializeField] float horizontalBiasAmount = 0.5f; // Độ lệch sang trái/phải [SerializeField] float biasSmoothTime = 3f; // Tốc độ chuyển đổi độ lệch [Header("Camera Shake")] [SerializeField] bool useShake = true; private float shakeIntensity = 0f; private float shakeDuration = 0f; private float shakeTimer = 0f; private Vector3 shakeOffset; [SerializeField] float minVerticalAngle = -45f; [SerializeField] float maxVerticalAngle = 45f; [SerializeField] Vector2 framingOffset; [SerializeField] private bool invertX; [SerializeField] private bool invertY; private float rotationX; private float rotationY; private float invertXVal; private float invertYVal; private float lastInputTime; private Vector3 currentVelocity; private Quaternion currentRotation; private Camera cam; private Renderer lastFadedRenderer; private Color originalColor; private float currentSideBias; private void Start() { cam = GetComponent(); Cursor.visible = false; Cursor.lockState = CursorLockMode.Locked; // Khởi tạo vòng xoay hiện tại rotationX = transform.eulerAngles.x; rotationY = transform.eulerAngles.y; currentRotation = transform.rotation; lastInputTime = Time.time; } private void Update() { if (inputReader != null) { // Kiểm tra xem có input xoay chuột không if (inputReader.LookInput.magnitude > 0.01f) { lastInputTime = Time.time; } invertXVal = (invertX) ? -1 : 1; invertYVal = (invertY) ? -1 : 1; rotationX -= inputReader.LookInput.y * invertYVal * sensitivity * Time.deltaTime; rotationX = Mathf.Clamp(rotationX, minVerticalAngle, maxVerticalAngle); rotationY += inputReader.LookInput.x * invertXVal * sensitivity * Time.deltaTime; // Logic Side Bias (Lệch khung hình khi di chuyển) if (useSideBias) { float targetBias = -inputReader.MoveInput.x * horizontalBiasAmount; currentSideBias = Mathf.Lerp(currentSideBias, targetBias, biasSmoothTime * Time.deltaTime); } else { currentSideBias = 0; } // Logic Tự động xoay sau lưng (Auto-Correction) if (useAutoRotation && Time.time - lastInputTime > autoRotateDelay) { // Chỉ xoay khi nhân vật đang di chuyển if (inputReader.MoveInput.magnitude > 0.1f) { // Lấy hướng nhân vật đang nhìn (Yaw) float targetYaw = followTarget.eulerAngles.y; // Dùng LerpAngle để xoay mượt mà về hướng đó rotationY = Mathf.LerpAngle(rotationY, targetYaw, autoRotateSpeed * Time.deltaTime); } } float scrollDelta = inputReader.ScrollInput.y; if (Mathf.Abs(scrollDelta) > 0.1f) { distance -= scrollDelta * zoomSensitivity * Time.deltaTime; distance = Mathf.Clamp(distance, minDistance, maxDistance); } } // Xoay chuột: Làm mượt bằng Slerp Quaternion targetRotation = Quaternion.Euler(rotationX, rotationY, 0f); currentRotation = Quaternion.Slerp(currentRotation, targetRotation, rotationSmoothTime * Time.deltaTime); // Vị trí mục tiêu: Áp dụng offset + Side Bias Vector3 focusPosition = followTarget.position + currentRotation * new Vector3(framingOffset.x + currentSideBias, framingOffset.y, 0); // Collision Logic (Dùng currentRotation đã được làm mượt) float targetDistance = distance; RaycastHit hit; Vector3 rayStart = focusPosition; Vector3 rayDirection = currentRotation * Vector3.back; if (Physics.SphereCast(rayStart, cameraRadius, rayDirection, out hit, distance, collisionLayers)) { targetDistance = Mathf.Max(minDistance, hit.distance - 0.1f); } // Logic Làm mờ nhân vật (Character Fading) if (useCharacterFading && characterRenderers != null && characterRenderers.Length > 0) { HandleCharacterFading(targetDistance); } // Vị trí cuối cùng: Làm mượt bằng SmoothDamp để tạo độ trễ đuổi theo Vector3 targetPosition = focusPosition - currentRotation * new Vector3(0, 0, targetDistance); // Xử lý Camera Shake if (useShake && shakeTimer > 0) { HandleShake(); } else { shakeOffset = Vector3.zero; } transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref currentVelocity, positionSmoothTime) + shakeOffset; transform.rotation = currentRotation; // Logic Làm trong suốt vật cản (Occlusion Transparency) if (useTransparency) { HandleTransparency(focusPosition); } // Logic FOV linh hoạt if (useDynamicFOV && cam != null) { HandleDynamicFOV(); } } private void HandleDynamicFOV() { float targetFOV = baseFOV; // Nếu đang di chuyển và nhấn giữ nút Sprint if (inputReader.MoveInput.magnitude > 0.1f && inputReader.IsSprintHeld) { targetFOV = sprintFOV; } // Làm mượt quá trình thay đổi FOV cam.fieldOfView = Mathf.Lerp(cam.fieldOfView, targetFOV, fovSmoothTime * Time.deltaTime); } private void HandleShake() { shakeTimer -= Time.deltaTime; // Cường độ rung giảm dần theo thời gian float currentIntensity = (shakeTimer / shakeDuration) * shakeIntensity; // Dùng Perlin Noise để tạo rung động mượt mà float shakeX = (Mathf.PerlinNoise(Time.time * 25f, 0f) - 0.5f) * 2f; float shakeY = (Mathf.PerlinNoise(0f, Time.time * 25f) - 0.5f) * 2f; float shakeZ = (Mathf.PerlinNoise(Time.time * 25f, Time.time * 25f) - 0.5f) * 2f; shakeOffset = new Vector3(shakeX, shakeY, shakeZ) * currentIntensity; } public void Shake(float intensity, float duration) { shakeIntensity = intensity; shakeDuration = duration; shakeTimer = duration; } private void HandleCharacterFading(float currentDistance) { // Tính độ mờ dựa trên khoảng cách float alpha = Mathf.InverseLerp(fullyHiddenDistance, minVisibleDistance, currentDistance); foreach (var renderer in characterRenderers) { if (renderer != null) { Color color = renderer.material.color; color.a = alpha; renderer.material.color = color; } } } private void HandleTransparency(Vector3 focusPosition) { Vector3 direction = focusPosition - transform.position; float distanceToPlayer = direction.magnitude; RaycastHit hit; // Bắn một tia từ Camera đến Nhân vật if (Physics.Raycast(transform.position, direction.normalized, out hit, distanceToPlayer, transparencyLayers)) { Renderer renderer = hit.collider.GetComponent(); if (renderer != null && renderer != lastFadedRenderer) { // Nếu chạm vật mới, khôi phục vật cũ ResetLastRenderer(); // Lưu thông tin vật mới và làm mờ lastFadedRenderer = renderer; originalColor = renderer.material.color; Color fadedColor = originalColor; fadedColor.a = fadeAlpha; // Lưu ý: Material cần hỗ trợ Transparency (Surface Type: Transparent trong URP) renderer.material.color = fadedColor; } } else { // Nếu không chạm gì, khôi phục vật cũ ResetLastRenderer(); } } private void ResetLastRenderer() { if (lastFadedRenderer != null) { lastFadedRenderer.material.color = originalColor; lastFadedRenderer = null; } } public Quaternion PlanarRotation => Quaternion.Euler(0f, rotationY, 0f); } }