- 탱커, 지원, 딜러 기본 슬롯 프리셋을 로컬 플레이어에 즉시 적용하는 디버그 메뉴를 추가 - PlayerSkillInput에 7칸 슬롯 자동 보정과 일괄 갱신 API를 넣어 기존 프리팹 직렬화와 호환되도록 정리 - SkillQuickSlotUI가 스킬 슬롯 변경 이벤트를 구독해 프리셋 적용 시 액션바가 즉시 갱신되도록 보강 - 플레이 검증에서 탱커, 지원, 딜러 프리셋이 모두 기대한 슬롯 순서와 Ctrl 회피 슬롯까지 정상 적용되는 것을 확인
340 lines
10 KiB
C#
340 lines
10 KiB
C#
using UnityEngine;
|
|
using Colosseum.Player;
|
|
using Colosseum.Skills;
|
|
|
|
namespace Colosseum.UI
|
|
{
|
|
/// <summary>
|
|
/// 스킬 퀵슬롯 UI 관리자.
|
|
/// PlayerSkillInput과 연동하여 쿨타임, 마나 등을 표시합니다.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
|
|
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<PlayerNetworkController>();
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 플레이어 참조 수동 설정 (씬 전환 등에서 사용)
|
|
/// </summary>
|
|
public void SetPlayer(PlayerSkillInput player)
|
|
{
|
|
if (playerSkillInput != null)
|
|
{
|
|
playerSkillInput.OnSkillSlotsChanged -= HandleSkillSlotsChanged;
|
|
}
|
|
|
|
playerSkillInput = player;
|
|
networkController = player?.GetComponent<PlayerNetworkController>();
|
|
|
|
if (playerSkillInput != null)
|
|
{
|
|
playerSkillInput.OnSkillSlotsChanged += HandleSkillSlotsChanged;
|
|
}
|
|
|
|
InitializeSlots();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 슬롯의 스킬 변경
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|