using System; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; public class RoomEnterHandler : MonoBehaviour { [Serializable] public class RoomTarget { public string roomId; public Transform spawnPoint; public GameObject roomRoot; public UnityEvent onEnter; } [Header("Enter Mode")] [Tooltip("켜면 RoomData의 Scene Name으로 씬을 이동합니다. 끄면 같은 씬 안에서 위치 이동/방 오브젝트 활성화를 합니다.")] [SerializeField] private bool useSceneLoading = false; [Header("Same Scene Movement")] [SerializeField] private Transform playerRoot; [SerializeField] private bool deactivateOtherRoomRoots = false; [SerializeField] private List roomTargets = new List(); [Header("UI")] [SerializeField] private GameObject roomSelectRoot; [SerializeField] private bool hideRoomSelectOnEnter = true; [Header("Room Enter Message UI")] [Tooltip("방 입장 메시지 패널입니다.")] [SerializeField] private GameObject roomEnterMessagePanel; [Tooltip("방 입장 메시지를 표시할 TextMeshProUGUI입니다.")] [SerializeField] private TextMeshProUGUI roomEnterMessageText; [Tooltip("방 입장 메시지 전체 Root입니다. 보통 RoomEnterMessageCanvas를 넣습니다.")] [SerializeField] private Transform roomEnterMessageRoot; [Tooltip("메시지가 화면에 표시되는 시간입니다.")] [SerializeField] private float roomEnterMessageDuration = 2f; [Tooltip("{0} 자리에 RoomData의 RoomName이 들어갑니다.")] [SerializeField] private string roomEnterMessageFormat = "{0}에 입장했습니다"; [Header("Room Enter Message Position")] [Tooltip("메시지를 기준으로 배치할 카메라입니다. XR Origin > Camera Offset > Main Camera를 넣으세요.")] [SerializeField] private Transform messageCameraTarget; [Tooltip("켜면 방 입장 메시지가 카메라 앞쪽으로 자동 이동합니다.")] [SerializeField] private bool placeMessageInFrontOfCamera = true; [Tooltip("카메라 앞 몇 m 위치에 메시지를 띄울지 정합니다.")] [SerializeField] private float messageDistance = 1.6f; [Tooltip("월드 기준 위/아래 위치 보정값입니다. 양수면 위로 올라갑니다.")] [SerializeField] private float messageHeightOffset = 0.25f; [Tooltip("글자가 뒤집혀 보이면 켜세요.")] [SerializeField] private bool flipMessageRotation = false; [Header("Events")] public UnityEvent onAnyRoomEntered; private Coroutine roomEnterMessageCoroutine; private void Awake() { if (messageCameraTarget == null && Camera.main != null) { messageCameraTarget = Camera.main.transform; } if (roomEnterMessagePanel != null) { roomEnterMessagePanel.SetActive(false); } } public void EnterRoom(RoomData roomData) { if (roomData == null) { Debug.LogWarning($"[{nameof(RoomEnterHandler)}] RoomData가 없습니다."); return; } if (hideRoomSelectOnEnter && roomSelectRoot != null) { roomSelectRoot.SetActive(false); } if (useSceneLoading) { EnterRoomByScene(roomData); } else { EnterRoomInSameScene(roomData); } onAnyRoomEntered?.Invoke(); } private void EnterRoomByScene(RoomData roomData) { if (!roomData.HasSceneName()) { Debug.LogWarning($"[{nameof(RoomEnterHandler)}] Scene Name이 비어 있습니다: {roomData.RoomName}"); return; } SceneManager.LoadScene(roomData.SceneName); } private void EnterRoomInSameScene(RoomData roomData) { RoomTarget target = FindTarget(roomData.RoomId); if (target == null) { Debug.Log($"[{nameof(RoomEnterHandler)}] 방 입장 테스트: {roomData.RoomName} / roomId: {roomData.RoomId}"); return; } if (deactivateOtherRoomRoots) { foreach (RoomTarget roomTarget in roomTargets) { if (roomTarget != null && roomTarget.roomRoot != null) { roomTarget.roomRoot.SetActive(roomTarget == target); } } } else if (target.roomRoot != null) { target.roomRoot.SetActive(true); } if (playerRoot != null && target.spawnPoint != null) { CharacterController controller = playerRoot.GetComponent(); bool hadController = controller != null; if (hadController) { controller.enabled = false; } playerRoot.SetPositionAndRotation(target.spawnPoint.position, target.spawnPoint.rotation); if (hadController) { controller.enabled = true; } } target.onEnter?.Invoke(); ShowRoomEnterMessage(roomData); Debug.Log($"[{nameof(RoomEnterHandler)}] 방 입장: {roomData.RoomName}"); } private RoomTarget FindTarget(string roomId) { return roomTargets.Find(target => target != null && target.roomId == roomId); } private void ShowRoomEnterMessage(RoomData roomData) { if (roomData == null) return; if (roomEnterMessagePanel == null || roomEnterMessageText == null) return; if (roomEnterMessageRoot != null) { roomEnterMessageRoot.gameObject.SetActive(true); } PlaceRoomEnterMessageInFrontOfCamera(); string roomName = roomData.RoomName; roomEnterMessageText.text = string.Format(roomEnterMessageFormat, roomName); roomEnterMessagePanel.SetActive(true); if (roomEnterMessageCoroutine != null) { StopCoroutine(roomEnterMessageCoroutine); } roomEnterMessageCoroutine = StartCoroutine(HideRoomEnterMessageAfterDelay()); } private void PlaceRoomEnterMessageInFrontOfCamera() { if (!placeMessageInFrontOfCamera) return; if (roomEnterMessageRoot == null) return; if (messageCameraTarget == null) { if (Camera.main != null) { messageCameraTarget = Camera.main.transform; } else { return; } } // 카메라가 아래를 보고 있어도 메시지가 바닥으로 들어가지 않게 // 카메라 forward에서 Y값을 제거해서 수평 방향만 사용 Vector3 flatForward = messageCameraTarget.forward; flatForward.y = 0f; if (flatForward.sqrMagnitude < 0.001f) { flatForward = messageCameraTarget.parent != null ? messageCameraTarget.parent.forward : Vector3.forward; flatForward.y = 0f; } flatForward.Normalize(); Vector3 targetPosition = messageCameraTarget.position + flatForward * messageDistance + Vector3.up * messageHeightOffset; roomEnterMessageRoot.position = targetPosition; Quaternion targetRotation = Quaternion.LookRotation(flatForward, Vector3.up); if (flipMessageRotation) { targetRotation *= Quaternion.Euler(0f, 180f, 0f); } roomEnterMessageRoot.rotation = targetRotation; } private IEnumerator HideRoomEnterMessageAfterDelay() { yield return new WaitForSeconds(roomEnterMessageDuration); if (roomEnterMessagePanel != null) { roomEnterMessagePanel.SetActive(false); } roomEnterMessageCoroutine = null; } }