Files
WhaleAdventure_VR/Assets/My project/maze Scripts/Ui/MazeUIManager.cs
2026-06-24 17:13:40 +09:00

618 lines
20 KiB
C#

using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[DisallowMultipleComponent]
public class MazeUIManager : MonoBehaviour
{
public enum CompassUIState
{
None,
Available,
Used
}
[Header("HUD")]
[SerializeField] private GameObject hudPanel;
[SerializeField] private TextMeshProUGUI objectiveText;
[SerializeField] private TextMeshProUGUI memoryPieceText;
[SerializeField] private TextMeshProUGUI compassStateText;
[Header("Compass UI")]
[SerializeField] private Button compassButton;
[SerializeField] private TextMeshProUGUI compassButtonText;
[Header("Icon HUD / Socket Overlay")]
[Tooltip("기억의 조각 홈 안에 들어가는 파란 수정 아이콘입니다. 홈 이미지는 연결하지 않아도 됩니다.")]
[SerializeField] private Image memoryCrystalIcon;
[Tooltip("나침반 버튼 안의 나침반 이미지입니다. 사용 가능/완료 색상만 바꿉니다.")]
[SerializeField] private Image compassIcon;
[Tooltip("나침반 사용 가능 상태를 알려주는 작은 느낌표 아이콘입니다.")]
[SerializeField] private GameObject hintAlertIcon;
[Tooltip("체크포인트 홈 위에 얹는 별 아이콘들입니다. 1번 체크포인트부터 순서대로 넣으세요.")]
[SerializeField] private Image[] checkpointStarIcons;
[Tooltip("체크하면 미도달 별을 끄고, 소켓 홈만 보이게 합니다.")]
[SerializeField] private bool hideInactiveCheckpointStars = true;
[Tooltip("체크하면 기억의 조각 미획득 시 수정 아이콘을 끄고, 빈 홈만 보이게 합니다.")]
[SerializeField] private bool hideMemoryCrystalWhenMissing = true;
[Tooltip("나침반 사용 가능 상태에서 느낌표 아이콘을 켭니다.")]
[SerializeField] private bool showHintAlertWhenCompassAvailable = true;
[Header("Guide Message UI")]
[SerializeField] private GameObject guidePanel;
[SerializeField] private CanvasGroup guideCanvasGroup;
[SerializeField] private TextMeshProUGUI guideText;
[SerializeField] private float guideShowTime = 2.5f;
[SerializeField] private float fadeTime = 0.25f;
[Header("Result UI")]
[SerializeField] private GameObject resultPanel;
[SerializeField] private TextMeshProUGUI resultTitleText;
[SerializeField] private TextMeshProUGUI resultBodyText;
[SerializeField] private Button retryButton;
[SerializeField] private Button exitButton;
[SerializeField] private TextMeshProUGUI retryButtonText;
[SerializeField] private TextMeshProUGUI exitButtonText;
[Header("Result Placement")]
[Tooltip("연결하면 결과창을 켜는 순간 카메라 앞에 즉시 배치합니다.")]
[SerializeField] private MazeMixedUIPanelFollower panelFollower;
[SerializeField] private bool placeResultInFrontWhenShown = true;
[Header("Result Button Text")]
[SerializeField] private string retryButtonLabel = "다시하기";
[SerializeField] private string exitButtonLabel = "계속하기";
[Header("Result Reward UI")]
[Tooltip("기억의 조각 뒤에 깔리는 푸른 빛 효과 오브젝트입니다. 없어도 됩니다.")]
[SerializeField] private GameObject resultRewardGlow;
[Tooltip("결과창 중앙 보상 홈 안에 들어가는 기억의 조각 아이콘입니다. 결과창용 소켓 이미지는 연결하지 않아도 됩니다.")]
[SerializeField] private Image resultMemoryCrystalIcon;
[Tooltip("결과창 체크포인트 홈 위에 얹는 별 아이콘들입니다. 1번부터 순서대로 넣으세요.")]
[SerializeField] private Image[] resultCheckpointStarIcons;
[Tooltip("체크하면 결과창의 미도달 별은 끄고, 결과창 체크포인트 홈만 보이게 합니다.")]
[SerializeField] private bool hideInactiveResultCheckpointStars = true;
[Tooltip("체크하면 결과창에서 기억의 조각을 획득하지 못한 상태일 때 수정 아이콘을 끕니다.")]
[SerializeField] private bool hideResultMemoryCrystalWhenMissing = true;
[Header("Current Maze Text")]
[SerializeField] private string startObjectiveMessage = "목표: 나침반의 발자국 힌트를 따라 미로 출구를 찾아라";
[SerializeField] private string memoryMissingMessage = "기억의 조각: 미획득";
[SerializeField] private string memoryCollectedMessage = "기억의 조각: 획득";
[Header("Result Text")]
[SerializeField] private string successResultTitleMessage = "기억의 조각을 되찾았다!";
[SerializeField] private string successResultBodyMessage = "별빛 발자국을 따라 미로를 통과했습니다.";
[SerializeField] private string failResultTitleMessage = "미로 탐험 실패";
[SerializeField] private string failResultBodyMessage = "다시 도전해 보세요.";
[Header("UI Colors")]
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private Color successColor = Color.green;
[SerializeField] private Color failColor = Color.red;
[SerializeField] private Color disabledColor = Color.gray;
[Header("Icon Colors")]
[SerializeField] private Color iconActiveColor = Color.white;
[SerializeField] private Color iconInactiveColor = new Color(1f, 1f, 1f, 0.25f);
[Header("Result Colors")]
[SerializeField] private Color resultSuccessTitleColor = new Color(1f, 0.78f, 0.36f, 1f);
[SerializeField] private Color resultFailTitleColor = new Color(1f, 0.35f, 0.35f, 1f);
[Header("Button Events")]
public UnityEvent OnCompassButtonClicked = new UnityEvent();
public UnityEvent OnRetryButtonClicked = new UnityEvent();
public UnityEvent OnExitButtonClicked = new UnityEvent();
private Coroutine guideRoutine;
private void Awake()
{
EnsureEvents();
if (compassButton != null)
compassButton.onClick.AddListener(HandleCompassButtonClicked);
if (retryButton != null)
retryButton.onClick.AddListener(HandleRetryButtonClicked);
if (exitButton != null)
exitButton.onClick.AddListener(HandleExitButtonClicked);
HideResult();
HideGuideImmediately();
}
private void EnsureEvents()
{
if (OnCompassButtonClicked == null)
OnCompassButtonClicked = new UnityEvent();
if (OnRetryButtonClicked == null)
OnRetryButtonClicked = new UnityEvent();
if (OnExitButtonClicked == null)
OnExitButtonClicked = new UnityEvent();
}
private void OnDestroy()
{
if (compassButton != null)
compassButton.onClick.RemoveListener(HandleCompassButtonClicked);
if (retryButton != null)
retryButton.onClick.RemoveListener(HandleRetryButtonClicked);
if (exitButton != null)
exitButton.onClick.RemoveListener(HandleExitButtonClicked);
}
public void HandleCompassButtonClicked()
{
OnCompassButtonClicked?.Invoke();
}
public void HandleRetryButtonClicked()
{
OnRetryButtonClicked?.Invoke();
}
public void HandleExitButtonClicked()
{
OnExitButtonClicked?.Invoke();
}
public void ShowHUD()
{
if (hudPanel != null)
hudPanel.SetActive(true);
}
public void HideHUD()
{
if (hudPanel != null)
hudPanel.SetActive(false);
}
public void SetObjectiveSearchingMemory()
{
SetObjective(startObjectiveMessage);
}
public void SetObjectiveReturningToExit()
{
SetObjective("목표: 출구로 이동해 기억의 조각을 획득하라");
}
public void SetStartObjective()
{
SetObjective(startObjectiveMessage);
}
public void SetObjective(string message)
{
if (objectiveText != null)
objectiveText.text = message;
}
public void SetMemoryPieceState(bool hasMemoryPiece)
{
if (memoryPieceText != null)
{
if (hasMemoryPiece)
{
memoryPieceText.text = memoryCollectedMessage;
memoryPieceText.color = successColor;
}
else
{
memoryPieceText.text = memoryMissingMessage;
memoryPieceText.color = normalColor;
}
}
if (memoryCrystalIcon != null)
{
if (hideMemoryCrystalWhenMissing)
memoryCrystalIcon.gameObject.SetActive(hasMemoryPiece);
else
{
memoryCrystalIcon.gameObject.SetActive(true);
memoryCrystalIcon.color = hasMemoryPiece ? iconActiveColor : iconInactiveColor;
}
}
}
public void SetCheckpointProgress(int reachedCount, int totalCount)
{
SetStarProgress(checkpointStarIcons, reachedCount, totalCount, hideInactiveCheckpointStars);
}
public void SetHintAlertVisible(bool visible)
{
if (hintAlertIcon != null)
hintAlertIcon.SetActive(visible);
}
public void SetCompassState(CompassUIState state)
{
switch (state)
{
case CompassUIState.None:
SetCompassNone();
break;
case CompassUIState.Available:
SetCompassAvailable();
break;
case CompassUIState.Used:
SetCompassUsed();
break;
}
}
public void SetCompassNone()
{
if (compassStateText != null)
{
compassStateText.text = "나침반: 없음";
compassStateText.color = disabledColor;
}
if (compassButtonText != null)
compassButtonText.text = "나침반 없음";
if (compassButton != null)
compassButton.interactable = false;
if (compassIcon != null)
compassIcon.color = iconInactiveColor;
SetHintAlertVisible(false);
}
public void SetCompassAvailable()
{
if (compassStateText != null)
{
compassStateText.text = "나침반: 사용 가능";
compassStateText.color = normalColor;
}
if (compassButtonText != null)
compassButtonText.text = "나침반 사용";
if (compassButton != null)
compassButton.interactable = true;
if (compassIcon != null)
compassIcon.color = iconActiveColor;
SetHintAlertVisible(showHintAlertWhenCompassAvailable);
}
public void SetCompassUsed()
{
if (compassStateText != null)
{
compassStateText.text = "나침반: 사용 완료";
compassStateText.color = disabledColor;
}
if (compassButtonText != null)
compassButtonText.text = "사용 완료";
if (compassButton != null)
compassButton.interactable = false;
if (compassIcon != null)
compassIcon.color = iconInactiveColor;
SetHintAlertVisible(false);
}
public void ShowMessage(string message)
{
ShowGuideMessage(message);
}
public void ShowGuideMessage(string message)
{
if (guideRoutine != null)
StopCoroutine(guideRoutine);
guideRoutine = StartCoroutine(GuideMessageRoutine(message));
}
private IEnumerator GuideMessageRoutine(string message)
{
if (guidePanel != null)
guidePanel.SetActive(true);
if (guideText != null)
{
guideText.text = message;
guideText.color = normalColor;
}
if (guideCanvasGroup != null)
{
yield return FadeCanvasGroup(guideCanvasGroup, 0f, 1f, fadeTime);
yield return new WaitForSeconds(guideShowTime);
yield return FadeCanvasGroup(guideCanvasGroup, 1f, 0f, fadeTime);
}
else
{
yield return new WaitForSeconds(guideShowTime);
}
if (guidePanel != null)
guidePanel.SetActive(false);
guideRoutine = null;
}
private IEnumerator FadeCanvasGroup(CanvasGroup canvasGroup, float from, float to, float duration)
{
if (canvasGroup == null)
yield break;
if (duration <= 0f)
{
canvasGroup.alpha = to;
yield break;
}
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / duration);
canvasGroup.alpha = Mathf.Lerp(from, to, t);
yield return null;
}
canvasGroup.alpha = to;
}
public void ShowSuccessResult(int reachedCheckpointCount, int totalCheckpointCount)
{
ShowSuccessResult(successResultTitleMessage, successResultBodyMessage, true, reachedCheckpointCount, totalCheckpointCount);
}
public void ShowSuccessResult(string titleMessage, string bodyMessage, bool showMemoryPieceReward, int reachedCheckpointCount, int totalCheckpointCount)
{
ShowResult(titleMessage, bodyMessage, resultSuccessTitleColor, showMemoryPieceReward, reachedCheckpointCount, totalCheckpointCount);
}
public void SetResultMemoryPieceState(bool hasMemoryPiece)
{
if (resultRewardGlow != null)
resultRewardGlow.SetActive(hasMemoryPiece);
if (resultMemoryCrystalIcon != null)
{
if (hideResultMemoryCrystalWhenMissing)
resultMemoryCrystalIcon.gameObject.SetActive(hasMemoryPiece);
else
{
resultMemoryCrystalIcon.gameObject.SetActive(true);
resultMemoryCrystalIcon.color = hasMemoryPiece ? iconActiveColor : iconInactiveColor;
}
}
}
public void SetResultCheckpointProgress(int reachedCount, int totalCount)
{
SetStarProgress(resultCheckpointStarIcons, reachedCount, totalCount, hideInactiveResultCheckpointStars);
}
public void ShowSuccess()
{
ShowSuccess(successResultTitleMessage, successResultBodyMessage);
}
public void ShowSuccess(string bodyMessage)
{
ShowSuccess(successResultTitleMessage, bodyMessage);
}
public void ShowSuccess(string titleMessage, string bodyMessage)
{
ShowResult(titleMessage, bodyMessage, resultSuccessTitleColor, true, -1, -1);
}
public void ShowFail()
{
ShowFail(failResultBodyMessage);
}
public void ShowFail(string bodyMessage)
{
ShowResult(failResultTitleMessage, bodyMessage, resultFailTitleColor, false, 0, GetResultStarIconCount());
}
private void ShowResult(string titleMessage, string bodyMessage, Color titleColor, bool showMemoryPieceReward, int reachedCheckpointCount, int totalCheckpointCount)
{
HideHUD();
HideGuideImmediately();
SetResultButtonLabels();
if (resultPanel != null)
resultPanel.SetActive(true);
if (placeResultInFrontWhenShown && panelFollower != null)
panelFollower.PlaceResultInFrontNow();
if (resultTitleText != null)
{
resultTitleText.text = titleMessage;
resultTitleText.color = titleColor;
}
if (resultBodyText != null)
resultBodyText.text = bodyMessage;
SetResultMemoryPieceState(showMemoryPieceReward);
if (reachedCheckpointCount >= 0 && totalCheckpointCount >= 0)
SetResultCheckpointProgress(reachedCheckpointCount, totalCheckpointCount);
}
public void SetResultButtonLabels()
{
if (retryButtonText != null)
retryButtonText.text = retryButtonLabel;
if (exitButtonText != null)
exitButtonText.text = exitButtonLabel;
}
public void HideResult()
{
if (resultPanel != null)
resultPanel.SetActive(false);
SetResultMemoryPieceState(false);
SetResultCheckpointProgress(0, GetResultStarIconCount());
}
public void HideReward()
{
HideResult();
}
public void HideGuideImmediately()
{
if (guideRoutine != null)
{
StopCoroutine(guideRoutine);
guideRoutine = null;
}
if (guideCanvasGroup != null)
guideCanvasGroup.alpha = 0f;
if (guidePanel != null)
guidePanel.SetActive(false);
}
public void ResetUI(bool hasCompass)
{
ShowHUD();
HideResult();
HideGuideImmediately();
SetStartObjective();
SetMemoryPieceState(false);
int checkpointIconCount = checkpointStarIcons != null ? checkpointStarIcons.Length : 0;
SetCheckpointProgress(0, checkpointIconCount);
if (hasCompass)
SetCompassAvailable();
else
SetCompassNone();
}
public void OnMemoryPieceCollected()
{
SetMemoryPieceState(true);
SetObjectiveReturningToExit();
ShowGuideMessage("기억의 조각을 찾았다!\n이제 출구로 돌아가자.");
}
public void OnCompassUsedToMemory()
{
SetCompassUsed();
ShowGuideMessage("나침반이 기억의 조각 방향을 가리킨다.\n빛나는 발자국을 따라가 보자.");
}
public void OnCompassUsedToExit()
{
SetCompassUsed();
ShowGuideMessage("나침반이 출구 방향을 가리킨다.\n발자국을 따라 출구로 돌아가자.");
}
public void OnCompassUsedToNextPath()
{
SetCompassUsed();
ShowGuideMessage("나침반이 다음 길을 가리킨다.\n빛나는 발자국을 따라가 보자.");
}
public void OnCompassMissing()
{
ShowGuideMessage("낡은 나침반이 없다.");
}
public void OnCompassAlreadyUsed()
{
ShowGuideMessage("나침반은 이미 사용했다.");
}
private void SetStarProgress(Image[] starIcons, int reachedCount, int totalCount, bool hideInactiveStars)
{
if (starIcons == null)
return;
int safeTotal = Mathf.Clamp(totalCount, 0, starIcons.Length);
int safeReached = Mathf.Clamp(reachedCount, 0, safeTotal);
for (int i = 0; i < starIcons.Length; i++)
{
Image starIcon = starIcons[i];
if (starIcon == null)
continue;
bool slotUsed = i < safeTotal;
bool reached = i < safeReached;
if (!slotUsed)
{
starIcon.gameObject.SetActive(false);
continue;
}
if (hideInactiveStars)
{
starIcon.gameObject.SetActive(reached);
if (reached)
starIcon.color = iconActiveColor;
}
else
{
starIcon.gameObject.SetActive(true);
starIcon.color = reached ? iconActiveColor : iconInactiveColor;
}
}
}
private int GetResultStarIconCount()
{
return resultCheckpointStarIcons != null ? resultCheckpointStarIcons.Length : 0;
}
#if UNITY_EDITOR
private void OnValidate()
{
guideShowTime = Mathf.Max(0f, guideShowTime);
fadeTime = Mathf.Max(0f, fadeTime);
if (checkpointStarIcons != null && checkpointStarIcons.Length == 0)
Debug.LogWarning("MazeUIManager: HUD Checkpoint Star Icons 배열이 비어 있습니다. HUD 체크포인트 별 아이콘을 연결하세요.", this);
if (resultCheckpointStarIcons != null && resultCheckpointStarIcons.Length == 0)
Debug.LogWarning("MazeUIManager: Result Checkpoint Star Icons 배열이 비어 있습니다. 결과창 체크포인트 별 아이콘을 연결하세요.", this);
if (placeResultInFrontWhenShown && panelFollower == null)
Debug.LogWarning("MazeUIManager: Place Result In Front When Shown이 켜져 있지만 Panel Follower가 비어 있습니다. 결과창 위치가 한 프레임 늦게 잡힐 수 있습니다.", this);
}
#endif
}