diff --git a/Assets/External/TextMesh Pro/Resources/TMP Settings.asset b/Assets/External/TextMesh Pro/Resources/TMP Settings.asset index 92a60536..697a9bdc 100644 --- a/Assets/External/TextMesh Pro/Resources/TMP Settings.asset +++ b/Assets/External/TextMesh Pro/Resources/TMP Settings.asset @@ -15,7 +15,7 @@ MonoBehaviour: assetVersion: 2 m_TextWrappingMode: 1 m_enableKerning: 1 - m_ActiveFontFeatures: 00000000 + m_ActiveFontFeatures: 6e72656b m_enableExtraPadding: 0 m_enableTintAllSprites: 0 m_enableParseEscapeCharacters: 1 @@ -33,20 +33,18 @@ MonoBehaviour: m_defaultTextMeshProUITextContainerSize: {x: 200, y: 50} m_autoSizeTextContainer: 0 m_IsTextObjectScaleStatic: 0 - m_fallbackFontAssets: [] + m_fallbackFontAssets: + - {fileID: 11400000, guid: ef44cbe516f6f9f418375e5b2b73ad8d, type: 2} m_matchMaterialPreset: 1 m_HideSubTextObjects: 0 - m_defaultSpriteAsset: {fileID: 11400000, guid: c41005c129ba4d66911b75229fd70b45, - type: 2} + m_defaultSpriteAsset: {fileID: 11400000, guid: c41005c129ba4d66911b75229fd70b45, type: 2} m_defaultSpriteAssetPath: Sprite Assets/ m_enableEmojiSupport: 1 m_MissingCharacterSpriteUnicode: 0 m_EmojiFallbackTextAssets: [] m_defaultColorGradientPresetsPath: Color Gradient Presets/ - m_defaultStyleSheet: {fileID: 11400000, guid: f952c082cb03451daed3ee968ac6c63e, - type: 2} + m_defaultStyleSheet: {fileID: 11400000, guid: f952c082cb03451daed3ee968ac6c63e, type: 2} m_StyleSheetsResourcePath: m_leadingCharacters: {fileID: 4900000, guid: d82c1b31c7e74239bff1220585707d2b, type: 3} - m_followingCharacters: {fileID: 4900000, guid: fade42e8bc714b018fac513c043d323b, - type: 3} + m_followingCharacters: {fileID: 4900000, guid: fade42e8bc714b018fac513c043d323b, type: 3} m_UseModernHangulLineBreakingRules: 0 diff --git a/Assets/_Game/Prefabs/Bosses/Prefab_Boss_Drog.prefab b/Assets/_Game/Prefabs/Bosses/Prefab_Boss_Drog.prefab index 6e173348..3348ed34 100644 --- a/Assets/_Game/Prefabs/Bosses/Prefab_Boss_Drog.prefab +++ b/Assets/_Game/Prefabs/Bosses/Prefab_Boss_Drog.prefab @@ -2083,7 +2083,7 @@ MonoBehaviour: intelligence: baseValue: 10 vitality: - baseValue: 10 + baseValue: 100 wisdom: baseValue: 10 spirit: @@ -2153,6 +2153,7 @@ MonoBehaviour: - {fileID: 712281148059590495, guid: b590c58b50c3b554687b172862fa5d9d, type: 3} - {fileID: 6888780564265376159, guid: 827dfeae95fdf6b41b78698f2e846b5f, type: 3} - {fileID: -8752051743343580635, guid: 5eaeca917bbeb494eb14ad0e0552c42f, type: 3} + - {fileID: 7400000, guid: c8fdea7dee0c6f04bbd27fe565071682, type: 2} debugMode: 1 showAreaDebug: 1 debugDrawDuration: 1 @@ -2191,7 +2192,6 @@ MonoBehaviour: navMeshAgent: {fileID: 5153439431748782209} behaviorGraphAgent: {fileID: 0} primaryPattern: {fileID: 11400000, guid: 5efd8123be76bf844875d386d9d5f73d, type: 2} - secondaryPattern: {fileID: 11400000, guid: 4a52d59d590b4eaa9ef92b7984eb08c7, type: 2} mobilityPattern: {fileID: 11400000, guid: 88e6cc7cab28baf4c8f8a742247000ec, type: 2} utilityPattern: {fileID: 11400000, guid: 9f7ab8078af64fd9a6ff4c9ce6aa9d3a, type: 2} comboPattern: {fileID: 11400000, guid: d4e7f2a6b8c31095e1a3c5d7f9b2d4e8, type: 2} @@ -2203,9 +2203,6 @@ MonoBehaviour: mobilityTriggerDistance: 8 punishSearchRadius: 6 utilityTriggerDistance: 5 - phase1SecondaryInterval: 3 - phase2SecondaryInterval: 2 - phase3SecondaryInterval: 2 basicLoopMinCountAfterBigPattern: 2 signatureRequiredDamageRatio: 0.1 signatureTelegraphAbnormality: {fileID: 11400000, guid: fb1a782e44ff4dc19fd8b3c633360752, type: 2} diff --git a/Assets/_Game/Scripts/Enemy/BossCombatBehaviorContext.cs b/Assets/_Game/Scripts/Enemy/BossCombatBehaviorContext.cs index 48be5dc5..8ff45f8e 100644 --- a/Assets/_Game/Scripts/Enemy/BossCombatBehaviorContext.cs +++ b/Assets/_Game/Scripts/Enemy/BossCombatBehaviorContext.cs @@ -475,6 +475,28 @@ namespace Colosseum.Enemy Debug.Log($"[{source}] {message}"); } + /// + /// 모든 패턴 슬롯에 포함된 고유 스킬 목록을 반환합니다. 디버그 용도로 사용됩니다. + /// + public List GetAllPatternSkills() + { + HashSet skillSet = new HashSet(); + BossPatternData[] allPatterns = { primaryPattern, mobilityPattern, utilityPattern, comboPattern, punishPattern, signaturePattern }; + for (int i = 0; i < allPatterns.Length; i++) + { + BossPatternData pattern = allPatterns[i]; + if (pattern?.Steps == null) + continue; + for (int j = 0; j < pattern.Steps.Count; j++) + { + PatternStep step = pattern.Steps[j]; + if (step.Skill != null) + skillSet.Add(step.Skill); + } + } + return new List(skillSet); + } + /// /// 지정 패턴이 grace period를 통과했는지 반환합니다. diff --git a/Assets/_Game/Scripts/InputSystem_Actions.cs b/Assets/_Game/Scripts/InputSystem_Actions.cs index cd21b577..356346ff 100644 --- a/Assets/_Game/Scripts/InputSystem_Actions.cs +++ b/Assets/_Game/Scripts/InputSystem_Actions.cs @@ -226,6 +226,15 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable ""processors"": """", ""interactions"": """", ""initialStateCheck"": false + }, + { + ""name"": ""UIMode"", + ""type"": ""Button"", + ""id"": ""2bb668a6-fd21-4c59-b81a-9b8a4faf6e62"", + ""expectedControlType"": """", + ""processors"": """", + ""interactions"": """", + ""initialStateCheck"": false } ], ""bindings"": [ @@ -613,6 +622,17 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable ""action"": ""DebugHUD"", ""isComposite"": false, ""isPartOfComposite"": false + }, + { + ""name"": """", + ""id"": ""600580e8-6722-42b9-b740-2b734603e4e1"", + ""path"": ""/leftAlt"", + ""interactions"": """", + ""processors"": """", + ""groups"": "";Keyboard&Mouse"", + ""action"": ""UIMode"", + ""isComposite"": false, + ""isPartOfComposite"": false } ] }, @@ -1213,6 +1233,7 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable m_Player_Skill6 = m_Player.FindAction("Skill 6", throwIfNotFound: true); m_Player_Evade = m_Player.FindAction("Evade", throwIfNotFound: true); m_Player_DebugHUD = m_Player.FindAction("DebugHUD", throwIfNotFound: true); + m_Player_UIMode = m_Player.FindAction("UIMode", throwIfNotFound: true); // UI m_UI = asset.FindActionMap("UI", throwIfNotFound: true); m_UI_Navigate = m_UI.FindAction("Navigate", throwIfNotFound: true); @@ -1321,6 +1342,7 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable private readonly InputAction m_Player_Skill6; private readonly InputAction m_Player_Evade; private readonly InputAction m_Player_DebugHUD; + private readonly InputAction m_Player_UIMode; /// /// Provides access to input actions defined in input action map "Player". /// @@ -1393,6 +1415,10 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable /// public InputAction @DebugHUD => m_Wrapper.m_Player_DebugHUD; /// + /// Provides access to the underlying input action "Player/UIMode". + /// + public InputAction @UIMode => m_Wrapper.m_Player_UIMode; + /// /// Provides access to the underlying input action map instance. /// public InputActionMap Get() { return m_Wrapper.m_Player; } @@ -1463,6 +1489,9 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable @DebugHUD.started += instance.OnDebugHUD; @DebugHUD.performed += instance.OnDebugHUD; @DebugHUD.canceled += instance.OnDebugHUD; + @UIMode.started += instance.OnUIMode; + @UIMode.performed += instance.OnUIMode; + @UIMode.canceled += instance.OnUIMode; } /// @@ -1519,6 +1548,9 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable @DebugHUD.started -= instance.OnDebugHUD; @DebugHUD.performed -= instance.OnDebugHUD; @DebugHUD.canceled -= instance.OnDebugHUD; + @UIMode.started -= instance.OnUIMode; + @UIMode.performed -= instance.OnUIMode; + @UIMode.canceled -= instance.OnUIMode; } /// @@ -1924,6 +1956,13 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable /// /// void OnDebugHUD(InputAction.CallbackContext context); + /// + /// Method invoked when associated input action "UIMode" is either , or . + /// + /// + /// + /// + void OnUIMode(InputAction.CallbackContext context); } /// /// Interface to implement callback methods for all input action callbacks associated with input actions defined by "UI" which allows adding and removing callbacks. diff --git a/Assets/_Game/Scripts/InputSystem_Actions.inputactions b/Assets/_Game/Scripts/InputSystem_Actions.inputactions index 1e4a98e1..501864a9 100644 --- a/Assets/_Game/Scripts/InputSystem_Actions.inputactions +++ b/Assets/_Game/Scripts/InputSystem_Actions.inputactions @@ -140,6 +140,15 @@ "processors": "", "interactions": "", "initialStateCheck": false + }, + { + "name": "UIMode", + "type": "Button", + "id": "2bb668a6-fd21-4c59-b81a-9b8a4faf6e62", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false } ], "bindings": [ @@ -527,6 +536,17 @@ "action": "DebugHUD", "isComposite": false, "isPartOfComposite": false + }, + { + "name": "", + "id": "600580e8-6722-42b9-b740-2b734603e4e1", + "path": "/leftAlt", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "UIMode", + "isComposite": false, + "isPartOfComposite": false } ] }, diff --git a/Assets/_Game/Scripts/UI/DebugPanelUI.cs b/Assets/_Game/Scripts/UI/DebugPanelUI.cs index 8a270bc9..4f827a98 100644 --- a/Assets/_Game/Scripts/UI/DebugPanelUI.cs +++ b/Assets/_Game/Scripts/UI/DebugPanelUI.cs @@ -1,6 +1,8 @@ #if UNITY_EDITOR || DEVELOPMENT_BUILD using System; +using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.UI; @@ -10,6 +12,12 @@ using Unity.Netcode; using Colosseum.Enemy; using Colosseum.Abnormalities; +using Colosseum.AI; +using Colosseum.Skills; + +#if UNITY_EDITOR +using UnityEditor; +#endif namespace Colosseum.UI { @@ -35,6 +43,12 @@ namespace Colosseum.UI private BossEnemy cachedBoss; private bool suppressSliderCallback; + // 스킬 강제 발동 + private TMP_Dropdown skillDropdown; + private List debugSkillList; + private SkillController debugSkillController; + private BossEnemy cachedBossForSkillDropdown; + // UI 참조 private GameObject toggleButtonObject; private GameObject panelRoot; @@ -61,6 +75,15 @@ namespace Colosseum.UI { if (panelRoot != null) panelRoot.SetActive(false); + + if (UIModeController.Instance != null) + UIModeController.Instance.OnUIModeChanged += OnUIModeChanged; + } + + private void OnDestroy() + { + if (UIModeController.Instance != null) + UIModeController.Instance.OnUIModeChanged -= OnUIModeChanged; } private void Update() @@ -70,6 +93,7 @@ namespace Colosseum.UI return; UpdateHPDisplay(); + RefreshSkillDropdownIfNeeded(); } /// @@ -198,6 +222,7 @@ namespace Colosseum.UI BuildBossControlSection(content.transform); BuildShieldSection(content.transform); BuildAbnormalitySection(content.transform); + BuildSkillForceSection(content.transform); } /// @@ -267,6 +292,20 @@ namespace Colosseum.UI MakeButton("위협 초기화", parent, OnClearThreat); } + /// + /// 스킬 강제 발동 섹션 + /// + private void BuildSkillForceSection(Transform parent) + { + MakeSectionHeader("스킬 강제 발동", parent); + + skillDropdown = MakeDropdown("SkillDropdown", parent); + + GameObject row = MakeRow(parent); + MakeButton("발동", row.transform, OnForceSkill, 80f); + MakeButton("취소", row.transform, OnCancelSkill, 80f); + } + // ────────────────────────────────────────────────── // UI 업데이트 // ────────────────────────────────────────────────── @@ -395,15 +434,154 @@ namespace Colosseum.UI cachedBoss.ClearAllThreat(); } + // ────────────────────────────────────────────────── + // 스킬 강제 발동 + // ────────────────────────────────────────────────── + + /// + /// 보스가 변경되었으면 스킬 드롭다운을 갱신합니다. + /// + private void RefreshSkillDropdownIfNeeded() + { + if (skillDropdown == null) + return; + + if (cachedBoss != cachedBossForSkillDropdown) + { + cachedBossForSkillDropdown = cachedBoss; + RebuildSkillDropdown(); + } + } + + /// + /// 드롭다운을 갱신합니다. + /// 에디터에서는 Data/Skills에서 보스 이름이 포함된 스킬을 모두 검색하고, + /// 빌드에서는 패턴 슬롯의 스킬만 표시합니다. + /// + private void RebuildSkillDropdown() + { + debugSkillController = cachedBoss != null + ? cachedBoss.GetComponent() + : null; + + if (cachedBoss == null || debugSkillController == null) + { + skillDropdown.ClearOptions(); + skillDropdown.options.Add(new TMP_Dropdown.OptionData("보스 없음")); + skillDropdown.value = 0; + debugSkillList = null; + return; + } + +#if UNITY_EDITOR + debugSkillList = LoadSkillsFromAssetFolder(); +#else + debugSkillList = LoadSkillsFromPatternSlots(); +#endif + + if (debugSkillList == null || debugSkillList.Count == 0) + { + skillDropdown.ClearOptions(); + skillDropdown.options.Add(new TMP_Dropdown.OptionData("스킬 없음")); + skillDropdown.value = 0; + return; + } + + List options = new List(); + for (int i = 0; i < debugSkillList.Count; i++) + { + string name = debugSkillList[i].SkillName; + options.Add(new TMP_Dropdown.OptionData(string.IsNullOrEmpty(name) ? $"Skill {i}" : name)); + } + + skillDropdown.ClearOptions(); + skillDropdown.AddOptions(options); + } + +#if UNITY_EDITOR + /// + /// 에디터 전용: Data/Skills에서 보스 이름이 포함된 SkillData를 모두 검색합니다. + /// + private List LoadSkillsFromAssetFolder() + { + string bossName = cachedBoss.gameObject.name; + string[] guids = AssetDatabase.FindAssets($"t:SkillData", new[] { "Assets/_Game/Data/Skills" }); + + List result = new List(); + for (int i = 0; i < guids.Length; i++) + { + string path = AssetDatabase.GUIDToAssetPath(guids[i]); + // 파일명에 보스 이름이 포함된 스킬만 필터링 + if (path.Contains(bossName)) + { + SkillData skill = AssetDatabase.LoadAssetAtPath(path); + if (skill != null) + result.Add(skill); + } + } + + return result; + } +#endif + + /// + /// 패턴 슬롯에서 고유 스킬을 수집합니다 (빌드용 fallback). + /// + private List LoadSkillsFromPatternSlots() + { + BossCombatBehaviorContext context = cachedBoss.GetComponent(); + if (context == null) + return null; + + return context.GetAllPatternSkills(); + } + + /// + /// 드롭다운에서 선택한 스킬을 강제 발동합니다. + /// + private void OnForceSkill() + { + if (!IsHost || NoBoss || debugSkillController == null || debugSkillList == null) + return; + + int index = skillDropdown.value; + if (index < 0 || index >= debugSkillList.Count) + return; + + if (debugSkillController.IsExecutingSkill) + debugSkillController.CancelSkill(); + + debugSkillController.ResetAllCooldowns(); + debugSkillController.ExecuteSkill(debugSkillList[index]); + } + + /// + /// 현재 실행 중인 스킬을 취소합니다. + /// + private void OnCancelSkill() + { + if (!IsHost || NoBoss || debugSkillController == null) + return; + + if (debugSkillController.IsExecutingSkill) + debugSkillController.CancelSkill(); + } + // ────────────────────────────────────────────────── // 토글 // ────────────────────────────────────────────────── private void TogglePanel() { - isPanelOpen = !isPanelOpen; + if (UIModeController.Instance != null) + UIModeController.Instance.SetUIModeActive(!UIModeController.Instance.IsUIModeActive); + } + + private void OnUIModeChanged(bool uiModeActive) + { if (panelRoot != null) - panelRoot.SetActive(isPanelOpen); + panelRoot.SetActive(uiModeActive); + isPanelOpen = uiModeActive; } // ────────────────────────────────────────────────── @@ -611,6 +789,148 @@ namespace Colosseum.UI if (DefaultFont != null) t.font = DefaultFont; return t; } + + private static TMP_Dropdown MakeDropdown(string name, Transform parent) + { + // 메인 드롭다운 오브젝트 + GameObject dropdownGo = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(TMP_Dropdown)); + dropdownGo.transform.SetParent(parent, false); + dropdownGo.AddComponent().preferredHeight = 30f; + + // 캡션 라벨 + GameObject captionGo = new GameObject("Caption", typeof(RectTransform), typeof(TextMeshProUGUI)); + captionGo.transform.SetParent(dropdownGo.transform, false); + RectTransform captionRt = captionGo.GetComponent(); + captionRt.anchorMin = Vector2.zero; + captionRt.anchorMax = Vector2.one; + captionRt.offsetMin = new Vector2(10f, 1f); + captionRt.offsetMax = new Vector2(-25f, -2f); + TMP_Text captionText = captionGo.GetComponent(); + captionText.text = "스킬 선택"; + captionText.fontSize = 14f; + captionText.alignment = TextAlignmentOptions.MidlineLeft; + captionText.color = Color.white; + captionText.textWrappingMode = TextWrappingModes.NoWrap; + if (DefaultFont != null) captionText.font = DefaultFont; + + // 화살표 + GameObject arrowGo = new GameObject("Arrow", typeof(RectTransform), typeof(Image)); + arrowGo.transform.SetParent(dropdownGo.transform, false); + RectTransform arrowRt = arrowGo.GetComponent(); + arrowRt.anchorMin = new Vector2(1f, 0.5f); + arrowRt.anchorMax = new Vector2(1f, 0.5f); + arrowRt.sizeDelta = new Vector2(20f, 20f); + arrowRt.pivot = new Vector2(0.5f, 0.5f); + arrowRt.anchoredPosition = new Vector2(-15f, 0f); + arrowGo.GetComponent().color = new Color(0.6f, 0.6f, 0.6f); + + // 템플릿 (팝업 목록) + GameObject templateGo = new GameObject("Template", typeof(RectTransform), typeof(Image), typeof(ScrollRect)); + templateGo.transform.SetParent(dropdownGo.transform, false); + templateGo.SetActive(false); + RectTransform templateRt = templateGo.GetComponent(); + templateRt.anchorMin = new Vector2(0f, -1f); + templateRt.anchorMax = new Vector2(1f, 0f); + templateRt.sizeDelta = new Vector2(0f, 150f); + templateRt.pivot = new Vector2(0.5f, 1f); + templateGo.GetComponent().color = new Color(0.15f, 0.15f, 0.15f, 0.95f); + + ScrollRect scroll = templateGo.GetComponent(); + scroll.horizontal = false; + scroll.movementType = ScrollRect.MovementType.Clamped; + scroll.scrollSensitivity = 20f; + + // 뷰포트 + GameObject viewportGo = new GameObject("Viewport", typeof(RectTransform), typeof(Image)); + viewportGo.transform.SetParent(templateGo.transform, false); + RectTransform viewportRt = viewportGo.GetComponent(); + viewportRt.anchorMin = Vector2.zero; + viewportRt.anchorMax = Vector2.one; + viewportRt.sizeDelta = Vector2.zero; + viewportRt.pivot = new Vector2(0f, 1f); + viewportGo.GetComponent().color = Color.clear; + scroll.viewport = viewportRt; + + // 콘텐츠 + GameObject contentGo = new GameObject("Content", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter)); + contentGo.transform.SetParent(viewportGo.transform, false); + RectTransform contentRt = contentGo.GetComponent(); + contentRt.anchorMin = new Vector2(0f, 1f); + contentRt.anchorMax = new Vector2(1f, 1f); + contentRt.pivot = new Vector2(0.5f, 1f); + contentRt.sizeDelta = Vector2.zero; + contentRt.anchoredPosition = Vector2.zero; + + VerticalLayoutGroup vlg = contentGo.GetComponent(); + vlg.childAlignment = TextAnchor.MiddleLeft; + vlg.childControlWidth = true; + vlg.childControlHeight = true; + vlg.childForceExpandWidth = true; + vlg.childForceExpandHeight = false; + contentGo.GetComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + scroll.content = contentRt; + + // 아이템 템플릿 + GameObject itemGo = new GameObject("Item", typeof(RectTransform), typeof(Toggle)); + itemGo.transform.SetParent(contentGo.transform, false); + RectTransform itemRt = itemGo.GetComponent(); + itemRt.anchorMin = new Vector2(0f, 0.5f); + itemRt.anchorMax = new Vector2(1f, 0.5f); + itemRt.sizeDelta = new Vector2(0f, 20f); + + LayoutElement itemLe = itemGo.AddComponent(); + itemLe.preferredHeight = 20f; + itemLe.minHeight = 20f; + + // 아이템 배경 + GameObject itemBgGo = new GameObject("Item Background", typeof(RectTransform), typeof(Image)); + itemBgGo.transform.SetParent(itemGo.transform, false); + RectTransform itemBgRt = itemBgGo.GetComponent(); + itemBgRt.anchorMin = Vector2.zero; + itemBgRt.anchorMax = Vector2.one; + itemBgRt.sizeDelta = Vector2.zero; + itemBgGo.GetComponent().color = new Color(0.25f, 0.25f, 0.25f, 1f); + + // 아이템 체크마크 + GameObject checkGo = new GameObject("Item Checkmark", typeof(RectTransform), typeof(Image)); + checkGo.transform.SetParent(itemGo.transform, false); + RectTransform checkRt = checkGo.GetComponent(); + checkRt.anchorMin = new Vector2(0f, 0.5f); + checkRt.anchorMax = new Vector2(0f, 0.5f); + checkRt.sizeDelta = new Vector2(20f, 20f); + checkRt.pivot = new Vector2(0.5f, 0.5f); + checkRt.anchoredPosition = new Vector2(10f, 0f); + + // 아이템 라벨 + GameObject itemLabelGo = new GameObject("Item Label", typeof(RectTransform), typeof(TextMeshProUGUI)); + itemLabelGo.transform.SetParent(itemGo.transform, false); + RectTransform itemLabelRt = itemLabelGo.GetComponent(); + itemLabelRt.anchorMin = Vector2.zero; + itemLabelRt.anchorMax = Vector2.one; + itemLabelRt.offsetMin = new Vector2(20f, 1f); + itemLabelRt.offsetMax = new Vector2(-5f, -2f); + TMP_Text itemLabelText = itemLabelGo.GetComponent(); + itemLabelText.fontSize = 14f; + itemLabelText.alignment = TextAlignmentOptions.MidlineLeft; + itemLabelText.color = Color.white; + itemLabelText.textWrappingMode = TextWrappingModes.NoWrap; + if (DefaultFont != null) itemLabelText.font = DefaultFont; + + // 토글 연결 + Toggle toggle = itemGo.GetComponent(); + toggle.targetGraphic = itemBgGo.GetComponent(); + toggle.graphic = checkGo.GetComponent(); + toggle.isOn = false; + + // 드롭다운 연결 + TMP_Dropdown dropdown = dropdownGo.GetComponent(); + dropdown.targetGraphic = dropdownGo.GetComponent(); + dropdown.captionText = captionText; + dropdown.itemText = itemLabelText; + dropdown.template = templateRt; + + return dropdown; + } } } diff --git a/Assets/_Game/Scripts/UI/PassiveTreeUI.cs b/Assets/_Game/Scripts/UI/PassiveTreeUI.cs index c653fae0..82781d45 100644 --- a/Assets/_Game/Scripts/UI/PassiveTreeUI.cs +++ b/Assets/_Game/Scripts/UI/PassiveTreeUI.cs @@ -34,7 +34,7 @@ namespace Colosseum.UI [Tooltip("토글 버튼에 표시할 텍스트")] [SerializeField] private string toggleButtonLabel = "패시브"; [Tooltip("토글 버튼의 캔버스 기준 위치")] - [SerializeField] private Vector2 toggleButtonAnchoredPosition = new Vector2(-132f, 48f); + [SerializeField] private Vector2 toggleButtonAnchoredPosition = new Vector2(-10f, 46f); [Header("Debug")] [Tooltip("플레이 모드 시작 시 패널을 자동으로 엽니다.")] @@ -380,7 +380,7 @@ namespace Colosseum.UI RectTransform toggleRect = viewInstance.ToggleButton.GetComponent(); if (toggleRect != null) { - toggleRect.anchoredPosition = toggleButtonAnchoredPosition; + toggleRect.anchoredPosition = new Vector2(-10f, 50f); } } } @@ -412,7 +412,7 @@ namespace Colosseum.UI buttonRect.anchorMin = new Vector2(1f, 0f); buttonRect.anchorMax = new Vector2(1f, 0f); buttonRect.pivot = new Vector2(1f, 0f); - buttonRect.anchoredPosition = toggleButtonAnchoredPosition; + buttonRect.anchoredPosition = new Vector2(-10f, 50f); buttonRect.sizeDelta = new Vector2(110f, 40f); Image buttonImage = buttonObject.AddComponent(); diff --git a/Assets/_Game/Scripts/UI/SkillGemInventoryUI.cs b/Assets/_Game/Scripts/UI/SkillGemInventoryUI.cs index 87c90e8e..a20b97c7 100644 --- a/Assets/_Game/Scripts/UI/SkillGemInventoryUI.cs +++ b/Assets/_Game/Scripts/UI/SkillGemInventoryUI.cs @@ -49,7 +49,7 @@ namespace Colosseum.UI [Tooltip("토글 버튼에 표시할 텍스트")] [SerializeField] private string toggleButtonLabel = "젬"; [Tooltip("토글 버튼의 캔버스 기준 위치")] - [SerializeField] private Vector2 toggleButtonAnchoredPosition = new Vector2(-48f, 164f); + [SerializeField] private Vector2 toggleButtonAnchoredPosition = new Vector2(-10f, 82f); [Header("Storage")] [Tooltip("젬 보관 수량")] @@ -327,7 +327,7 @@ namespace Colosseum.UI buttonRect.anchorMin = new Vector2(1f, 0f); buttonRect.anchorMax = new Vector2(1f, 0f); buttonRect.pivot = new Vector2(1f, 0f); - buttonRect.anchoredPosition = toggleButtonAnchoredPosition; + buttonRect.anchoredPosition = new Vector2(-10f, 90f); buttonRect.sizeDelta = new Vector2(72f, 34f); Image buttonImage = buttonObject.AddComponent(); diff --git a/Assets/_Game/Scripts/UI/UIModeController.cs b/Assets/_Game/Scripts/UI/UIModeController.cs new file mode 100644 index 00000000..f64ea8b4 --- /dev/null +++ b/Assets/_Game/Scripts/UI/UIModeController.cs @@ -0,0 +1,151 @@ +#if UNITY_EDITOR || DEVELOPMENT_BUILD + +using System; + +using UnityEngine; + +using Colosseum.Player; + +namespace Colosseum.UI +{ + /// + /// 공용 UI 모드 컨트롤러. + /// UIMode 입력(leftAlt)으로 커서 표시/숨김과 게임플레이 입력 차단을 토글합니다. + /// + [DisallowMultipleComponent] + public class UIModeController : MonoBehaviour + { + private static UIModeController instance; + + /// + /// 현재 활성화된 UIModeController 인스턴스 + /// + public static UIModeController Instance => instance; + + /// + /// UI 모드 활성화 여부 + /// + public bool IsUIModeActive { get; private set; } + + /// + /// UI 모드 상태 변경 이벤트 + /// + public event Action OnUIModeChanged; + + private InputSystem_Actions inputActions; + private PlayerMovement playerMovement; + private PlayerSkillInput playerSkillInput; + + private bool previousCursorVisible; + private CursorLockMode previousCursorLockState; + + private void Awake() + { + if (instance != null && instance != this) + { + Destroy(gameObject); + return; + } + + instance = this; + } + + private void OnDestroy() + { + if (instance == this) + instance = null; + + CleanupInput(); + } + + private void Start() + { + InitializeInput(); + } + + private void Update() + { + if (playerSkillInput == null) + FindLocalPlayerReferences(); + } + + private void InitializeInput() + { + if (inputActions != null) + return; + + inputActions = new InputSystem_Actions(); + inputActions.Player.UIMode.performed += OnUIModePerformed; + inputActions.Player.UIMode.Enable(); + } + + private void CleanupInput() + { + if (inputActions != null) + { + inputActions.Player.UIMode.performed -= OnUIModePerformed; + inputActions.Player.UIMode.Disable(); + inputActions.Dispose(); + inputActions = null; + } + } + + private void FindLocalPlayerReferences() + { + PlayerSkillInput[] skillInputs = FindObjectsByType(FindObjectsSortMode.None); + for (int i = 0; i < skillInputs.Length; i++) + { + if (skillInputs[i] != null && skillInputs[i].IsOwner) + { + playerSkillInput = skillInputs[i]; + playerMovement = skillInputs[i].GetComponent(); + break; + } + } + } + + /// + /// UI 모드를 수동으로 설정합니다. + /// + public void SetUIModeActive(bool active) + { + if (IsUIModeActive == active) + return; + + IsUIModeActive = active; + + if (active) + { + previousCursorVisible = Cursor.visible; + previousCursorLockState = Cursor.lockState; + Cursor.visible = true; + Cursor.lockState = CursorLockMode.None; + SetGameplayInputBlocked(true); + } + else + { + SetGameplayInputBlocked(false); + Cursor.visible = previousCursorVisible; + Cursor.lockState = previousCursorLockState; + } + + OnUIModeChanged?.Invoke(IsUIModeActive); + } + + private void OnUIModePerformed(UnityEngine.InputSystem.InputAction.CallbackContext context) + { + SetUIModeActive(!IsUIModeActive); + } + + private void SetGameplayInputBlocked(bool blocked) + { + if (playerMovement != null) + playerMovement.SetGameplayInputEnabled(!blocked); + + if (playerSkillInput != null) + playerSkillInput.SetGameplayInputEnabled(!blocked); + } + } +} + +#endif diff --git a/Assets/_Game/Scripts/UI/UIModeController.cs.meta b/Assets/_Game/Scripts/UI/UIModeController.cs.meta new file mode 100644 index 00000000..0d3b0ff9 --- /dev/null +++ b/Assets/_Game/Scripts/UI/UIModeController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3faf1bf9fabb1c24bbe4c2c2f4b400da \ No newline at end of file