Projext/Assets/02_Scripts/Level_Scripts/LevelUpUIManager.cs

304 lines
20 KiB
C#
Raw Normal View History

2026-02-25 10:39:20 +00:00
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
using System.Collections.Generic; // 리스트를 사용할거에요 -> System.Collections.Generic을
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
using UnityEngine.UI; // 버튼 제어를 위해 불러올거에요 -> UnityEngine.UI를
using GridLayout = UnityEngine.UI.GridLayoutGroup; // GridLayoutGroup을 짧게 쓰기 위해 별칭 선언
2026-02-25 10:39:20 +00:00
public class LevelUpUIManager : MonoBehaviour // 클래스를 선언할거에요 -> 레벨업 UI를 관리하는 LevelUpUIManager를
2026-01-29 06:58:38 +00:00
{
2026-02-25 10:39:20 +00:00
[SerializeField] private GameObject panel; // 변수를 선언할거에요 -> 카드 선택 패널을
[SerializeField] private CardUI[] cardPrefabs; // 배열을 선언할거에요 -> 카드 UI 프리팹들을
[SerializeField] private Transform cardParent; // 변수를 선언할거에요 -> 카드가 생성될 부모 위치를
[SerializeField] private List<CardData> cardPool; // 리스트를 선언할거에요 -> 카드 데이터 풀을
2026-01-29 06:58:38 +00:00
2026-02-25 10:39:20 +00:00
[Header("--- 확정 버튼 설정 ---")] // 인스펙터 제목을 달거에요 -> 확정 버튼을
[SerializeField] private Button applyButton; // 변수를 선언할거에요 -> Apply 버튼을
[Header("--- 자동 탐색 이름 (폴백 모드용) ---")] // 인스펙터 제목을 달거에요 -> 자동 탐색 설정을
[Tooltip("카드 선택 패널 오브젝트 이름 (자동 탐색용)")] // 툴팁을 달거에요 -> 설명을
[SerializeField] private string panelName = "LevelUPPanel"; // 변수를 선언할거에요 -> 패널 오브젝트 이름을
[Tooltip("카드가 생성될 부모 오브젝트 이름 (자동 탐색용)")] // 툴팁을 달거에요 -> 설명을
[SerializeField] private string cardParentName = "LevelParent"; // 변수를 선언할거에요 -> 카드 부모 오브젝트 이름을 (실제 프리팹 이름: LevelParent)
[Tooltip("Apply 버튼 오브젝트 이름 (자동 탐색용)")] // 툴팁을 달거에요 -> 설명을
[SerializeField] private string applyButtonName = "Apply Button"; // 변수를 선언할거에요 -> Apply 버튼 오브젝트 이름을 (실제 프리팹 이름: Apply Button)
2026-02-25 10:39:20 +00:00
private CardUI _selectedCardUI; // 변수를 선언할거에요 -> 현재 선택된 카드를
private void Awake() // 함수를 실행할거에요 -> 초기화 시 자동 탐색을
{
AutoFindReferences(); // 실행할거에요 -> 비어있는 참조를 이름으로 자동 탐색
}
2026-02-25 10:39:20 +00:00
private void OnEnable() // 함수를 실행할거에요 -> 활성화 시
2026-01-29 06:58:38 +00:00
{
2026-02-25 10:39:20 +00:00
PlayerLevelSystem.OnLevelUp += Show; // 구독할거에요 -> 레벨업 이벤트를
if (applyButton != null) applyButton.onClick.AddListener(OnApplyButtonClick); // 연결할거에요 -> 버튼 클릭을
2026-01-29 06:58:38 +00:00
}
2026-02-25 10:39:20 +00:00
private void OnDisable() // 함수를 실행할거에요 -> 비활성화 시
2026-01-29 06:58:38 +00:00
{
2026-02-25 10:39:20 +00:00
PlayerLevelSystem.OnLevelUp -= Show; // 해제할거에요 -> 레벨업 이벤트를
if (applyButton != null) applyButton.onClick.RemoveListener(OnApplyButtonClick); // 해제할거에요 -> 버튼 클릭을
2026-01-29 06:58:38 +00:00
}
2026-02-25 10:39:20 +00:00
void Show() // 함수를 선언할거에요 -> 카드 선택 UI를 표시하는 Show를
2026-01-29 06:58:38 +00:00
{
// 참조가 끊어졌을 수 있으니 한번 더 탐색 시도
if (panel == null || cardParent == null || applyButton == null) AutoFindReferences(); // 재탐색할거에요 -> 참조가 하나라도 비어있으면
if (panel == null) // 방어할거에요 -> 패널이 비어있으면 크래시 방지
{
Debug.LogWarning("[LevelUpUIManager] panel이 연결되지 않았습니다! Inspector에서 LevelUPPanel을 연결하세요."); // 경고를 찍을거에요
return; // 중단할거에요 -> 나머지 로직 실행 방지
}
if (cardParent == null) // 방어할거에요 -> 카드 부모가 비어있으면 크래시 방지
{
Debug.LogWarning("[LevelUpUIManager] cardParent가 연결되지 않았습니다!"); // 경고를 찍을거에요
return; // 중단할거에요
}
2026-02-25 10:39:20 +00:00
panel.SetActive(true); // 켤거에요 -> 패널을
Time.timeScale = 0f; // 멈출거에요 -> 게임 시간을 (카드 선택 중 일시정지)
// ── [버그 수정] 보스 씬에서 카드 클릭 안 되는 문제 ──────
// 원인: 보스 체력바 Canvas 또는 BossAttackIndicator Canvas가
// 더 높은 sortingOrder로 위에 올라와 클릭을 가로챔
// 해결: 레벨업 패널이 속한 Canvas를 최상위 sortingOrder로 올려서
// 어떤 Canvas보다도 앞에 오게 강제 설정
Canvas panelCanvas = panel.GetComponentInParent<Canvas>(); // 찾을거에요 -> 패널이 속한 루트 Canvas를
if (panelCanvas != null) // 조건이 맞으면 실행할거에요 -> Canvas가 있으면
{
panelCanvas.overrideSorting = true; // 켤거에요 -> 정렬 순서 재정의를
panelCanvas.sortingOrder = 999; // 올릴거에요 -> 가장 높은 순서로 (다른 모든 Canvas 위에)
Debug.Log($"[LevelUpUIManager] Canvas sortingOrder → 999 (보스 UI 위에 올리기)"); // 로그를 찍을거에요
}
2026-01-29 06:58:38 +00:00
2026-02-25 10:39:20 +00:00
_selectedCardUI = null; // 초기화할거에요 -> 선택 카드를
if (applyButton != null) applyButton.interactable = false; // 비활성화할거에요 -> Apply 버튼을
2026-02-25 10:39:20 +00:00
foreach (Transform child in cardParent) // 반복할거에요 -> 기존 카드들을
Destroy(child.gameObject); // 파괴할거에요 -> 이전 카드를
2026-01-29 06:58:38 +00:00
2026-02-25 10:39:20 +00:00
int slotCount = 3; // [FIX] 값을 설정할거에요 -> 카드 개수를 3장으로
2026-01-29 06:58:38 +00:00
2026-02-02 10:51:54 +00:00
/* =========================
* 1 CardData ( )
* ========================= */
2026-02-25 10:39:20 +00:00
List<CardData> selectedCards = new List<CardData>(); // 리스트를 만들거에요 -> 선택된 카드 데이터를
List<CardData> tempDataPool = new List<CardData>(cardPool); // 리스트를 복사할거에요 -> 임시 풀을
2026-01-29 06:58:38 +00:00
2026-02-25 10:39:20 +00:00
for (int i = 0; i < slotCount && tempDataPool.Count > 0; i++) // 반복할거에요 -> 슬롯 수만큼
2026-01-29 06:58:38 +00:00
{
2026-02-25 10:39:20 +00:00
int idx = Random.Range(0, tempDataPool.Count); // 뽑을거에요 -> 랜덤 인덱스를
selectedCards.Add(tempDataPool[idx]); // 추가할거에요 -> 선택된 카드를
tempDataPool.RemoveAt(idx); // 제거할거에요 -> 중복 방지용으로
2026-01-29 06:58:38 +00:00
}
2026-02-02 10:51:54 +00:00
/* =========================
2026-02-25 10:39:20 +00:00
* 2 3
*
* A Health B Speed C Damage
* ( )
2026-02-02 10:51:54 +00:00
* ========================= */
2026-02-25 10:39:20 +00:00
List<StatType> mainStats = new List<StatType> // 리스트를 만들거에요 -> 전체 스탯 목록을
{
StatType.Health, // 추가할거에요 -> 체력을
StatType.Speed, // 추가할거에요 -> 속도를
StatType.Damage // 추가할거에요 -> 공격력을
};
ShuffleList(mainStats); // 섞을거에요 -> 배정 순서를 랜덤으로
2026-02-02 10:51:54 +00:00
2026-02-25 10:39:20 +00:00
/* =========================
* 3 CardUI + ( )
* ========================= */
List<CardUI> tempPrefabs = new List<CardUI>(cardPrefabs); // 리스트를 복사할거에요 -> 프리팹 풀을
for (int i = 0; i < selectedCards.Count && tempPrefabs.Count > 0; i++) // 반복할거에요 -> 선택된 카드 수만큼
2026-01-29 06:58:38 +00:00
{
2026-02-25 10:39:20 +00:00
int idx = Random.Range(0, tempPrefabs.Count); // 뽑을거에요 -> 랜덤 인덱스를
CardUI ui = Instantiate(tempPrefabs[idx], cardParent); // 생성할거에요 -> 카드 UI를
// ⭐ i번째 카드에 i번째 메인 스탯 배정 (3장이 절대 겹치지 않음)
StatType mainStat = i < mainStats.Count ? mainStats[i] : mainStats[0]; // 결정할거에요 -> 이 카드의 메인 버프 스탯을
ui.Setup(selectedCards[i], this, mainStat); // 설정할거에요 -> 카드 데이터 + 메인 스탯을
tempPrefabs.RemoveAt(idx); // 제거할거에요 -> 중복 방지용으로
2026-01-29 06:58:38 +00:00
}
}
2026-02-25 10:39:20 +00:00
public void OnCardClick(CardUI clickedUI) // 함수를 선언할거에요 -> 카드 클릭 처리를
{
2026-02-25 10:39:20 +00:00
if (_selectedCardUI != null) _selectedCardUI.SetSelected(false); // 해제할거에요 -> 이전 선택을
_selectedCardUI = clickedUI; // 저장할거에요 -> 새 선택을
_selectedCardUI.SetSelected(true); // 설정할거에요 -> 선택 상태를
2026-02-25 10:39:20 +00:00
if (applyButton != null) applyButton.interactable = true; // 활성화할거에요 -> Apply 버튼을
}
2026-02-25 10:39:20 +00:00
private void OnApplyButtonClick() // 함수를 선언할거에요 -> Apply 버튼 클릭 처리를
{
2026-02-25 10:39:20 +00:00
if (_selectedCardUI == null) return; // 조건이 맞으면 중단할거에요 -> 선택 없으면
_selectedCardUI.ApplyCurrentEffect(); // 실행할거에요 -> 카드 효과를
Close(); // 실행할거에요 -> UI 닫기를
}
2026-02-25 10:39:20 +00:00
public void Close() // 함수를 선언할거에요 -> UI 닫기를
2026-01-29 06:58:38 +00:00
{
2026-02-25 10:39:20 +00:00
Time.timeScale = 1f; // 복원할거에요 -> 게임 시간을
if (panel != null) panel.SetActive(false); // 끌거에요 -> 패널이 있으면 패널을
// ── [버그 수정] Canvas sortingOrder 원복 ─────────────
// Show()에서 999로 올렸던 sortingOrder를 원래대로 되돌림
// 닫은 후에도 999로 남아있으면 다른 UI가 가려질 수 있음
Canvas panelCanvas = panel != null ? panel.GetComponentInParent<Canvas>() : null; // 찾을거에요 -> 패널이 속한 Canvas를
if (panelCanvas != null) // 조건이 맞으면 실행할거에요 -> Canvas가 있으면
{
panelCanvas.overrideSorting = false; // 끌거에요 -> 정렬 순서 재정의를 (기본값 복원)
panelCanvas.sortingOrder = 0; // 복원할거에요 -> 기본 sortingOrder로
Debug.Log("[LevelUpUIManager] Canvas sortingOrder 원복 완료"); // 로그를 찍을거에요
}
}
// ─────────────────────────────────────────────────────────────
// 자동 탐색 — 폴백 모드에서 끊어진 참조를 이름으로 재연결
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 인스펙터 참조가 비어있으면 오브젝트 이름으로 자동 탐색합니다.
/// [Managers] 프리팹에서 폴백 생성 시 Player Canvas 내부 참조가 끊기는 문제를 해결합니다.
/// </summary>
private void AutoFindReferences() // 함수를 정의할거에요 -> 참조 자동 탐색을
{
// ── 1. panel (LevelUPPanel) 탐색 — 비활성 오브젝트도 찾기 ──
if (panel == null) // 조건이 맞으면 실행할거에요 -> 패널이 비어있으면
{
// ★ GameObject.Find()는 비활성 오브젝트를 못 찾으므로
// Canvas를 먼저 찾고 그 하위에서 이름으로 탐색합니다.
Canvas[] allCanvas = FindObjectsOfType<Canvas>(true); // 찾을거에요 -> 모든 Canvas를 (비활성 포함)
for (int i = 0; i < allCanvas.Length; i++) // 반복할거에요 -> 각 Canvas마다
{
Transform found = FindChildRecursive(allCanvas[i].transform, panelName); // 찾을거에요 -> Canvas 하위에서 패널을
if (found != null) // 조건이 맞으면 실행할거에요 -> 찾았다면
{
panel = found.gameObject; // 설정할거에요 -> 패널 참조를
Debug.Log($"[LevelUpUIManager] panel 자동 탐색 성공: {found.name} (Canvas: {allCanvas[i].name})"); // 로그를 찍을거에요
break; // 중단할거에요 -> 첫 번째 매칭으로 충분
}
}
}
// ── 2. cardParent 탐색 (★ 컴포넌트 기반: GridLayoutGroup으로 확실히 찾기) ──
if (cardParent == null && panel != null) // 조건이 맞으면 실행할거에요 -> 카드 부모가 비어있고 패널은 있으면
{
// 2-a. 이름으로 먼저 시도 (LevelParent, CardParent 둘 다 시도)
Transform found = FindChildRecursive(panel.transform, cardParentName); // 찾을거에요 -> 설정된 이름으로
if (found == null) found = FindChildRecursive(panel.transform, "LevelParent"); // 못 찾으면 -> LevelParent로 재시도
if (found == null) found = FindChildRecursive(panel.transform, "CardParent"); // 못 찾으면 -> CardParent로 재시도
// 2-b. 이름으로 못 찾았으면 GridLayoutGroup 컴포넌트로 찾기 (가장 확실한 방법)
if (found == null) // 조건이 맞으면 실행할거에요 -> 이름으로 못 찾았으면
{
GridLayout grid = panel.GetComponentInChildren<GridLayout>(true); // 찾을거에요 -> 패널 하위 GridLayoutGroup을 (비활성 포함)
if (grid != null) found = grid.transform; // 설정할거에요 -> GridLayout이 있는 오브젝트를 카드 부모로
}
if (found != null) // 조건이 맞으면 실행할거에요 -> 찾았다면
{
cardParent = found; // 설정할거에요 -> 카드 부모 참조를
Debug.Log($"[LevelUpUIManager] cardParent 자동 탐색 성공: {found.name}"); // 로그를 찍을거에요
}
else // 최종 폴백: 정말 못 찾았으면 panel 자체 사용
{
cardParent = panel.transform; // 설정할거에요 -> 패널을 카드 부모로 임시 사용
Debug.LogWarning("[LevelUpUIManager] cardParent를 찾지 못해 panel을 사용합니다."); // 경고를 찍을거에요
}
}
// ── 3. applyButton 탐색 (★ 컴포넌트 기반: panel 직속 자식 Button 우선) ──
if (applyButton == null && panel != null) // 조건이 맞으면 실행할거에요 -> 버튼이 비어있으면
{
// 3-a. 이름으로 먼저 시도 (Apply Button, ApplyButton 둘 다 시도)
Transform applyTransform = FindChildRecursive(panel.transform, applyButtonName); // 찾을거에요 -> 설정된 이름으로
if (applyTransform == null) applyTransform = FindChildRecursive(panel.transform, "Apply Button"); // 못 찾으면 -> 공백 포함으로 재시도
if (applyTransform == null) applyTransform = FindChildRecursive(panel.transform, "ApplyButton"); // 못 찾으면 -> 공백 없이 재시도
if (applyTransform != null) // 조건이 맞으면 실행할거에요 -> 이름으로 찾았다면
{
Button btn = applyTransform.GetComponent<Button>(); // 가져올거에요 -> Button 컴포넌트를
if (btn != null) applyButton = btn; // 설정할거에요 -> Apply 버튼 참조를
}
// 3-b. 이름으로 못 찾았으면 panel 직속 자식 중 Button 컴포넌트가 있는 것 찾기
if (applyButton == null) // 조건이 맞으면 실행할거에요 -> 아직 못 찾았으면
{
// panel의 직속 자식들만 검색 (카드 내부 버튼이 아닌 패널 레벨 버튼)
for (int i = 0; i < panel.transform.childCount; i++) // 반복할거에요 -> 패널 직속 자식마다
{
Transform child = panel.transform.GetChild(i); // 가져올거에요 -> i번째 자식을
Button btn = child.GetComponent<Button>(); // 가져올거에요 -> Button 컴포넌트를
if (btn != null) // 조건이 맞으면 실행할거에요 -> Button이 있으면
{
applyButton = btn; // 설정할거에요 -> Apply 버튼 참조를 (패널 직속 자식 Button = Apply 버튼)
Debug.Log($"[LevelUpUIManager] applyButton 컴포넌트 탐색 성공: {child.name}"); // 로그를 찍을거에요
break; // 중단할거에요
}
}
}
// 3-c. 그래도 못 찾았으면 panel 하위 전체에서 이름 부분 매칭
if (applyButton == null) // 조건이 맞으면 실행할거에요 -> 여전히 못 찾았으면
{
Button[] buttons = panel.GetComponentsInChildren<Button>(true); // 찾을거에요 -> 패널 하위 모든 버튼을 (비활성 포함)
for (int i = 0; i < buttons.Length; i++) // 반복할거에요 -> 각 버튼마다
{
string btnName = buttons[i].gameObject.name.ToLower(); // 변환할거에요 -> 이름을 소문자로
if (btnName.Contains("apply") || btnName.Contains("confirm") || btnName.Contains("ok")) // 조건이 맞으면
{
applyButton = buttons[i]; // 설정할거에요 -> Apply 버튼 참조를
break; // 중단할거에요
}
}
}
// 최종: 버튼을 찾았으면 리스너 등록
if (applyButton != null) // 조건이 맞으면 실행할거에요 -> 버튼을 찾았다면
{
applyButton.onClick.RemoveListener(OnApplyButtonClick); // 제거할거에요 -> 중복 방지를 위해 기존 리스너 제거
applyButton.onClick.AddListener(OnApplyButtonClick); // 등록할거에요 -> 클릭 리스너를
Debug.Log($"[LevelUpUIManager] applyButton 자동 탐색 성공: {applyButton.gameObject.name}"); // 로그를 찍을거에요
}
else // 못 찾았으면
{
Debug.LogWarning("[LevelUpUIManager] Apply 버튼을 찾지 못했습니다!"); // 경고를 찍을거에요
}
}
}
/// <summary>
/// Transform 하위에서 이름으로 자식을 재귀 탐색합니다.
/// 비활성 오브젝트도 찾을 수 있습니다. (GameObject.Find는 비활성을 못 찾음)
/// </summary>
private Transform FindChildRecursive(Transform parent, string targetName) // 함수를 정의할거에요 -> 재귀 자식 탐색을
{
for (int i = 0; i < parent.childCount; i++) // 반복할거에요 -> 각 자식마다
{
Transform child = parent.GetChild(i); // 가져올거에요 -> i번째 자식을
if (child.name == targetName) return child; // 찾았으면 반환할거에요 -> 이름이 일치하면
Transform result = FindChildRecursive(child, targetName); // 재귀 탐색할거에요 -> 자식의 자식도
if (result != null) return result; // 찾았으면 반환할거에요
}
return null; // 못 찾았으면 null 반환
2026-02-25 10:39:20 +00:00
}
private void ShuffleList<T>(List<T> list) // 함수를 선언할거에요 -> 리스트를 랜덤으로 섞는 ShuffleList를
{
for (int i = list.Count - 1; i > 0; i--) // 반복할거에요 -> 뒤에서부터
{
int j = Random.Range(0, i + 1); // 뽑을거에요 -> 랜덤 인덱스를
T temp = list[i]; // 임시 저장할거에요 -> 현재 값을
list[i] = list[j]; // 교체할거에요 -> i와 j 위치를
list[j] = temp; // 교체할거에요 -> j에 임시값을
}
2026-01-29 06:58:38 +00:00
}
}