feat: 플레이어 자원/액션바 HUD 분리 및 긴급회피 슬롯 추가

This commit is contained in:
2026-03-24 12:23:51 +09:00
parent 0889bb0f25
commit 543b83e707
11 changed files with 6301 additions and 1333 deletions

View File

@@ -11,25 +11,193 @@ namespace Colosseum.UI
public class SkillQuickSlotUI : MonoBehaviour
{
[Header("Skill Slots")]
[Tooltip("6개의 스킬 슬롯 UI (인덱스 순서대로)")]
[SerializeField] private SkillSlotUI[] skillSlots = new SkillSlotUI[6];
[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("키바인딩 표시 텍스트 (기본: Q, W, E, R, A, S)")]
[SerializeField] private string[] keyLabels = { "Q", "W", "E", "R", "A", "S" };
[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();
}
/// <summary>
/// 자식 슬롯을 자동 수집해 프리팹 중첩 구조 변경에도 참조가 유지되도록 합니다.
/// </summary>
private void AutoCollectSkillSlots()
{
SkillSlotUI[] foundSlots = GetComponentsInChildren<SkillSlotUI>(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();
}
/// <summary>
/// 슬롯 수 변경이나 프리팹 구조 변경 시 기본 표시 구성을 복구합니다.
/// </summary>
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);
}
}
/// <summary>
/// 7칸 액션바는 긴급회피를 맨 왼쪽에 두고, 나머지는 기본 스킬 순서를 유지합니다.
/// </summary>
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;
}
/// <summary>
/// 기본 키 라벨을 슬롯 수에 맞춰 생성합니다.
/// </summary>
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;
}
/// <summary>
/// 표시 매핑이 현재 슬롯 수에 대해 유효한지 검사합니다.
/// </summary>
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;
}
/// <summary>
/// UI 인덱스를 실제 플레이어 스킬 슬롯 인덱스로 변환합니다.
/// </summary>
private int GetMappedSlotIndex(int displayIndex)
{
EnsureDisplayConfig();
if (displayIndex < 0 || displayIndex >= slotMappings.Length)
{
return displayIndex;
}
return slotMappings[displayIndex];
}
/// <summary>
/// 실제 슬롯 인덱스를 표시 슬롯 인덱스로 역변환합니다.
/// </summary>
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<PlayerSkillInput>(FindObjectsSortMode.None);
@@ -57,17 +225,19 @@ namespace Colosseum.UI
private void InitializeSlots()
{
AutoCollectSkillSlots();
if (playerSkillInput == null) return;
int initializedCount = 0;
for (int i = 0; i < skillSlots.Length && i < keyLabels.Length; i++)
for (int i = 0; i < skillSlots.Length; i++)
{
SkillData skill = playerSkillInput.GetSkill(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(i, skill, keyLabel);
skillSlots[i].Initialize(mappedSlotIndex, skill, keyLabel);
if (skill != null) initializedCount++;
}
else
@@ -102,16 +272,17 @@ namespace Colosseum.UI
{
if (skillSlots[i] == null) continue;
SkillData skill = playerSkillInput.GetSkill(i);
int mappedSlotIndex = GetMappedSlotIndex(i);
SkillData skill = playerSkillInput.GetSkill(mappedSlotIndex);
if (skill == null) continue;
float remainingCooldown = playerSkillInput.GetRemainingCooldown(i);
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 {i}: {skill.SkillName}, CD: {remainingCooldown:F1}/{totalCooldown:F1}");
Debug.Log($"[SkillQuickSlotUI] Slot {mappedSlotIndex}: {skill.SkillName}, CD: {remainingCooldown:F1}/{totalCooldown:F1}");
}
skillSlots[i].UpdateState(remainingCooldown, totalCooldown, hasEnoughMana);
@@ -133,10 +304,13 @@ namespace Colosseum.UI
/// </summary>
public void UpdateSkillSlot(int slotIndex, SkillData skill)
{
if (slotIndex < 0 || slotIndex >= skillSlots.Length) return;
AutoCollectSkillSlots();
string keyLabel = slotIndex < keyLabels.Length ? keyLabels[slotIndex] : (slotIndex + 1).ToString();
skillSlots[slotIndex].Initialize(slotIndex, skill, keyLabel);
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);
}
}
}