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을 짧게 쓰기 위해 별칭 선언 public class LevelUpUIManager : MonoBehaviour // 클래스를 선언할거에요 -> 레벨업 UI를 관리하는 LevelUpUIManager를 { [SerializeField] private GameObject panel; // 변수를 선언할거에요 -> 카드 선택 패널을 [SerializeField] private CardUI[] cardPrefabs; // 배열을 선언할거에요 -> 카드 UI 프리팹들을 [SerializeField] private Transform cardParent; // 변수를 선언할거에요 -> 카드가 생성될 부모 위치를 [SerializeField] private List cardPool; // 리스트를 선언할거에요 -> 카드 데이터 풀을 [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) private CardUI _selectedCardUI; // 변수를 선언할거에요 -> 현재 선택된 카드를 private void Awake() // 함수를 실행할거에요 -> 초기화 시 자동 탐색을 { AutoFindReferences(); // 실행할거에요 -> 비어있는 참조를 이름으로 자동 탐색 } private void OnEnable() // 함수를 실행할거에요 -> 활성화 시 { PlayerLevelSystem.OnLevelUp += Show; // 구독할거에요 -> 레벨업 이벤트를 if (applyButton != null) applyButton.onClick.AddListener(OnApplyButtonClick); // 연결할거에요 -> 버튼 클릭을 } private void OnDisable() // 함수를 실행할거에요 -> 비활성화 시 { PlayerLevelSystem.OnLevelUp -= Show; // 해제할거에요 -> 레벨업 이벤트를 if (applyButton != null) applyButton.onClick.RemoveListener(OnApplyButtonClick); // 해제할거에요 -> 버튼 클릭을 } void Show() // 함수를 선언할거에요 -> 카드 선택 UI를 표시하는 Show를 { // 참조가 끊어졌을 수 있으니 한번 더 탐색 시도 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; // 중단할거에요 } panel.SetActive(true); // 켤거에요 -> 패널을 Time.timeScale = 0f; // 멈출거에요 -> 게임 시간을 (카드 선택 중 일시정지) // ── [버그 수정] 보스 씬에서 카드 클릭 안 되는 문제 ────── // 원인: 보스 체력바 Canvas 또는 BossAttackIndicator Canvas가 // 더 높은 sortingOrder로 위에 올라와 클릭을 가로챔 // 해결: 레벨업 패널이 속한 Canvas를 최상위 sortingOrder로 올려서 // 어떤 Canvas보다도 앞에 오게 강제 설정 Canvas panelCanvas = panel.GetComponentInParent(); // 찾을거에요 -> 패널이 속한 루트 Canvas를 if (panelCanvas != null) // 조건이 맞으면 실행할거에요 -> Canvas가 있으면 { panelCanvas.overrideSorting = true; // 켤거에요 -> 정렬 순서 재정의를 panelCanvas.sortingOrder = 999; // 올릴거에요 -> 가장 높은 순서로 (다른 모든 Canvas 위에) Debug.Log($"[LevelUpUIManager] Canvas sortingOrder → 999 (보스 UI 위에 올리기)"); // 로그를 찍을거에요 } _selectedCardUI = null; // 초기화할거에요 -> 선택 카드를 if (applyButton != null) applyButton.interactable = false; // 비활성화할거에요 -> Apply 버튼을 foreach (Transform child in cardParent) // 반복할거에요 -> 기존 카드들을 Destroy(child.gameObject); // 파괴할거에요 -> 이전 카드를 int slotCount = 3; // [FIX] 값을 설정할거에요 -> 카드 개수를 3장으로 /* ========================= * 1️⃣ CardData 선택 (중복 없음) * ========================= */ List selectedCards = new List(); // 리스트를 만들거에요 -> 선택된 카드 데이터를 List tempDataPool = new List(cardPool); // 리스트를 복사할거에요 -> 임시 풀을 for (int i = 0; i < slotCount && tempDataPool.Count > 0; i++) // 반복할거에요 -> 슬롯 수만큼 { int idx = Random.Range(0, tempDataPool.Count); // 뽑을거에요 -> 랜덤 인덱스를 selectedCards.Add(tempDataPool[idx]); // 추가할거에요 -> 선택된 카드를 tempDataPool.RemoveAt(idx); // 제거할거에요 -> 중복 방지용으로 } /* ========================= * 2️⃣ 메인 버프 스탯 배분 — 3장이 서로 다른 스탯을 메인으로 가짐 * * 카드A → Health 메인 카드B → Speed 메인 카드C → Damage 메인 * (순서는 매번 랜덤으로 섞임) * ========================= */ List mainStats = new List // 리스트를 만들거에요 -> 전체 스탯 목록을 { StatType.Health, // 추가할거에요 -> 체력을 StatType.Speed, // 추가할거에요 -> 속도를 StatType.Damage // 추가할거에요 -> 공격력을 }; ShuffleList(mainStats); // 섞을거에요 -> 배정 순서를 랜덤으로 /* ========================= * 3️⃣ CardUI 프리팹 선택 + 메인 스탯 전달 (중복 없음) * ========================= */ List tempPrefabs = new List(cardPrefabs); // 리스트를 복사할거에요 -> 프리팹 풀을 for (int i = 0; i < selectedCards.Count && tempPrefabs.Count > 0; i++) // 반복할거에요 -> 선택된 카드 수만큼 { 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); // 제거할거에요 -> 중복 방지용으로 } } public void OnCardClick(CardUI clickedUI) // 함수를 선언할거에요 -> 카드 클릭 처리를 { if (_selectedCardUI != null) _selectedCardUI.SetSelected(false); // 해제할거에요 -> 이전 선택을 _selectedCardUI = clickedUI; // 저장할거에요 -> 새 선택을 _selectedCardUI.SetSelected(true); // 설정할거에요 -> 선택 상태를 if (applyButton != null) applyButton.interactable = true; // 활성화할거에요 -> Apply 버튼을 } private void OnApplyButtonClick() // 함수를 선언할거에요 -> Apply 버튼 클릭 처리를 { if (_selectedCardUI == null) return; // 조건이 맞으면 중단할거에요 -> 선택 없으면 _selectedCardUI.ApplyCurrentEffect(); // 실행할거에요 -> 카드 효과를 Close(); // 실행할거에요 -> UI 닫기를 } public void Close() // 함수를 선언할거에요 -> UI 닫기를 { Time.timeScale = 1f; // 복원할거에요 -> 게임 시간을 if (panel != null) panel.SetActive(false); // 끌거에요 -> 패널이 있으면 패널을 // ── [버그 수정] Canvas sortingOrder 원복 ───────────── // Show()에서 999로 올렸던 sortingOrder를 원래대로 되돌림 // 닫은 후에도 999로 남아있으면 다른 UI가 가려질 수 있음 Canvas panelCanvas = panel != null ? panel.GetComponentInParent() : null; // 찾을거에요 -> 패널이 속한 Canvas를 if (panelCanvas != null) // 조건이 맞으면 실행할거에요 -> Canvas가 있으면 { panelCanvas.overrideSorting = false; // 끌거에요 -> 정렬 순서 재정의를 (기본값 복원) panelCanvas.sortingOrder = 0; // 복원할거에요 -> 기본 sortingOrder로 Debug.Log("[LevelUpUIManager] Canvas sortingOrder 원복 완료"); // 로그를 찍을거에요 } } // ───────────────────────────────────────────────────────────── // 자동 탐색 — 폴백 모드에서 끊어진 참조를 이름으로 재연결 // ───────────────────────────────────────────────────────────── /// /// 인스펙터 참조가 비어있으면 오브젝트 이름으로 자동 탐색합니다. /// [Managers] 프리팹에서 폴백 생성 시 Player Canvas 내부 참조가 끊기는 문제를 해결합니다. /// private void AutoFindReferences() // 함수를 정의할거에요 -> 참조 자동 탐색을 { // ── 1. panel (LevelUPPanel) 탐색 — 비활성 오브젝트도 찾기 ── if (panel == null) // 조건이 맞으면 실행할거에요 -> 패널이 비어있으면 { // ★ GameObject.Find()는 비활성 오브젝트를 못 찾으므로 // Canvas를 먼저 찾고 그 하위에서 이름으로 탐색합니다. Canvas[] allCanvas = FindObjectsOfType(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(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