using System.Collections; using TMPro; using UnityEngine; [DisallowMultipleComponent] public class MemoryProgressUI : MonoBehaviour { [Header("Reference")] [SerializeField] private MemoryProgressManager memoryProgressManager; [SerializeField] private bool autoFindManager = true; [SerializeField] private bool registerOnEnable = true; [Tooltip("매니저를 찾지 못했을 때만 0/max로 초기 표시합니다.")] [SerializeField] private bool initializeEmptyWhenNoManager = true; [Header("Text")] [SerializeField] private TMP_Text titleText; [SerializeField] private TMP_Text countText; [Header("Piece Icons - Detail Panel")] [Tooltip("상세 패널용 획득 조각 아이콘입니다. PieceSlot_01~05 안의 FilledIcon을 순서대로 넣으세요.")] [SerializeField] private GameObject[] filledIcons; [Tooltip("선택 사항입니다. 획득 후 계속 켜둘 정적 Glow 이미지입니다. 사용하지 않으려면 비워두세요.")] [SerializeField] private GameObject[] glowIcons; [Header("Fill Flash - One Shot")] [Tooltip("조각이 새로 채워지는 순간 잠깐 켰다가 자동으로 끄는 효과입니다. PieceSlot_01~05 안의 FillFlashIcon을 순서대로 넣으세요.")] [SerializeField] private GameObject[] fillFlashIcons; [Tooltip("조각이 새로 채워질 때 FillFlashIcon을 잠깐 표시합니다.")] [SerializeField] private bool useFillFlash = true; [Tooltip("FillFlashIcon이 켜져 있는 시간입니다. 애니메이션 없이 SetActive만 사용합니다.")] [SerializeField] private float fillFlashShowTime = 0.5f; [Tooltip("SetProgress가 처음 호출될 때는 기존 진행도를 복구하는 상황일 수 있으므로 Flash를 재생하지 않습니다.")] [SerializeField] private bool skipFlashOnFirstUpdate = true; [Header("Single Icon State - Mini Button")] [Tooltip("미니 버튼처럼 아이콘 1개만 상태 제어할 때 켭니다. 상세 패널에서는 꺼두세요.")] [SerializeField] private bool useSingleIconState = false; [Tooltip("0개일 때 켤 아이콘입니다. 예: 어두운 기억 조각. 소켓을 항상 보이게 둘 거면 비워둬도 됩니다.")] [SerializeField] private GameObject emptyStateIcon; [Tooltip("1개 이상일 때 켤 아이콘입니다. 예: MiniCrystalIcon.")] [SerializeField] private GameObject activeStateIcon; [Tooltip("진행도 상태에 따라 켤 정적 Glow 이미지입니다. 예: MiniGlowIcon.")] [SerializeField] private GameObject singleStateGlowIcon; [Tooltip("체크하면 완료 시에만 미니 Glow가 켜집니다. 끄면 1개 이상 획득 시 켜집니다.")] [SerializeField] private bool singleStateGlowOnlyWhenCompleted = true; [Header("Optional Objects")] [Tooltip("모든 조각을 모았을 때 켤 정적 완료 표시 오브젝트입니다. 없어도 됩니다.")] [SerializeField] private GameObject completedEffect; [Header("Settings")] [SerializeField] private int maxPieces = 5; [SerializeField] private string title = "기억의 조각"; [SerializeField] private bool showCountText = true; [Tooltip("상세 패널용 Glow Icons에만 적용됩니다.")] [SerializeField] private bool showGlowOnlyWhenCompleted = true; private bool registered; private bool hasProgressSnapshot; private int lastCurrentPieces; private Coroutine[] flashRoutines; private void Awake() { SetTitle(); InitializeFlashIcons(false); } private void OnEnable() { if (!registerOnEnable) { RefreshFromManager(); return; } TryRegisterToManager(); } private void OnDisable() { if (registered && memoryProgressManager != null) memoryProgressManager.UnregisterUI(this); registered = false; StopAllFlashRoutines(); InitializeFlashIcons(false); } public void SetProgress(int current, int max) { maxPieces = Mathf.Max(1, max); current = Mathf.Clamp(current, 0, maxPieces); int previous = lastCurrentPieces; bool shouldPlayFlash = useFillFlash && hasProgressSnapshot && current > previous && (!skipFlashOnFirstUpdate || hasProgressSnapshot); SetTitle(); if (countText != null) { countText.gameObject.SetActive(showCountText); countText.text = $"{current} / {maxPieces}"; } UpdatePieceIcons(current); UpdateSingleStateIcons(current); UpdateCompletedEffect(current >= maxPieces); if (shouldPlayFlash) PlayFillFlash(previous, current); lastCurrentPieces = current; hasProgressSnapshot = true; } public void RefreshFromManager() { if (memoryProgressManager == null && autoFindManager) memoryProgressManager = FindFirstObjectByType(); if (memoryProgressManager != null) SetProgress(memoryProgressManager.CurrentPieces, memoryProgressManager.RequiredPieces); else if (initializeEmptyWhenNoManager) SetProgress(0, maxPieces); } public void SetMemoryProgressManager(MemoryProgressManager manager) { if (registered && memoryProgressManager != null) memoryProgressManager.UnregisterUI(this); memoryProgressManager = manager; registered = false; hasProgressSnapshot = false; if (isActiveAndEnabled && registerOnEnable) TryRegisterToManager(); else RefreshFromManager(); } public void ResetProgressSnapshot() { hasProgressSnapshot = false; lastCurrentPieces = 0; StopAllFlashRoutines(); InitializeFlashIcons(false); } private void TryRegisterToManager() { if (memoryProgressManager == null && autoFindManager) memoryProgressManager = FindFirstObjectByType(); if (memoryProgressManager != null) { memoryProgressManager.RegisterUI(this); registered = true; } else if (initializeEmptyWhenNoManager) { SetProgress(0, maxPieces); } } private void SetTitle() { if (titleText != null) titleText.text = title; } private void UpdatePieceIcons(int current) { if (filledIcons != null) { for (int i = 0; i < filledIcons.Length; i++) { if (filledIcons[i] != null) filledIcons[i].SetActive(i < current); } } if (glowIcons == null) return; bool completed = current >= maxPieces; for (int i = 0; i < glowIcons.Length; i++) { if (glowIcons[i] == null) continue; if (showGlowOnlyWhenCompleted) glowIcons[i].SetActive(completed); else glowIcons[i].SetActive(i < current); } } private void UpdateSingleStateIcons(int current) { if (!useSingleIconState) return; bool hasAnyPiece = current > 0; bool completed = current >= maxPieces; if (emptyStateIcon != null) emptyStateIcon.SetActive(!hasAnyPiece); if (activeStateIcon != null) activeStateIcon.SetActive(hasAnyPiece); if (singleStateGlowIcon != null) { bool showGlow = singleStateGlowOnlyWhenCompleted ? completed : hasAnyPiece; singleStateGlowIcon.SetActive(showGlow); } } private void UpdateCompletedEffect(bool completed) { if (completedEffect != null) completedEffect.SetActive(completed); } private void PlayFillFlash(int previousCurrent, int newCurrent) { if (fillFlashIcons == null || fillFlashIcons.Length == 0) return; EnsureFlashRoutineArray(); int start = Mathf.Clamp(previousCurrent, 0, fillFlashIcons.Length); int end = Mathf.Clamp(newCurrent, 0, fillFlashIcons.Length); for (int i = start; i < end; i++) { if (fillFlashIcons[i] == null) continue; if (flashRoutines[i] != null) StopCoroutine(flashRoutines[i]); flashRoutines[i] = StartCoroutine(FlashRoutine(i)); } } private IEnumerator FlashRoutine(int index) { GameObject target = fillFlashIcons[index]; if (target == null) yield break; target.SetActive(true); if (fillFlashShowTime > 0f) yield return new WaitForSeconds(fillFlashShowTime); if (target != null) target.SetActive(false); if (flashRoutines != null && index >= 0 && index < flashRoutines.Length) flashRoutines[index] = null; } private void EnsureFlashRoutineArray() { int length = fillFlashIcons == null ? 0 : fillFlashIcons.Length; if (flashRoutines == null || flashRoutines.Length != length) flashRoutines = new Coroutine[length]; } private void StopAllFlashRoutines() { if (flashRoutines == null) return; for (int i = 0; i < flashRoutines.Length; i++) { if (flashRoutines[i] != null) { StopCoroutine(flashRoutines[i]); flashRoutines[i] = null; } } } private void InitializeFlashIcons(bool active) { if (fillFlashIcons == null) return; for (int i = 0; i < fillFlashIcons.Length; i++) { if (fillFlashIcons[i] != null) fillFlashIcons[i].SetActive(active); } } #if UNITY_EDITOR private void OnValidate() { maxPieces = Mathf.Max(1, maxPieces); fillFlashShowTime = Mathf.Max(0f, fillFlashShowTime); if (filledIcons != null && filledIcons.Length > 0 && filledIcons.Length != maxPieces) { Debug.LogWarning( $"MemoryProgressUI: Filled Icons 개수({filledIcons.Length})와 Max Pieces({maxPieces})가 다릅니다. 미니 버튼 아이콘 1개 제어는 Filled Icons가 아니라 Single Icon State를 사용하세요.", this ); } if (glowIcons != null && glowIcons.Length > 0 && filledIcons != null && filledIcons.Length > 0 && glowIcons.Length != filledIcons.Length) { Debug.LogWarning("MemoryProgressUI: Glow Icons 개수와 Filled Icons 개수가 다릅니다.", this); } if (fillFlashIcons != null && fillFlashIcons.Length > 0 && filledIcons != null && filledIcons.Length > 0 && fillFlashIcons.Length != filledIcons.Length) { Debug.LogWarning("MemoryProgressUI: Fill Flash Icons 개수와 Filled Icons 개수가 다릅니다.", this); } } #endif }