Files
WhaleAdventure_VR/Assets/My project/RoomSelect/Scripts/RoomEnterHandler.cs
skrwns304@gmail.com b1e85a5b89 2026-06-19 UI, UI로직
2026-06-19 14:27:40 +09:00

266 lines
7.9 KiB
C#

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<RoomTarget> roomTargets = new List<RoomTarget>();
[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<CharacterController>();
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;
}
}