using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class QuickSlot : MonoBehaviour, IDropHandler, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler { [Header("Slot Binding")] public string SlotName; // "UseSlot_Q", "UseSlot_E", ... [Header("References")] [SerializeField] private Image _iconImage; [SerializeField] private TextMeshProUGUI _stackText; public UseableEntry CurrentEntry { get; private set; } private Transform _dragTransform; private Transform _iconOriginalParent; private CanvasGroup _iconCanvasGroup; private void Awake() { if (_iconImage != null) { _iconCanvasGroup = _iconImage.GetComponent(); if (_iconCanvasGroup == null) _iconCanvasGroup = _iconImage.gameObject.AddComponent(); } ClearSlot(); } private void Start() { _dragTransform = GameManager.Instance.InGameUI.DragCanvas; } private void Update() { // 아이템 퀵슬롯은 인벤토리와 동기화: 스택 수량 갱신 및 소진 시 자동 해제 if (CurrentEntry is Item itemData) { int total = GameManager.Instance.Inventory.GetTotalStack(itemData); if (total <= 0) { ClearSlot(); return; } if (_stackText != null) { _stackText.text = itemData.IsStackable ? total.ToString() : ""; _stackText.enabled = itemData.IsStackable; } } else if (_stackText != null) { _stackText.enabled = false; } } public void SetEntry(UseableEntry entry) { CurrentEntry = entry; UpdateUI(); } public void ClearSlot() { CurrentEntry = null; if (_iconImage != null) { _iconImage.sprite = null; _iconImage.enabled = false; } if (_stackText != null) { _stackText.text = ""; _stackText.enabled = false; } } public void UpdateUI() { if (CurrentEntry == null || CurrentEntry.Icon == null) { if (_iconImage != null) _iconImage.enabled = false; return; } _iconImage.sprite = CurrentEntry.Icon; _iconImage.enabled = true; } public void Execute(InputState state) { if (CurrentEntry == null) return; if (CurrentEntry is Item itemData) { // 아이템은 누를 때(Started)만 사용 if (state == InputState.Started) GameManager.Instance.Inventory.UseItemByData(itemData); } else if (CurrentEntry is SkillData skillData) { // 스킬은 Started/Canceled 모두 전달 (Charge/Channel 지원) var character = GameManager.Instance.Level.CurrentCharacter; if (character == null) return; if (character.TryGetComponent(out var skillModule)) skillModule.SkillInputByData(skillData, state); } } public void OnDrop(PointerEventData eventData) { GameObject dropped = eventData.pointerDrag; if (dropped == null) return; // 인벤토리에서 드래그 InventoryItemControl invItem = dropped.GetComponent(); if (invItem != null && invItem.ParentSlot != null && invItem.ParentSlot.currentItem != null) { SetEntry(invItem.ParentSlot.currentItem.Data); return; } // 다른 퀵슬롯에서 드래그 → 스왑 QuickSlot otherSlot = dropped.GetComponent(); if (otherSlot != null && otherSlot != this) { UseableEntry temp = CurrentEntry; SetEntry(otherSlot.CurrentEntry); otherSlot.SetEntry(temp); } // 스킬창에서 드래그는 스킬창 제작 시 SkillEntryControl 같은 컴포넌트를 여기서 감지 } public void OnPointerClick(PointerEventData eventData) { if (eventData.button == PointerEventData.InputButton.Right) { ClearSlot(); } } public void OnBeginDrag(PointerEventData eventData) { if (CurrentEntry == null || _iconImage == null) { eventData.pointerDrag = null; return; } _iconOriginalParent = _iconImage.transform.parent; _iconImage.transform.SetParent(_dragTransform); _iconCanvasGroup.blocksRaycasts = false; } public void OnDrag(PointerEventData eventData) { if (_iconImage == null) return; _iconImage.transform.position = eventData.position; } public void OnEndDrag(PointerEventData eventData) { _iconCanvasGroup.blocksRaycasts = true; _iconImage.transform.SetParent(_iconOriginalParent); _iconImage.transform.localPosition = Vector3.zero; UpdateUI(); } } /* 변경 요약 1. 입력 시스템 (InputManager, LevelManager) 추가: OnKeyDown_KKeyEvent 이벤트 + OnKeyDown_KKey 핸들러 → K 키로 스킬창 열기 연결: LevelManager에서 K키 → InGameUIManager.SkillWindowToggle에 구독 요구사항: Input Action Asset의 Character와 InGameUI 맵 양쪽에 OnKeyDown_KKey 액션 추가 필요 2. SkillModule (자동 바인딩 제거) Before: 무기 장착 시 Q/E/R/T가 InputManager에 직접 바인딩 → 누르면 바로 발동 After: 무기 장착 시 _equippedSkills만 채움. 키와는 무관. 퀵슬롯에 등록된 스킬만 발동됨 SkillSlotNames, _skillBindData, DispatchSlot, RegisterSkillsToQuickslot, BindSlotAction 호출 → 삭제 _equippedSkillDataCache[] + GetEquippedSkillDatas() → 추가 (스킬창에서 목록 표시용) 3. QuickSlot (각 슬롯에 부착) 역할: 슬롯 1칸. 스킬이든 아이템이든 UseableEntry 하나를 담음 SlotName 인스펙터 필드 (UseSlot_Q~UseSlot_G) SetEntry(entry) / ClearSlot() / Execute(state) Execute(): 담긴 게 Item이면 Inventory.UseItemByData(), SkillData면 SkillModule.SkillInputByData() 드래그 처리: OnDrop: 인벤토리 아이템 드롭 받으면 등록, 다른 퀵슬롯이면 스왑 OnBeginDrag/OnEndDrag: 자기 슬롯 아이콘을 마우스 따라다니게 (인벤토리와 동일) OnPointerClick 우클릭: 해제 Update(): 아이템이면 인벤토리 잔여 수량 동기화, 0이면 자동 해제 4. QuickSlotController (QuickSlotRoot에 부착) 역할: 슬롯들 묶어서 입력 시스템에 일괄 등록 Awake(): 자식 QuickSlot[] 수집 Start(): 각 슬롯의 SlotName으로 LevelManager.BindSlotAction() 호출 → 키 누르면 → slot.Execute(state) RegisterEntry(slotName, entry) / GetSlot(slotName) 외부 API 5. InventoryManager (퀵슬롯 동기화 메서드 추가) GetTotalStack(Item): 인벤토리 전체에서 해당 아이템 총 개수 (퀵슬롯 카운트 표시용) UseItemByData(Item): 데이터 기반으로 가장 앞 슬롯의 아이템 사용 6. 스킬창 신규 SkillEntry: 스킬창 한 줄. IBeginDragHandler/IDragHandler/IEndDragHandler로 드래그 → 마우스 위 퀵슬롯에 SetEntry() 호출. 본체는 항상 제자리 복귀 SkillWindowUI: Toggle() 호출되면 활성/비활성 + Refresh()로 SkillModule.GetEquippedSkillDatas() 받아 엔트리 인스턴싱 7. InGameUIManager QuickSlot, SkillWindow 참조 필드 추가 SkillWindowToggle(): 스킬창 켜고 끄면서 인벤토리와 동일하게 커서/입력맵 토글 데이터 흐름 키 입력 → 발동 키보드 Q → InputManager.OnKeyDown_SlotQEvent → LevelManager.BindSlotAction이 등록한 콜백 → QuickSlot.Execute(state) → 분기: Item → Inventory.UseItemByData SkillData → SkillModule.SkillInputByData 스킬 등록 K키 → SkillWindow 열림 → SkillModule에서 장착 스킬 목록 받아 표시 사용자가 SkillEntry 드래그 → QuickSlot 위에서 놓음 QuickSlot.SetEntry(skillData) → 아이콘 갱신 아이템 등록 인벤토리 슬롯의 InventoryItemControl 드래그 QuickSlot.OnDrop → SetEntry(itemData) 인벤토리에서 아이템 소진되면 QuickSlot.Update가 감지해서 자동 ClearSlot */