using UnityEngine; using Colosseum.Player; using Colosseum.Skills; namespace Colosseum.UI { /// /// 스킬 퀵슬롯 UI 관리자. /// PlayerSkillInput과 연동하여 쿨타임, 마나 등을 표시합니다. /// public class SkillQuickSlotUI : MonoBehaviour { [Header("Skill Slots")] [Tooltip("표시 순서대로 배치된 스킬 슬롯 UI")] [SerializeField] private SkillSlotUI[] skillSlots = new SkillSlotUI[7]; [Header("Debug")] [SerializeField] private bool debugMode = false; [Header("Display Order")] [Tooltip("UI 슬롯 순서가 참조할 실제 스킬 슬롯 인덱스")] [SerializeField] private int[] slotMappings = { 6, 0, 1, 2, 3, 4, 5 }; [Header("Keybind Labels")] [Tooltip("키바인딩 표시 텍스트")] [SerializeField] private string[] keyLabels = { "Ctrl", "L", "R", "1", "2", "3", "4" }; private PlayerSkillInput playerSkillInput; private PlayerNetworkController networkController; private void Awake() { AutoCollectSkillSlots(); } private void OnValidate() { AutoCollectSkillSlots(); } private void Start() { // 로컬 플레이어 찾기 FindLocalPlayer(); } private void OnDestroy() { if (playerSkillInput != null) { playerSkillInput.OnSkillSlotsChanged -= HandleSkillSlotsChanged; } } /// /// 자식 슬롯을 자동 수집해 프리팹 중첩 구조 변경에도 참조가 유지되도록 합니다. /// private void AutoCollectSkillSlots() { SkillSlotUI[] foundSlots = GetComponentsInChildren(true); if (foundSlots == null || foundSlots.Length == 0) { return; } if (skillSlots == null || skillSlots.Length != foundSlots.Length) { skillSlots = foundSlots; return; } for (int i = 0; i < foundSlots.Length; i++) { if (skillSlots[i] != foundSlots[i]) { skillSlots = foundSlots; return; } } EnsureDisplayConfig(); } /// /// 슬롯 수 변경이나 프리팹 구조 변경 시 기본 표시 구성을 복구합니다. /// private void EnsureDisplayConfig() { int slotCount = skillSlots != null ? skillSlots.Length : 0; if (slotCount <= 0) { return; } if (!IsValidSlotMappings(slotMappings, slotCount)) { slotMappings = BuildDefaultSlotMappings(slotCount); } if (keyLabels == null || keyLabels.Length != slotCount) { keyLabels = BuildDefaultKeyLabels(slotCount); } } /// /// 7칸 액션바는 긴급회피를 맨 왼쪽에 두고, 나머지는 기본 스킬 순서를 유지합니다. /// private static int[] BuildDefaultSlotMappings(int slotCount) { int[] defaultMappings = new int[slotCount]; if (slotCount == 7) { defaultMappings[0] = 6; for (int i = 1; i < slotCount; i++) { defaultMappings[i] = i - 1; } return defaultMappings; } for (int i = 0; i < slotCount; i++) { defaultMappings[i] = i; } return defaultMappings; } /// /// 기본 키 라벨을 슬롯 수에 맞춰 생성합니다. /// private static string[] BuildDefaultKeyLabels(int slotCount) { if (slotCount == 7) { return new[] { "Ctrl", "L", "R", "1", "2", "3", "4" }; } string[] defaultLabels = new string[slotCount]; for (int i = 0; i < slotCount; i++) { defaultLabels[i] = (i + 1).ToString(); } return defaultLabels; } /// /// 표시 매핑이 현재 슬롯 수에 대해 유효한지 검사합니다. /// private static bool IsValidSlotMappings(int[] mappings, int slotCount) { if (mappings == null || mappings.Length != slotCount) { return false; } bool[] usedIndices = new bool[slotCount]; for (int i = 0; i < mappings.Length; i++) { int mappedIndex = mappings[i]; if (mappedIndex < 0 || mappedIndex >= slotCount || usedIndices[mappedIndex]) { return false; } usedIndices[mappedIndex] = true; } return true; } /// /// UI 인덱스를 실제 플레이어 스킬 슬롯 인덱스로 변환합니다. /// private int GetMappedSlotIndex(int displayIndex) { EnsureDisplayConfig(); if (displayIndex < 0 || displayIndex >= slotMappings.Length) { return displayIndex; } return slotMappings[displayIndex]; } /// /// 실제 슬롯 인덱스를 표시 슬롯 인덱스로 역변환합니다. /// private int GetDisplayIndex(int slotIndex) { EnsureDisplayConfig(); for (int i = 0; i < slotMappings.Length; i++) { if (slotMappings[i] == slotIndex) { return i; } } return -1; } private void FindLocalPlayer() { var players = FindObjectsByType(FindObjectsSortMode.None); if (players.Length == 0) { Debug.LogWarning("[SkillQuickSlotUI] No PlayerSkillInput found in scene"); return; } foreach (var player in players) { if (player.IsOwner) { SetPlayer(player); networkController = player.GetComponent(); Debug.Log($"[SkillQuickSlotUI] Found local player: {player.name}"); return; } } Debug.LogWarning("[SkillQuickSlotUI] No local player found (IsOwner = false)"); } private void InitializeSlots() { AutoCollectSkillSlots(); if (playerSkillInput == null) return; int initializedCount = 0; for (int i = 0; i < skillSlots.Length; i++) { int mappedSlotIndex = GetMappedSlotIndex(i); SkillData skill = playerSkillInput.GetSkill(mappedSlotIndex); string keyLabel = i < keyLabels.Length ? keyLabels[i] : (i + 1).ToString(); if (skillSlots[i] != null) { skillSlots[i].Initialize(mappedSlotIndex, skill, keyLabel); if (skill != null) initializedCount++; } else { Debug.LogWarning($"[SkillQuickSlotUI] Slot {i} is not assigned"); } } Debug.Log($"[SkillQuickSlotUI] Initialized {initializedCount} skill slots"); } private void Update() { if (playerSkillInput == null) { // 플레이어가 아직 없으면 다시 찾기 시도 FindLocalPlayer(); return; } UpdateSlotStates(); } private float debugLogTimer = 0f; private void UpdateSlotStates() { bool shouldLog = debugMode && Time.time > debugLogTimer; if (shouldLog) debugLogTimer = Time.time + 1f; for (int i = 0; i < skillSlots.Length; i++) { if (skillSlots[i] == null) continue; int mappedSlotIndex = GetMappedSlotIndex(i); SkillData skill = playerSkillInput.GetSkill(mappedSlotIndex); if (skill == null) continue; float remainingCooldown = playerSkillInput.GetRemainingCooldown(mappedSlotIndex); float totalCooldown = skill.Cooldown; bool hasEnoughMana = networkController == null || networkController.Mana >= skill.ManaCost; if (shouldLog && remainingCooldown > 0f) { Debug.Log($"[SkillQuickSlotUI] Slot {mappedSlotIndex}: {skill.SkillName}, CD: {remainingCooldown:F1}/{totalCooldown:F1}"); } skillSlots[i].UpdateState(remainingCooldown, totalCooldown, hasEnoughMana); } } /// /// 플레이어 참조 수동 설정 (씬 전환 등에서 사용) /// public void SetPlayer(PlayerSkillInput player) { if (playerSkillInput != null) { playerSkillInput.OnSkillSlotsChanged -= HandleSkillSlotsChanged; } playerSkillInput = player; networkController = player?.GetComponent(); if (playerSkillInput != null) { playerSkillInput.OnSkillSlotsChanged += HandleSkillSlotsChanged; } InitializeSlots(); } /// /// 특정 슬롯의 스킬 변경 /// public void UpdateSkillSlot(int slotIndex, SkillData skill) { AutoCollectSkillSlots(); int displayIndex = GetDisplayIndex(slotIndex); if (displayIndex < 0 || displayIndex >= skillSlots.Length) return; string keyLabel = displayIndex < keyLabels.Length ? keyLabels[displayIndex] : (displayIndex + 1).ToString(); skillSlots[displayIndex].Initialize(slotIndex, skill, keyLabel); } private void HandleSkillSlotsChanged() { InitializeSlots(); } } }