feat: 디버그 패널 스킬 강제 발동 및 UI 모드 토글 시스템 추가
- UIModeController: leftAlt 키로 커서 표시/게임플레이 입력 차단 토글 (공용 싱글톤) - DebugPanelUI: 보스 스킬 강제 발동 섹션 추가 (드롭다운 + 발동/취소 버튼) - 에디터에서 Data/Skills의 보스 이름 기반 스킬 검색, 빌드에서 패턴 슬롯 fallback - BossCombatBehaviorContext.GetAllPatternSkills() 추가 (디버그용 스킬 목록 수집) - TMP Settings에 한글 폰트(MaruBuri)를 fallback으로 등록 - 젬/패시브/디버그 토글 버튼을 우측 하단에 수직 정렬 - InputSystem에 UIMode 액션(leftAlt) 추가
This commit is contained in:
@@ -475,6 +475,28 @@ namespace Colosseum.Enemy
|
||||
Debug.Log($"[{source}] {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 패턴 슬롯에 포함된 고유 스킬 목록을 반환합니다. 디버그 용도로 사용됩니다.
|
||||
/// </summary>
|
||||
public List<SkillData> GetAllPatternSkills()
|
||||
{
|
||||
HashSet<SkillData> skillSet = new HashSet<SkillData>();
|
||||
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<SkillData>(skillSet);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 지정 패턴이 grace period를 통과했는지 반환합니다.
|
||||
|
||||
@@ -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"": ""<Keyboard>/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;
|
||||
/// <summary>
|
||||
/// Provides access to input actions defined in input action map "Player".
|
||||
/// </summary>
|
||||
@@ -1393,6 +1415,10 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable
|
||||
/// </summary>
|
||||
public InputAction @DebugHUD => m_Wrapper.m_Player_DebugHUD;
|
||||
/// <summary>
|
||||
/// Provides access to the underlying input action "Player/UIMode".
|
||||
/// </summary>
|
||||
public InputAction @UIMode => m_Wrapper.m_Player_UIMode;
|
||||
/// <summary>
|
||||
/// Provides access to the underlying input action map instance.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1924,6 +1956,13 @@ public partial class @InputSystem_Actions: IInputActionCollection2, IDisposable
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||
void OnDebugHUD(InputAction.CallbackContext context);
|
||||
/// <summary>
|
||||
/// Method invoked when associated input action "UIMode" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||
void OnUIMode(InputAction.CallbackContext context);
|
||||
}
|
||||
/// <summary>
|
||||
/// Interface to implement callback methods for all input action callbacks associated with input actions defined by "UI" which allows adding and removing callbacks.
|
||||
|
||||
@@ -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": "<Keyboard>/leftAlt",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "UIMode",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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<SkillData> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,6 +222,7 @@ namespace Colosseum.UI
|
||||
BuildBossControlSection(content.transform);
|
||||
BuildShieldSection(content.transform);
|
||||
BuildAbnormalitySection(content.transform);
|
||||
BuildSkillForceSection(content.transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -267,6 +292,20 @@ namespace Colosseum.UI
|
||||
MakeButton("위협 초기화", parent, OnClearThreat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스킬 강제 발동 섹션
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// 스킬 강제 발동
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 보스가 변경되었으면 스킬 드롭다운을 갱신합니다.
|
||||
/// </summary>
|
||||
private void RefreshSkillDropdownIfNeeded()
|
||||
{
|
||||
if (skillDropdown == null)
|
||||
return;
|
||||
|
||||
if (cachedBoss != cachedBossForSkillDropdown)
|
||||
{
|
||||
cachedBossForSkillDropdown = cachedBoss;
|
||||
RebuildSkillDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드롭다운을 갱신합니다.
|
||||
/// 에디터에서는 Data/Skills에서 보스 이름이 포함된 스킬을 모두 검색하고,
|
||||
/// 빌드에서는 패턴 슬롯의 스킬만 표시합니다.
|
||||
/// </summary>
|
||||
private void RebuildSkillDropdown()
|
||||
{
|
||||
debugSkillController = cachedBoss != null
|
||||
? cachedBoss.GetComponent<SkillController>()
|
||||
: 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<TMP_Dropdown.OptionData> options = new List<TMP_Dropdown.OptionData>();
|
||||
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
|
||||
/// <summary>
|
||||
/// 에디터 전용: Data/Skills에서 보스 이름이 포함된 SkillData를 모두 검색합니다.
|
||||
/// </summary>
|
||||
private List<SkillData> LoadSkillsFromAssetFolder()
|
||||
{
|
||||
string bossName = cachedBoss.gameObject.name;
|
||||
string[] guids = AssetDatabase.FindAssets($"t:SkillData", new[] { "Assets/_Game/Data/Skills" });
|
||||
|
||||
List<SkillData> result = new List<SkillData>();
|
||||
for (int i = 0; i < guids.Length; i++)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||
// 파일명에 보스 이름이 포함된 스킬만 필터링
|
||||
if (path.Contains(bossName))
|
||||
{
|
||||
SkillData skill = AssetDatabase.LoadAssetAtPath<SkillData>(path);
|
||||
if (skill != null)
|
||||
result.Add(skill);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 패턴 슬롯에서 고유 스킬을 수집합니다 (빌드용 fallback).
|
||||
/// </summary>
|
||||
private List<SkillData> LoadSkillsFromPatternSlots()
|
||||
{
|
||||
BossCombatBehaviorContext context = cachedBoss.GetComponent<BossCombatBehaviorContext>();
|
||||
if (context == null)
|
||||
return null;
|
||||
|
||||
return context.GetAllPatternSkills();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드롭다운에서 선택한 스킬을 강제 발동합니다.
|
||||
/// </summary>
|
||||
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]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 실행 중인 스킬을 취소합니다.
|
||||
/// </summary>
|
||||
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<LayoutElement>().preferredHeight = 30f;
|
||||
|
||||
// 캡션 라벨
|
||||
GameObject captionGo = new GameObject("Caption", typeof(RectTransform), typeof(TextMeshProUGUI));
|
||||
captionGo.transform.SetParent(dropdownGo.transform, false);
|
||||
RectTransform captionRt = captionGo.GetComponent<RectTransform>();
|
||||
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<TextMeshProUGUI>();
|
||||
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<RectTransform>();
|
||||
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<Image>().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<RectTransform>();
|
||||
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<Image>().color = new Color(0.15f, 0.15f, 0.15f, 0.95f);
|
||||
|
||||
ScrollRect scroll = templateGo.GetComponent<ScrollRect>();
|
||||
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<RectTransform>();
|
||||
viewportRt.anchorMin = Vector2.zero;
|
||||
viewportRt.anchorMax = Vector2.one;
|
||||
viewportRt.sizeDelta = Vector2.zero;
|
||||
viewportRt.pivot = new Vector2(0f, 1f);
|
||||
viewportGo.GetComponent<Image>().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<RectTransform>();
|
||||
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<VerticalLayoutGroup>();
|
||||
vlg.childAlignment = TextAnchor.MiddleLeft;
|
||||
vlg.childControlWidth = true;
|
||||
vlg.childControlHeight = true;
|
||||
vlg.childForceExpandWidth = true;
|
||||
vlg.childForceExpandHeight = false;
|
||||
contentGo.GetComponent<ContentSizeFitter>().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<RectTransform>();
|
||||
itemRt.anchorMin = new Vector2(0f, 0.5f);
|
||||
itemRt.anchorMax = new Vector2(1f, 0.5f);
|
||||
itemRt.sizeDelta = new Vector2(0f, 20f);
|
||||
|
||||
LayoutElement itemLe = itemGo.AddComponent<LayoutElement>();
|
||||
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<RectTransform>();
|
||||
itemBgRt.anchorMin = Vector2.zero;
|
||||
itemBgRt.anchorMax = Vector2.one;
|
||||
itemBgRt.sizeDelta = Vector2.zero;
|
||||
itemBgGo.GetComponent<Image>().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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<TextMeshProUGUI>();
|
||||
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>();
|
||||
toggle.targetGraphic = itemBgGo.GetComponent<Image>();
|
||||
toggle.graphic = checkGo.GetComponent<Image>();
|
||||
toggle.isOn = false;
|
||||
|
||||
// 드롭다운 연결
|
||||
TMP_Dropdown dropdown = dropdownGo.GetComponent<TMP_Dropdown>();
|
||||
dropdown.targetGraphic = dropdownGo.GetComponent<Image>();
|
||||
dropdown.captionText = captionText;
|
||||
dropdown.itemText = itemLabelText;
|
||||
dropdown.template = templateRt;
|
||||
|
||||
return dropdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<RectTransform>();
|
||||
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<Image>();
|
||||
|
||||
@@ -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<Image>();
|
||||
|
||||
151
Assets/_Game/Scripts/UI/UIModeController.cs
Normal file
151
Assets/_Game/Scripts/UI/UIModeController.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Player;
|
||||
|
||||
namespace Colosseum.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 공용 UI 모드 컨트롤러.
|
||||
/// UIMode 입력(leftAlt)으로 커서 표시/숨김과 게임플레이 입력 차단을 토글합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class UIModeController : MonoBehaviour
|
||||
{
|
||||
private static UIModeController instance;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 활성화된 UIModeController 인스턴스
|
||||
/// </summary>
|
||||
public static UIModeController Instance => instance;
|
||||
|
||||
/// <summary>
|
||||
/// UI 모드 활성화 여부
|
||||
/// </summary>
|
||||
public bool IsUIModeActive { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// UI 모드 상태 변경 이벤트
|
||||
/// </summary>
|
||||
public event Action<bool> 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<PlayerSkillInput>(FindObjectsSortMode.None);
|
||||
for (int i = 0; i < skillInputs.Length; i++)
|
||||
{
|
||||
if (skillInputs[i] != null && skillInputs[i].IsOwner)
|
||||
{
|
||||
playerSkillInput = skillInputs[i];
|
||||
playerMovement = skillInputs[i].GetComponent<PlayerMovement>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI 모드를 수동으로 설정합니다.
|
||||
/// </summary>
|
||||
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
|
||||
2
Assets/_Game/Scripts/UI/UIModeController.cs.meta
Normal file
2
Assets/_Game/Scripts/UI/UIModeController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3faf1bf9fabb1c24bbe4c2c2f4b400da
|
||||
Reference in New Issue
Block a user