Files
Colosseum/Assets/_Game/Scripts/Editor/BossEnemyEditor.cs
dal4segno 904bc88d36 feat: 드로그 보스 AI 및 런타임 상태 구조 재구성
- 드로그 전투 컨텍스트를 BossBehaviorRuntimeState 중심 구조로 정리하고 BossEnemy, 패턴 액션, 조건 노드가 마지막 실행 결과와 phase 상태를 직접 사용하도록 갱신
- BT_Drog와 재빌드 에디터 스크립트를 확장해 phase 전환, 집행 결과 분기, 거리/쿨타임 기반 패턴 선택을 드로그 전용 자산과 노드 파라미터로 재구성
- 드로그 패턴/스킬/이펙트/애니메이션 플레이스홀더 자산을 재생성하고 보스 프리팹이 새 런타임 상태 및 등록 클립 구성을 참조하도록 정리
2026-04-06 13:56:47 +09:00

331 lines
11 KiB
C#

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using Colosseum.Enemy;
namespace Colosseum.Editor
{
/// <summary>
/// BossEnemy 커스텀 인스펙터.
/// 페이즈 정보, HP, 상태를 시각적으로 표시합니다.
/// </summary>
[CustomEditor(typeof(BossEnemy))]
public class BossEnemyEditor : UnityEditor.Editor
{
private BossEnemy boss;
private BossBehaviorRuntimeState bossContext;
private bool showPhaseDetails = true;
private bool showThreatInfo = true;
private bool showDebugTools = true;
private int selectedPhaseIndex = 0;
private float debugHPPercent = 1f;
private float debugHPValue = 0f;
private string customConditionId = "Enraged";
private void OnEnable()
{
boss = (BossEnemy)target;
bossContext = boss != null ? boss.GetComponent<BossBehaviorRuntimeState>() : null;
}
public override void OnInspectorGUI()
{
// 기본 인스펙터 그리기
DrawDefaultInspector();
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("런타임 디버그 정보는 플레이 모드에서만 표시됩니다.", MessageType.Info);
return;
}
EditorGUILayout.Space(10);
// 상태 요약
DrawStatusSummary();
EditorGUILayout.Space(10);
// 페이즈 정보
DrawPhaseInfo();
EditorGUILayout.Space(10);
// 위협 정보
DrawThreatInfo();
EditorGUILayout.Space(10);
// 디버그 도구
DrawDebugTools();
}
/// <summary>
/// 상태 요약 표시
/// </summary>
private void DrawStatusSummary()
{
EditorGUILayout.LabelField("상태 요약", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
// HP 바
float hpPercent = boss.MaxHealth > 0 ? boss.CurrentHealth / boss.MaxHealth : 0f;
DrawProgressBar("HP", hpPercent, GetHealthColor(hpPercent), $"{boss.CurrentHealth:F0} / {boss.MaxHealth:F0}");
// 상태 정보
EditorGUILayout.LabelField("현재 페이즈", bossContext != null
? $"{bossContext.CurrentPatternPhase} / {bossContext.MaxPatternPhase}"
: "N/A");
EditorGUILayout.LabelField("상태", GetStatusText());
EditorGUI.indentLevel--;
}
/// <summary>
/// 페이즈 상세 정보 표시
/// </summary>
private void DrawPhaseInfo()
{
showPhaseDetails = EditorGUILayout.Foldout(showPhaseDetails, "페이즈 상세 정보", true);
if (!showPhaseDetails)
return;
EditorGUI.indentLevel++;
if (bossContext == null)
{
EditorGUILayout.HelpBox("BossBehaviorRuntimeState를 찾지 못했습니다.", MessageType.Warning);
EditorGUI.indentLevel--;
return;
}
EditorGUILayout.LabelField("현재 Phase", bossContext.CurrentPatternPhase.ToString());
EditorGUILayout.LabelField("최대 Phase", bossContext.MaxPatternPhase.ToString());
EditorGUILayout.LabelField("경과 시간", $"{bossContext.PhaseElapsedTime:F1}초");
EditorGUI.indentLevel--;
}
/// <summary>
/// 위협 정보 표시
/// </summary>
private void DrawThreatInfo()
{
showThreatInfo = EditorGUILayout.Foldout(showThreatInfo, "위협 정보", true);
if (!showThreatInfo)
return;
EditorGUI.indentLevel++;
if (!boss.UseThreatSystem)
{
EditorGUILayout.HelpBox("위협 시스템이 비활성화되어 있습니다.", MessageType.Info);
EditorGUI.indentLevel--;
return;
}
EditorGUILayout.LabelField("위협 테이블", EditorStyles.boldLabel);
EditorGUILayout.TextArea(boss.GetThreatDebugSummary(), GUILayout.MinHeight(70f));
if (GUILayout.Button("위협 초기화"))
{
boss.ClearAllThreat();
}
EditorGUI.indentLevel--;
}
/// <summary>
/// 디버그 도구 표시
/// </summary>
private void DrawDebugTools()
{
showDebugTools = EditorGUILayout.Foldout(showDebugTools, "디버그 도구", true);
if (!showDebugTools)
return;
EditorGUI.indentLevel++;
EditorGUILayout.HelpBox("이 도구는 서버에서만 작동합니다.", MessageType.Info);
// 페이즈 강제 전환
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("페이즈 강제 전환", GUILayout.Width(120));
int maxPhaseIndex = bossContext != null ? Mathf.Max(0, bossContext.MaxPatternPhase - 1) : 0;
selectedPhaseIndex = EditorGUILayout.IntSlider(selectedPhaseIndex, 0, maxPhaseIndex);
if (GUILayout.Button("전환", GUILayout.Width(60)))
{
if (Application.isPlaying && bossContext != null)
{
bossContext.SetCurrentPatternPhase(selectedPhaseIndex + 1);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// 현재 페이즈 재시작
if (GUILayout.Button("현재 페이즈 재시작"))
{
if (Application.isPlaying && bossContext != null)
{
bossContext.RestartCurrentPhaseTimer();
}
}
EditorGUILayout.Space(5);
// 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%"))
{
SetBossHP(0.1f);
}
if (GUILayout.Button("HP 30%"))
{
SetBossHP(0.3f);
}
if (GUILayout.Button("HP 50%"))
{
SetBossHP(0.5f);
}
if (GUILayout.Button("HP 100%"))
{
SetBossHP(1f);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// 커스텀 조건
EditorGUILayout.LabelField("커스텀 조건 설정", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("조건 ID:", GUILayout.Width(60));
customConditionId = EditorGUILayout.TextField(customConditionId);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("활성화"))
{
bossContext?.SetPhaseCustomCondition(customConditionId, true);
}
if (GUILayout.Button("비활성화"))
{
bossContext?.SetPhaseCustomCondition(customConditionId, false);
}
EditorGUILayout.EndHorizontal();
EditorGUI.indentLevel--;
}
/// <summary>
/// HP 설정 (서버에서만)
/// </summary>
private void SetBossHP(float percent)
{
if (!Application.isPlaying)
return;
float targetHP = boss.MaxHealth * percent;
float damage = boss.CurrentHealth - targetHP;
if (damage > 0)
{
boss.TakeDamage(damage);
}
else if (damage < 0)
{
boss.Heal(-damage);
}
}
/// <summary>
/// 진행 바 그리기
/// </summary>
private void DrawProgressBar(string label, float value, Color color, string text = "")
{
Rect rect = EditorGUILayout.GetControlRect();
rect.height = 20f;
// 레이블
Rect labelRect = new Rect(rect.x, rect.y, 60, rect.height);
EditorGUI.LabelField(labelRect, label);
// 바
Rect barRect = new Rect(rect.x + 65, rect.y, rect.width - 65, rect.height);
EditorGUI.DrawRect(barRect, new Color(0.2f, 0.2f, 0.2f));
Rect fillRect = new Rect(barRect.x, barRect.y, barRect.width * Mathf.Clamp01(value), barRect.height);
EditorGUI.DrawRect(fillRect, color);
// 텍스트
if (!string.IsNullOrEmpty(text))
{
GUIStyle centeredStyle = new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleCenter
};
EditorGUI.LabelField(barRect, text, centeredStyle);
}
}
/// <summary>
/// HP 비율에 따른 색상 반환
/// </summary>
private Color GetHealthColor(float percent)
{
if (percent > 0.6f)
return new Color(0.2f, 0.8f, 0.2f); // 녹색
if (percent > 0.3f)
return new Color(0.9f, 0.7f, 0.1f); // 노란색
return new Color(0.9f, 0.2f, 0.2f); // 빨간색
}
/// <summary>
/// 상태 텍스트 반환
/// </summary>
private string GetStatusText()
{
if (boss.IsDead)
return "<color=red>사망</color>";
return "<color=green>활성</color>";
}
}
}
#endif