using System; using System.Collections.Generic; using System.Text; using TMPro; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; using Colosseum.Player; using Colosseum.Skills; #if UNITY_EDITOR using UnityEditor; #endif namespace Colosseum.UI { /// /// 젬 보관함과 스킬별 장착/탈착 UI를 관리합니다. /// public class SkillGemInventoryUI : MonoBehaviour { private const string DefaultGemSearchFolder = "Assets/_Game/Data/SkillGems"; [Serializable] private class SkillGemStorageEntry { [SerializeField] private SkillGemData gem; [Min(0)] [SerializeField] private int quantity = 1; public SkillGemData Gem { get => gem; set => gem = value; } public int Quantity { get => quantity; set => quantity = Mathf.Max(0, value); } } [Header("Toggle")] [Tooltip("젬 UI를 여닫는 키")] [SerializeField] private Key toggleKey = Key.G; [Tooltip("토글 버튼에 표시할 텍스트")] [SerializeField] private string toggleButtonLabel = "젬"; [Tooltip("토글 버튼의 캔버스 기준 위치")] [SerializeField] private Vector2 toggleButtonAnchoredPosition = new Vector2(-10f, 82f); [Header("Storage")] [Tooltip("젬 보관 수량")] [SerializeField] private List ownedGemEntries = new(); [Header("Debug")] [Tooltip("플레이 모드 시작 시 패널을 자동으로 엽니다.")] [SerializeField] private bool openOnStartInPlayMode = false; #if UNITY_EDITOR [Header("Editor Auto Collect")] [Tooltip("에디터에서 젬 자산을 자동으로 수집합니다.")] [SerializeField] private bool autoCollectOwnedGemsInEditor = true; [Tooltip("자동 수집할 젬 자산 폴더")] [SerializeField] private string gemSearchFolder = DefaultGemSearchFolder; [Tooltip("자동 수집 시 신규 젬 기본 수량")] [Min(0)] [SerializeField] private int autoCollectedGemQuantity = 1; #endif [Header("Style")] [SerializeField] private Color panelBackgroundColor = new Color(0.08f, 0.08f, 0.11f, 0.96f); [SerializeField] private Color sectionBackgroundColor = new Color(0.14f, 0.14f, 0.18f, 0.95f); [SerializeField] private Color buttonNormalColor = new Color(0.19f, 0.19f, 0.24f, 0.96f); [SerializeField] private Color buttonSelectedColor = new Color(0.48f, 0.32f, 0.16f, 0.96f); [SerializeField] private Color buttonDisabledColor = new Color(0.12f, 0.12f, 0.15f, 0.65f); [SerializeField] private Color statusNormalColor = new Color(0.86f, 0.85f, 0.78f, 1f); [SerializeField] private Color statusErrorColor = new Color(1f, 0.52f, 0.45f, 1f); private static readonly string[] SlotDisplayNames = { "좌클릭", "우클릭", "기술 1", "기술 2", "기술 3", "기술 4", "긴급 회피", }; private PlayerSkillInput playerSkillInput; private PlayerMovement playerMovement; private Canvas parentCanvas; private RectTransform canvasRectTransform; private TMP_FontAsset sharedFont; private GameObject overlayRoot; private RectTransform panelRectTransform; private RectTransform skillListContent; private RectTransform socketListContent; private RectTransform gemListContent; private TextMeshProUGUI storageSummaryText; private TextMeshProUGUI skillSummaryText; private TextMeshProUGUI gemDetailText; private TextMeshProUGUI statusText; private Button equipButton; private Button unequipButton; private TextMeshProUGUI equipButtonLabel; private TextMeshProUGUI unequipButtonLabel; private bool isPanelVisible; private bool uiInitialized; private int selectedSkillSlotIndex; private int selectedSocketIndex; private SkillGemData selectedGem; private bool previousCursorVisible; private CursorLockMode previousCursorLockState; private void Awake() { EnsureUi(); HidePanelImmediate(); } private void Start() { EnsureUi(); FindLocalPlayer(); RefreshAll(); if (Application.isPlaying && openOnStartInPlayMode) { SetPanelVisible(true); } } private void Update() { if (playerSkillInput == null) { FindLocalPlayer(); } if (Keyboard.current == null) return; if (Keyboard.current[toggleKey].wasPressedThisFrame) { TogglePanelVisibility(); } else if (isPanelVisible && Keyboard.current.escapeKey.wasPressedThisFrame) { SetPanelVisible(false); } } private void OnDestroy() { UnsubscribeFromPlayer(); SetGameplayInputBlocked(false); RestoreCursorState(); } #if UNITY_EDITOR private void OnValidate() { if (Application.isPlaying) return; AutoCollectOwnedGemsInEditor(); } #endif private void FindLocalPlayer() { PlayerSkillInput[] players = FindObjectsByType(FindObjectsSortMode.None); for (int i = 0; i < players.Length; i++) { if (!players[i].IsOwner) continue; if (playerSkillInput == players[i]) return; SetTarget(players[i]); return; } } private void SetTarget(PlayerSkillInput target) { UnsubscribeFromPlayer(); playerSkillInput = target; playerMovement = playerSkillInput != null ? playerSkillInput.GetComponent() : null; if (playerSkillInput != null) { playerSkillInput.OnSkillSlotsChanged += HandleSkillSlotsChanged; } if (isPanelVisible) { SetGameplayInputBlocked(true); } RefreshAll(); } private void UnsubscribeFromPlayer() { if (playerSkillInput != null) { playerSkillInput.OnSkillSlotsChanged -= HandleSkillSlotsChanged; } } private void HandleSkillSlotsChanged() { RefreshAll(); } private void TogglePanelVisibility() { SetPanelVisible(!isPanelVisible); } private void SetPanelVisible(bool visible) { EnsureUi(); isPanelVisible = visible; UpdatePanelSize(); if (overlayRoot != null) { overlayRoot.SetActive(visible); } if (visible) { RememberCursorState(); Cursor.visible = true; Cursor.lockState = CursorLockMode.None; SetGameplayInputBlocked(true); RefreshAll(); } else { SetGameplayInputBlocked(false); RestoreCursorState(); } } private void HidePanelImmediate() { isPanelVisible = false; if (overlayRoot != null) { overlayRoot.SetActive(false); } } private void RememberCursorState() { previousCursorVisible = Cursor.visible; previousCursorLockState = Cursor.lockState; } private void RestoreCursorState() { Cursor.visible = previousCursorVisible; Cursor.lockState = previousCursorLockState; } private void SetGameplayInputBlocked(bool blocked) { if (playerMovement != null) { playerMovement.SetGameplayInputEnabled(!blocked); } if (playerSkillInput != null) { playerSkillInput.SetGameplayInputEnabled(!blocked); } } private void EnsureUi() { if (uiInitialized) return; parentCanvas = GetComponentInParent(); canvasRectTransform = parentCanvas != null ? parentCanvas.GetComponent() : null; if (parentCanvas == null || canvasRectTransform == null) { Debug.LogWarning("[SkillGemInventoryUI] Canvas를 찾지 못했습니다."); return; } TextMeshProUGUI referenceText = GetComponentInChildren(true); sharedFont = referenceText != null ? referenceText.font : TMP_Settings.defaultFontAsset; CreateToggleButton(); CreateOverlay(); UpdatePanelSize(); uiInitialized = true; } private void UpdatePanelSize() { if (panelRectTransform == null || canvasRectTransform == null) return; Rect canvasRect = canvasRectTransform.rect; float width = Mathf.Clamp(canvasRect.width - 120f, 920f, 1380f); float height = Mathf.Clamp(canvasRect.height - 120f, 620f, 820f); panelRectTransform.sizeDelta = new Vector2(width, height); } private void CreateToggleButton() { GameObject buttonObject = CreateUiObject("Button_GemInventory", canvasRectTransform); RectTransform buttonRect = buttonObject.AddComponent(); buttonRect.anchorMin = new Vector2(1f, 0f); buttonRect.anchorMax = new Vector2(1f, 0f); buttonRect.pivot = new Vector2(1f, 0f); buttonRect.anchoredPosition = new Vector2(-10f, 90f); buttonRect.sizeDelta = new Vector2(72f, 34f); Image buttonImage = buttonObject.AddComponent(); buttonImage.color = buttonNormalColor; Button toggleButton = buttonObject.AddComponent