using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; namespace Colosseum.Player { /// /// 3인칭 카메라 컨트롤러 /// public class PlayerCamera : MonoBehaviour { [Header("Camera Settings")] [SerializeField] private float distance = 5f; [SerializeField] private float height = 2f; [SerializeField] private float rotationSpeed = 2f; [SerializeField] private float minPitch = -30f; [SerializeField] private float maxPitch = 60f; [Header("Collision")] [SerializeField] private LayerMask collisionMask; [SerializeField] private float minHeightAboveGround = 0.3f; // 바닥 위 최소 카메라 높이 [Header("Smooth Distance")] [Min(0.01f)] [SerializeField] private float distanceSmoothTime = 0.15f; private Transform target; private float yaw; private float pitch; private Camera cameraInstance; private InputSystem_Actions inputActions; private bool isSpectating = false; private float currentDistance; private float distanceSmoothVelocity; public Transform Target => target; public bool IsSpectating => isSpectating; private void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { // 씬 로드 시 카메라 참조 갱신 RefreshCamera(); SnapToTarget(); Debug.Log($"[PlayerCamera] Scene loaded, camera refreshed"); } public void Initialize(Transform playerTransform, InputSystem_Actions actions) { target = playerTransform; inputActions = actions; isSpectating = false; // 충돌 마스크 기본값 설정 (Default, Enemy, Ground 레이어만) if (collisionMask == 0) collisionMask = LayerMask.GetMask("Default", "Enemy", "Ground"); // 기존 메인 카메라 사용 또는 새로 생성 cameraInstance = Camera.main; Debug.Log($"[PlayerCamera] Initialize: target={playerTransform?.name}, Camera.main={cameraInstance?.name ?? "NULL"}"); if (cameraInstance == null) { var cameraObject = new GameObject("PlayerCamera"); cameraInstance = cameraObject.AddComponent(); cameraObject.tag = "MainCamera"; } // 초기 각도 if (target != null) { yaw = target.eulerAngles.y; } pitch = 20f; // 부드러운 거리 보간 상태 초기화 currentDistance = distance; distanceSmoothVelocity = 0f; // 장애물 반투명 컨트롤러 연동 var transparencyController = cameraInstance.gameObject.GetComponent(); if (transparencyController == null) transparencyController = cameraInstance.gameObject.AddComponent(); transparencyController.Initialize(target, collisionMask); // 플레이어 외곽선 컨트롤러 연동 (보스 가림 시) var outlineController = cameraInstance.gameObject.GetComponent(); if (outlineController == null) outlineController = cameraInstance.gameObject.AddComponent(); outlineController.Initialize(cameraInstance.transform, target); // 카메라 위치를 즉시 타겟 위치로 초기화 SnapToTarget(); } /// /// 관전 대상 변경 /// public void SetTarget(Transform newTarget) { if (newTarget == null) return; target = newTarget; isSpectating = true; // 부드러운 전환을 위해 현재 카메라 위치에서 새 타겟으로 yaw = target.eulerAngles.y; Debug.Log($"[PlayerCamera] Now spectating: {target.name}"); } /// /// 원래 플레이어로 복귀 /// public void ResetToPlayer(Transform playerTransform) { target = playerTransform; isSpectating = false; } /// /// 카메라 위치를 타겟 위치로 즉시 이동 (부드러운 전환 없이) /// public void SnapToTarget() { if (target == null || cameraInstance == null) return; // 부드러운 거리 보간 상태 리셋 currentDistance = distance; distanceSmoothVelocity = 0f; UpdateCameraPosition(); } /// /// 카메라 참조 갱신 (씬 전환 후 호출) /// public void RefreshCamera() { // 씬 전환 시 항상 새 카메라 참조 획득 cameraInstance = Camera.main; } private void LateUpdate() { // 카메라 참조가 없으면 갱신 시도 if (cameraInstance == null) { RefreshCamera(); } if (target == null || cameraInstance == null) return; HandleRotation(); UpdateCameraPosition(); } private void HandleRotation() { if (inputActions == null) return; // Input Actions에서 Look 입력 받기 Vector2 lookInput = inputActions.Player.Look.ReadValue(); float mouseX = lookInput.x * rotationSpeed * 0.1f; float mouseY = lookInput.y * rotationSpeed * 0.1f; yaw += mouseX; pitch -= mouseY; pitch = Mathf.Clamp(pitch, minPitch, maxPitch); } private void UpdateCameraPosition() { // 구면 좌표로 카메라 위치 계산 Quaternion rotation = Quaternion.Euler(pitch, yaw, 0f); Vector3 offset = rotation * new Vector3(0f, 0f, -distance); offset.y += height; Vector3 pivot = target.position + Vector3.up * height * 0.5f; Vector3 desiredPos = target.position + offset; // 부드러운 거리 보간 currentDistance = Mathf.SmoothDamp(currentDistance, distance, ref distanceSmoothVelocity, distanceSmoothTime); // 보간된 거리로 최종 오프셋 재계산 offset = rotation * new Vector3(0f, 0f, -currentDistance); offset.y += height; desiredPos = target.position + offset; // 카메라 아래에 지면이 너무 가까우면 위로 밀어올려 바닥 아래면이 보이지 않게 함 if (Physics.Raycast(desiredPos, Vector3.down, out RaycastHit groundHit, minHeightAboveGround + 1f, collisionMask, QueryTriggerInteraction.Ignore)) { float groundY = groundHit.point.y + minHeightAboveGround; if (desiredPos.y < groundY) desiredPos.y = groundY; } cameraInstance.transform.position = desiredPos; cameraInstance.transform.LookAt(pivot); } } }