Files
BABA_YAGA/Assets/Scripts/Camera Controller/CameraController.cs

287 lines
12 KiB
C#
Raw Normal View History

2026-03-26 20:27:19 +07:00
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<Camera>();
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<Renderer>();
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);
}
}