diff --git a/Assets/Scenes/Test.unity b/Assets/Scenes/Test.unity
index fb36720a..e5d12b08 100644
--- a/Assets/Scenes/Test.unity
+++ b/Assets/Scenes/Test.unity
@@ -1184,6 +1184,7 @@ RectTransform:
- {fileID: 678443228}
- {fileID: 1221067101607693524}
- {fileID: 1943804129}
+ - {fileID: 985852648}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -5040,6 +5041,58 @@ Transform:
m_CorrespondingSourceObject: {fileID: 7132605379903659868, guid: 5b4ac53b97612ae4392b84786de0d50d, type: 3}
m_PrefabInstance: {fileID: 539760736}
m_PrefabAsset: {fileID: 0}
+--- !u!1 &985852647
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 985852648}
+ - component: {fileID: 985852649}
+ m_Layer: 0
+ m_Name: DebugPanel
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &985852648
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 985852647}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 260528176}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &985852649
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 985852647}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 7b7611f77d92f8e41bfe5dfb5ac1768f, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.DebugPanelUI
+ stunAbnormalityData: {fileID: 0}
+ silenceAbnormalityData: {fileID: 0}
+ panelWidth: 280
+ panelMaxHeight: 500
--- !u!1001 &989227100
PrefabInstance:
m_ObjectHideFlags: 0
diff --git a/Assets/_Game/Scripts/Editor/BossEnemyEditor.cs b/Assets/_Game/Scripts/Editor/BossEnemyEditor.cs
index c1ccfe4d..1f066d05 100644
--- a/Assets/_Game/Scripts/Editor/BossEnemyEditor.cs
+++ b/Assets/_Game/Scripts/Editor/BossEnemyEditor.cs
@@ -17,6 +17,8 @@ namespace Colosseum.Editor
private bool showThreatInfo = true;
private bool showDebugTools = true;
private int selectedPhaseIndex = 0;
+ private float debugHPPercent = 1f;
+ private float debugHPValue = 0f;
private void OnEnable()
{
@@ -214,6 +216,37 @@ namespace Colosseum.Editor
// HP 조작
EditorGUILayout.LabelField("HP 조작", EditorStyles.boldLabel);
+ EditorGUI.indentLevel++;
+
+ // 현재 HP 표시
+ EditorGUILayout.LabelField("현재",
+ $"{boss.CurrentHealth:F0} / {boss.MaxHealth:F0} ({(boss.MaxHealth > 0 ? boss.CurrentHealth / boss.MaxHealth * 100f : 0f):F1}%)");
+
+ // 퍼센트 슬라이더
+ EditorGUI.BeginChangeCheck();
+ debugHPPercent = EditorGUILayout.Slider("퍼센트", debugHPPercent, 0f, 1f);
+ if (EditorGUI.EndChangeCheck())
+ {
+ SetBossHP(debugHPPercent);
+ }
+
+ // 직접 HP 값 입력
+ EditorGUILayout.BeginHorizontal();
+ debugHPValue = EditorGUILayout.FloatField("직접 입력", debugHPValue);
+ EditorGUILayout.LabelField($"/ {boss.MaxHealth:F0}", GUILayout.Width(80));
+ if (GUILayout.Button("적용", GUILayout.Width(60)))
+ {
+ float clamped = Mathf.Clamp(debugHPValue, 0f, boss.MaxHealth);
+ float percent = boss.MaxHealth > 0 ? clamped / boss.MaxHealth : 0f;
+ debugHPPercent = percent;
+ SetBossHP(percent);
+ }
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUI.indentLevel--;
+
+ // 빠른 HP 설정 버튼
+ EditorGUILayout.Space(3);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("HP 10%"))
{
diff --git a/Assets/_Game/Scripts/Editor/DebugBossMenuItems.cs b/Assets/_Game/Scripts/Editor/DebugBossMenuItems.cs
new file mode 100644
index 00000000..e7371767
--- /dev/null
+++ b/Assets/_Game/Scripts/Editor/DebugBossMenuItems.cs
@@ -0,0 +1,255 @@
+using System.Text;
+
+using Colosseum.Enemy;
+using Colosseum.Abnormalities;
+
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.Networking;
+
+namespace Colosseum.Editor
+{
+ ///
+ /// MCP (execute_menu_item) 또는 에디터 메뉴에서 호출 가능한 보스 디버그 커맨드.
+ /// 파라미터가 필요한 연산은 static 필드를 설정한 뒤 Custom MenuItem을 호출합니다.
+ ///
+ public static class DebugBossMenuItems
+ {
+ private const string StunAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Stun.asset";
+ private const string SilenceAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Silence.asset";
+
+ // ── MCP가 설정 후 Custom 메뉴 호출하는 파라미터 필드 ──
+
+ ///
+ /// Set Boss HP Custom에서 사용할 퍼센트 (0.0 ~ 1.0).
+ /// MCP는 script_apply_edits로 이 값을 변경한 뒤 Custom 메뉴를 호출합니다.
+ ///
+ public static float customHPPercent = 0.5f;
+
+ ///
+ /// Force Phase Custom에서 사용할 페이즈 인덱스 (0-based).
+ ///
+ public static int customPhaseIndex = 0;
+
+ ///
+ /// Apply Shield Custom에서 사용할 보호막 수치.
+ ///
+ public static float customShieldAmount = 1000f;
+
+ // ── 보스 HP ──
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Set HP 10%")]
+ private static void SetHP10() => SetBossHP(0.1f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Set HP 25%")]
+ private static void SetHP25() => SetBossHP(0.25f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Set HP 50%")]
+ private static void SetHP50() => SetBossHP(0.5f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Set HP 75%")]
+ private static void SetHP75() => SetBossHP(0.75f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Set HP 100%")]
+ private static void SetHP100() => SetBossHP(1f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Set HP Custom")]
+ private static void SetHPCustom() => SetBossHP(Mathf.Clamp01(customHPPercent));
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Full Heal")]
+ private static void FullHeal()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+ boss.Heal(boss.MaxHealth);
+ Debug.Log($"[Debug] 보스 HP 풀회복 | HP={boss.CurrentHealth:F0}/{boss.MaxHealth:F0}");
+ }
+
+ // ── 보스 제어 ──
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Force Phase 0")]
+ private static void ForcePhase0() => ForcePhase(0);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Force Phase 1")]
+ private static void ForcePhase1() => ForcePhase(1);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Force Phase 2")]
+ private static void ForcePhase2() => ForcePhase(2);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Force Phase Custom")]
+ private static void ForcePhaseCustom() => ForcePhase(customPhaseIndex);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Restart Current Phase")]
+ private static void RestartPhase()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+ boss.RestartCurrentPhase();
+ Debug.Log($"[Debug] 보스 현재 페이즈 재시작 | Phase={boss.CurrentPhaseIndex}");
+ }
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Respawn")]
+ private static void Respawn()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+ boss.Respawn();
+ Debug.Log($"[Debug] 보스 리스폰 | HP={boss.CurrentHealth:F0}/{boss.MaxHealth:F0}");
+ }
+
+ // ── 보호막 ──
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Apply Shield 500")]
+ private static void ApplyShield500() => ApplyShield(500f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Apply Shield 1000")]
+ private static void ApplyShield1000() => ApplyShield(1000f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Apply Shield 5000")]
+ private static void ApplyShield5000() => ApplyShield(5000f);
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Apply Shield Custom")]
+ private static void ApplyShieldCustom() => ApplyShield(Mathf.Max(0f, customShieldAmount));
+
+ // ── 이상상태 ──
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Apply Stun")]
+ private static void ApplyStun()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+
+ AbnormalityData data = AssetDatabase.LoadAssetAtPath(StunAbnormalityPath);
+ if (data == null)
+ {
+ Debug.LogWarning($"[Debug] 기절 에셋을 찾지 못했습니다: {StunAbnormalityPath}");
+ return;
+ }
+
+ AbnormalityManager am = boss.GetComponent();
+ if (am != null)
+ {
+ am.ApplyAbnormality(data, boss.gameObject);
+ Debug.Log($"[Debug] 보스 기절 적용 | {data.abnormalityName}");
+ }
+ }
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Apply Silence")]
+ private static void ApplySilence()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+
+ AbnormalityData data = AssetDatabase.LoadAssetAtPath(SilenceAbnormalityPath);
+ if (data == null)
+ {
+ Debug.LogWarning($"[Debug] 침묵 에셋을 찾지 못했습니다: {SilenceAbnormalityPath}");
+ return;
+ }
+
+ AbnormalityManager am = boss.GetComponent();
+ if (am != null)
+ {
+ am.ApplyAbnormality(data, boss.gameObject);
+ Debug.Log($"[Debug] 보스 침묵 적용 | {data.abnormalityName}");
+ }
+ }
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Clear Threat")]
+ private static void ClearThreat()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+ boss.ClearAllThreat();
+ Debug.Log("[Debug] 보스 위협 초기화");
+ }
+
+ // ── 상태 조회 ──
+
+ [MenuItem("Tools/Colosseum/Debug/Boss/Log Status")]
+ private static void LogStatus()
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine($"[Debug] 보스 상태 | {boss.name}");
+ sb.AppendLine($" HP: {boss.CurrentHealth:F0} / {boss.MaxHealth:F0} ({(boss.MaxHealth > 0 ? boss.CurrentHealth / boss.MaxHealth * 100f : 0f):F1}%)");
+ sb.AppendLine($" Shield: {boss.Shield:F0}");
+ sb.AppendLine($" Phase: {boss.CurrentPhaseIndex + 1} / {boss.TotalPhases}");
+ sb.AppendLine($" IsDead: {boss.IsDead}");
+
+ if (boss.CurrentPhase != null)
+ sb.AppendLine($" PhaseName: {boss.CurrentPhase.PhaseName}");
+
+ Debug.Log(sb.ToString());
+ }
+
+ // ── 내부 구현 ──
+
+ ///
+ /// 활성 보스 찾기
+ ///
+ private static BossEnemy FindBoss()
+ {
+ if (!EditorApplication.isPlaying)
+ {
+ Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
+ return null;
+ }
+
+ BossEnemy boss = BossEnemy.ActiveBoss != null
+ ? BossEnemy.ActiveBoss
+ : Object.FindFirstObjectByType();
+
+ if (boss == null)
+ Debug.LogWarning("[Debug] 활성 보스를 찾지 못했습니다.");
+
+ return boss;
+ }
+
+ ///
+ /// 보스 HP를 퍼센트로 설정
+ ///
+ private static void SetBossHP(float percent)
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+
+ float targetHP = boss.MaxHealth * percent;
+ float diff = boss.CurrentHealth - targetHP;
+
+ if (diff > 0f)
+ boss.TakeDamage(diff);
+ else if (diff < 0f)
+ boss.Heal(-diff);
+
+ Debug.Log($"[Debug] 보스 HP 설정 {percent * 100f:F0}% | HP={boss.CurrentHealth:F0}/{boss.MaxHealth:F0}");
+ }
+
+ ///
+ /// 페이즈 강제 전환
+ ///
+ private static void ForcePhase(int index)
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+
+ index = Mathf.Clamp(index, 0, Mathf.Max(0, boss.TotalPhases - 1));
+ boss.ForcePhaseTransition(index);
+ Debug.Log($"[Debug] 보스 페이즈 강제 전환 | Phase={index}");
+ }
+
+ ///
+ /// 보호막 적용
+ ///
+ private static void ApplyShield(float amount)
+ {
+ BossEnemy boss = FindBoss();
+ if (boss == null) return;
+
+ boss.ApplyShield(amount, 30f);
+ Debug.Log($"[Debug] 보스 보호막 적용 | Amount={amount:F0} | Total={boss.Shield:F0}");
+ }
+ }
+}
diff --git a/Assets/_Game/Scripts/Editor/DebugBossMenuItems.cs.meta b/Assets/_Game/Scripts/Editor/DebugBossMenuItems.cs.meta
new file mode 100644
index 00000000..5253fae8
--- /dev/null
+++ b/Assets/_Game/Scripts/Editor/DebugBossMenuItems.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b710fae394e79d44fa88104e3412c04a
\ No newline at end of file
diff --git a/Assets/_Game/Scripts/UI/DebugPanelUI.cs b/Assets/_Game/Scripts/UI/DebugPanelUI.cs
new file mode 100644
index 00000000..8a270bc9
--- /dev/null
+++ b/Assets/_Game/Scripts/UI/DebugPanelUI.cs
@@ -0,0 +1,617 @@
+#if UNITY_EDITOR || DEVELOPMENT_BUILD
+
+using System;
+
+using UnityEngine;
+using UnityEngine.UI;
+using TMPro;
+
+using Unity.Netcode;
+
+using Colosseum.Enemy;
+using Colosseum.Abnormalities;
+
+namespace Colosseum.UI
+{
+ ///
+ /// 런타임 디버그 패널 UI.
+ /// 호스트(서버)에서 보스 HP, 페이즈, 보호막 등을 조작할 수 있는 접이식 HUD 패널입니다.
+ ///
+ public class DebugPanelUI : MonoBehaviour
+ {
+ [Header("Abnormality Data (Runtime)")]
+ [Tooltip("기절 이상상태 데이터")]
+ [SerializeField] private AbnormalityData stunAbnormalityData;
+ [Tooltip("침묵 이상상태 데이터")]
+ [SerializeField] private AbnormalityData silenceAbnormalityData;
+
+ [Header("Settings")]
+ [Tooltip("패널 너비")]
+ [SerializeField] private float panelWidth = 280f;
+ [Tooltip("패널 최대 높이")]
+ [SerializeField] private float panelMaxHeight = 500f;
+
+ // 보스 캐시
+ private BossEnemy cachedBoss;
+ private bool suppressSliderCallback;
+
+ // UI 참조
+ private GameObject toggleButtonObject;
+ private GameObject panelRoot;
+ private bool isPanelOpen;
+
+ // 보스 HP UI
+ private TMP_Text hpInfoText;
+ private Slider hpSlider;
+ private Image hpSliderFillImage;
+ private TMP_InputField hpInputField;
+
+ // 보스 제어 UI
+ private TMP_InputField phaseInputField;
+
+ // 보호막 UI
+ private TMP_InputField shieldAmountField;
+
+ private void Awake()
+ {
+ BuildUI();
+ }
+
+ private void Start()
+ {
+ if (panelRoot != null)
+ panelRoot.SetActive(false);
+ }
+
+ private void Update()
+ {
+ RefreshBoss();
+ if (cachedBoss == null)
+ return;
+
+ UpdateHPDisplay();
+ }
+
+ ///
+ /// 보스 참조 새로고침
+ ///
+ private void RefreshBoss()
+ {
+ if (cachedBoss != null && cachedBoss.gameObject.activeInHierarchy)
+ return;
+
+ cachedBoss = BossEnemy.ActiveBoss;
+ }
+
+ ///
+ /// 보스 없음 여부 반환
+ ///
+ private bool NoBoss => cachedBoss == null;
+
+ ///
+ /// 서버 권한 확인
+ ///
+ private bool IsHost => NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer;
+
+ // ──────────────────────────────────────────────────
+ // UI 빌드
+ // ──────────────────────────────────────────────────
+
+ ///
+ /// 전체 UI 트리 생성
+ ///
+ private void BuildUI()
+ {
+ BuildToggleButton();
+ BuildPanel();
+ }
+
+ ///
+ /// 토글 버튼 생성 (우측 하단 고정)
+ ///
+ private void BuildToggleButton()
+ {
+ toggleButtonObject = new GameObject("DebugToggle",
+ typeof(RectTransform), typeof(Image), typeof(Button));
+ toggleButtonObject.transform.SetParent(transform, false);
+
+ RectTransform r = toggleButtonObject.GetComponent();
+ r.anchorMin = new Vector2(1f, 0f);
+ r.anchorMax = new Vector2(1f, 0f);
+ r.pivot = new Vector2(1f, 0f);
+ r.anchoredPosition = new Vector2(-10f, 10f);
+ r.sizeDelta = new Vector2(80f, 36f);
+
+ toggleButtonObject.GetComponent().color = new Color(0.15f, 0.15f, 0.15f, 0.92f);
+
+ TMP_Text label = MakeTextChild("Label", toggleButtonObject.transform);
+ label.text = "Debug";
+ label.fontSize = 16f;
+ label.fontStyle = FontStyles.Bold;
+ label.alignment = TextAlignmentOptions.Center;
+ label.color = new Color(0.8f, 0.8f, 0.8f);
+
+ toggleButtonObject.GetComponent