2026.06.24

This commit is contained in:
2026-06-24 17:13:40 +09:00
parent b1e85a5b89
commit d601161390
86 changed files with 10287 additions and 336 deletions

View File

@@ -1,34 +1,95 @@
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")]
[Tooltip("획득 조각 아이콘입니다. PieceSlot_01~05 안의 FilledIcon을 순서대로 넣으세요.")]
[Header("Piece Icons - Detail Panel")]
[Tooltip("상세 패널용 획득 조각 아이콘입니다. PieceSlot_01~05 안의 FilledIcon을 순서대로 넣으세요.")]
[SerializeField] private GameObject[] filledIcons;
[Tooltip("완성 또는 획득 연출용 Glow입니다. 없어도 됩니다.")]
[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("5개를 모두 모았을 때 켤 전체 완성 이펙트입니다. 없어도 됩니다.")]
[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()
{
if (titleText != null)
titleText.text = title;
SetTitle();
InitializeFlashIcons(false);
}
SetProgress(0, maxPieces);
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)
@@ -36,14 +97,85 @@ public void SetProgress(int current, int max)
maxPieces = Mathf.Max(1, max);
current = Mathf.Clamp(current, 0, maxPieces);
if (titleText != null)
titleText.text = title;
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<MemoryProgressManager>();
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<MemoryProgressManager>();
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)
@@ -74,9 +206,132 @@ private void UpdatePieceIcons(int 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
}