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; }