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