using UnityEngine;
///
/// กล้องมุมมองบุคคลที่สาม: กดเมาส์ขวาค้างแล้วลากเพื่อหมุนกล้องรอบตัวผู้เล่น
/// เลื่อนลูกกลิ้งเมาส์เพื่อซูม และกล้องไม่ทะลุกำแพง
/// ใส่สคริปต์นี้ที่ Main Camera
///
public class ThirdPersonCamera : MonoBehaviour
{
[Header("Target")]
[SerializeField] private Transform target;
[Tooltip("จุดที่กล้องมองรอบ ๆ (ความสูงจากเท้าผู้เล่น ประมาณระดับหัว/ไหล่)")]
[SerializeField] private float pivotHeight = 1.6f;
[Header("Rotation (กดเมาส์ขวาค้าง)")]
[SerializeField] private float sensitivity = 3f;
[SerializeField] private bool invertY = false;
[SerializeField] private float minPitch = -20f;
[SerializeField] private float maxPitch = 70f;
[SerializeField] private bool hideCursorWhileRotating = true;
[Header("Zoom")]
[SerializeField] private float distance = 5f;
[SerializeField] private float minDistance = 2f;
[SerializeField] private float maxDistance = 10f;
[SerializeField] private float zoomSpeed = 1.5f;
[SerializeField] private float zoomSmoothing = 10f;
[Header("Collision")]
[SerializeField] private LayerMask collisionMask = ~0;
[SerializeField] private float collisionRadius = 0.25f;
[Header("Auto follow (ไม่บังคับ)")]
[Tooltip("เปิดแล้วกล้องจะค่อย ๆ หมุนไปอยู่ด้านหลังตัวละครเมื่อเดินไปข้างหน้า (ตอนไม่ได้กดเมาส์ขวา)")]
[SerializeField] private bool autoFollowBehind = false;
[SerializeField] private float followSpeed = 2f;
private float yaw;
private float pitch = 15f;
private float currentDistance;
private bool wasRotating;
private Vector3 lastTargetPos;
/// มุม yaw ปัจจุบันของกล้อง (องศา)
public float Yaw => yaw;
private void Start()
{
if (target == null)
{
var player = GameObject.FindGameObjectWithTag("Player");
if (player) target = player.transform;
}
var e = transform.eulerAngles;
yaw = e.y;
pitch = Mathf.Clamp(NormalizeAngle(e.x), minPitch, maxPitch);
currentDistance = distance;
if (target) lastTargetPos = target.position;
}
private void LateUpdate()
{
if (target == null) return;
bool rotating = GameInput.RightMouseHeld;
// ----- หมุนกล้อง -----
if (rotating)
{
Vector2 look = GameInput.Look;
yaw += look.x * sensitivity;
pitch += (invertY ? 1f : -1f) * look.y * sensitivity;
pitch = Mathf.Clamp(pitch, minPitch, maxPitch);
}
else if (autoFollowBehind && GameInput.Move.y > 0.1f)
{
Vector3 delta = target.position - lastTargetPos;
delta.y = 0f;
if (delta.sqrMagnitude > 0.00001f)
{
float walkYaw = Mathf.Atan2(delta.x, delta.z) * Mathf.Rad2Deg;
yaw = Mathf.LerpAngle(yaw, walkYaw, 1f - Mathf.Exp(-followSpeed * Time.deltaTime));
}
}
lastTargetPos = target.position;
// ----- ซ่อน/ล็อกเคอร์เซอร์ตอนกดขวาค้าง -----
if (hideCursorWhileRotating && rotating != wasRotating)
{
Cursor.lockState = rotating ? CursorLockMode.Locked : CursorLockMode.None;
Cursor.visible = !rotating;
}
wasRotating = rotating;
// ----- ซูม -----
distance = Mathf.Clamp(distance - GameInput.Scroll * zoomSpeed, minDistance, maxDistance);
// ----- คำนวณตำแหน่งกล้อง -----
Quaternion rot = Quaternion.Euler(pitch, yaw, 0f);
Vector3 pivot = target.position + Vector3.up * pivotHeight;
Vector3 back = rot * Vector3.back;
float wanted = distance;
if (Physics.SphereCast(pivot, collisionRadius, back, out RaycastHit hit, distance,
collisionMask, QueryTriggerInteraction.Ignore)
&& !hit.transform.IsChildOf(target))
{
wanted = Mathf.Max(hit.distance, 0.5f);
}
// ชนกำแพง = ดึงกล้องเข้าทันที / พ้นกำแพง = ค่อย ๆ ถอยออก
currentDistance = wanted < currentDistance
? wanted
: Mathf.Lerp(currentDistance, wanted, 1f - Mathf.Exp(-zoomSmoothing * Time.deltaTime));
transform.SetPositionAndRotation(pivot + back * currentDistance, rot);
}
private void OnDisable()
{
if (hideCursorWhileRotating)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
}
private static float NormalizeAngle(float a) => a > 180f ? a - 360f : a;
}