feat: 드로그 보스 AI 및 런타임 상태 구조 재구성

- 드로그 전투 컨텍스트를 BossBehaviorRuntimeState 중심 구조로 정리하고 BossEnemy, 패턴 액션, 조건 노드가 마지막 실행 결과와 phase 상태를 직접 사용하도록 갱신
- BT_Drog와 재빌드 에디터 스크립트를 확장해 phase 전환, 집행 결과 분기, 거리/쿨타임 기반 패턴 선택을 드로그 전용 자산과 노드 파라미터로 재구성
- 드로그 패턴/스킬/이펙트/애니메이션 플레이스홀더 자산을 재생성하고 보스 프리팹이 새 런타임 상태 및 등록 클립 구성을 참조하도록 정리
This commit is contained in:
2026-04-06 13:56:47 +09:00
parent 60275c6cd9
commit 904bc88d36
172 changed files with 98477 additions and 3490 deletions

View File

@@ -13,16 +13,19 @@ namespace Colosseum.Editor
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()
@@ -70,14 +73,11 @@ namespace Colosseum.Editor
DrawProgressBar("HP", hpPercent, GetHealthColor(hpPercent), $"{boss.CurrentHealth:F0} / {boss.MaxHealth:F0}");
// 상태 정보
EditorGUILayout.LabelField("현재 페이즈", $"{boss.CurrentPhaseIndex + 1} / {boss.TotalPhases}");
EditorGUILayout.LabelField("현재 페이즈", bossContext != null
? $"{bossContext.CurrentPatternPhase} / {bossContext.MaxPatternPhase}"
: "N/A");
EditorGUILayout.LabelField("상태", GetStatusText());
if (boss.CurrentPhase != null)
{
EditorGUILayout.LabelField("페이즈명", boss.CurrentPhase.PhaseName);
}
EditorGUI.indentLevel--;
}
@@ -93,53 +93,16 @@ namespace Colosseum.Editor
EditorGUI.indentLevel++;
var phasesProp = serializedObject.FindProperty("phases");
if (phasesProp == null || phasesProp.arraySize == 0)
if (bossContext == null)
{
EditorGUILayout.HelpBox("등록된 페이즈가 없습니다.", MessageType.Warning);
EditorGUILayout.HelpBox("BossBehaviorRuntimeState를 찾지 못했습니다.", MessageType.Warning);
EditorGUI.indentLevel--;
return;
}
for (int i = 0; i < phasesProp.arraySize; i++)
{
var phaseProp = phasesProp.GetArrayElementAtIndex(i);
var phase = phaseProp.objectReferenceValue as BossPhaseData;
if (phase == null)
continue;
bool isCurrentPhase = i == boss.CurrentPhaseIndex;
bool isCompleted = i < boss.CurrentPhaseIndex;
// 페이즈 헤더
GUIStyle phaseStyle = new GUIStyle(EditorStyles.foldout);
if (isCurrentPhase)
phaseStyle.fontStyle = FontStyle.Bold;
EditorGUILayout.BeginHorizontal();
// 상태 아이콘
string statusIcon = isCurrentPhase ? "▶" : (isCompleted ? "✓" : "○");
GUIContent phaseLabel = new GUIContent($"{statusIcon} Phase {i + 1}: {phase.PhaseName}");
EditorGUILayout.LabelField(phaseLabel, GUILayout.Width(200));
// 전환 조건
EditorGUILayout.LabelField($"[{phase.TransitionType}]", EditorStyles.miniLabel, GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
if (isCurrentPhase)
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField($"전환 조건: {GetTransitionConditionText(phase)}");
EditorGUILayout.LabelField($"경과 시간: {boss.PhaseElapsedTime:F1}초");
EditorGUI.indentLevel--;
}
EditorGUILayout.Space(2);
}
EditorGUILayout.LabelField("현재 Phase", bossContext.CurrentPatternPhase.ToString());
EditorGUILayout.LabelField("최대 Phase", bossContext.MaxPatternPhase.ToString());
EditorGUILayout.LabelField("경과 시간", $"{bossContext.PhaseElapsedTime:F1}초");
EditorGUI.indentLevel--;
}
@@ -191,12 +154,13 @@ namespace Colosseum.Editor
// 페이즈 강제 전환
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("페이즈 강제 전환", GUILayout.Width(120));
selectedPhaseIndex = EditorGUILayout.IntSlider(selectedPhaseIndex, 0, Mathf.Max(0, boss.TotalPhases - 1));
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)
if (Application.isPlaying && bossContext != null)
{
boss.ForcePhaseTransition(selectedPhaseIndex);
bossContext.SetCurrentPatternPhase(selectedPhaseIndex + 1);
}
}
EditorGUILayout.EndHorizontal();
@@ -206,9 +170,9 @@ namespace Colosseum.Editor
// 현재 페이즈 재시작
if (GUILayout.Button("현재 페이즈 재시작"))
{
if (Application.isPlaying)
if (Application.isPlaying && bossContext != null)
{
boss.RestartCurrentPhase();
bossContext.RestartCurrentPhaseTimer();
}
}
@@ -272,17 +236,17 @@ namespace Colosseum.Editor
EditorGUILayout.LabelField("커스텀 조건 설정", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("조건 ID:", GUILayout.Width(60));
string conditionId = EditorGUILayout.TextField("Enraged");
customConditionId = EditorGUILayout.TextField(customConditionId);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("활성화"))
{
boss.SetCustomCondition(conditionId, true);
bossContext?.SetPhaseCustomCondition(customConditionId, true);
}
if (GUILayout.Button("비활성화"))
{
boss.SetCustomCondition(conditionId, false);
bossContext?.SetPhaseCustomCondition(customConditionId, false);
}
EditorGUILayout.EndHorizontal();
@@ -359,25 +323,8 @@ namespace Colosseum.Editor
{
if (boss.IsDead)
return "<color=red>사망</color>";
if (boss.IsTransitioning)
return "<color=yellow>페이즈 전환 중</color>";
return "<color=green>활성</color>";
}
/// <summary>
/// 전환 조건 텍스트 반환
/// </summary>
private string GetTransitionConditionText(BossPhaseData phase)
{
return phase.TransitionType switch
{
PhaseTransitionType.HealthPercent => $"HP ≤ {phase.HealthPercentThreshold * 100:F0}%",
PhaseTransitionType.TimeElapsed => $"시간 ≥ {phase.TimeThreshold:F0}초",
PhaseTransitionType.CustomCondition => $"조건: {phase.CustomConditionId}",
PhaseTransitionType.Manual => "수동 전환",
_ => "알 수 없음"
};
}
}
}
#endif