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

@@ -25,7 +25,7 @@ public abstract partial class BossPatternActionBase : Action
protected BossEnemy bossEnemy;
protected EnemyBase enemyBase;
protected SkillController skillController;
protected BossCombatBehaviorContext combatBehaviorContext;
protected BossBehaviorRuntimeState runtimeState;
protected UnityEngine.AI.NavMeshAgent navMeshAgent;
protected AbnormalityManager abnormalityManager;
@@ -34,6 +34,7 @@ public abstract partial class BossPatternActionBase : Action
private int currentStepIndex;
private bool isWaiting;
private float waitEndTime;
private bool isSkillStepExecuting;
private bool isChargeWaiting;
private float chargeEndTime;
@@ -47,6 +48,11 @@ public abstract partial class BossPatternActionBase : Action
/// </summary>
protected abstract bool TryResolvePattern(out BossPatternData pattern, out GameObject target);
/// <summary>
/// 패턴이 의미 있는 결과와 함께 종료되었을 때, 실패 결과도 다음 노드로 넘길지 결정합니다.
/// </summary>
protected virtual bool ContinueSequenceOnResolvedFailure => false;
protected override Status OnStart()
{
ResolveReferences();
@@ -55,13 +61,13 @@ public abstract partial class BossPatternActionBase : Action
if (!IsReady())
return Status.Failure;
if (combatBehaviorContext.IsBehaviorSuppressed)
if (runtimeState.IsBehaviorSuppressed)
{
StopMovement();
return Status.Failure;
}
if (bossEnemy.IsDead || bossEnemy.IsTransitioning)
if (bossEnemy.IsDead)
return Status.Failure;
if (skillController.IsPlayingAnimation)
@@ -72,10 +78,16 @@ public abstract partial class BossPatternActionBase : Action
activePattern = pattern;
activeTarget = target;
runtimeState.BeginPatternExecution(activePattern);
runtimeState.WasChargeBroken = false;
runtimeState.LastChargeStaggerDuration = 0f;
if (Target != null)
Target.Value = target;
runtimeState.SetCurrentTarget(target);
StopMovement();
return ExecuteCurrentStep();
}
@@ -83,16 +95,16 @@ public abstract partial class BossPatternActionBase : Action
protected override Status OnUpdate()
{
if (!IsReady() || activePattern == null)
return Status.Failure;
return FinalizePatternFailure(BossPatternExecutionResult.Failed);
if (combatBehaviorContext.IsBehaviorSuppressed)
if (runtimeState.IsBehaviorSuppressed)
{
StopMovement();
return Status.Failure;
return FinalizePatternFailure(BossPatternExecutionResult.Cancelled);
}
if (bossEnemy.IsDead || bossEnemy.IsTransitioning)
return Status.Failure;
if (bossEnemy.IsDead)
return FinalizePatternFailure(BossPatternExecutionResult.Cancelled);
if (isChargeWaiting)
{
@@ -100,9 +112,11 @@ public abstract partial class BossPatternActionBase : Action
{
EndChargeWait(broken: true);
skillController?.CancelSkill(SkillCancelReason.Interrupt);
runtimeState.WasChargeBroken = true;
runtimeState.SetPatternCooldown(activePattern);
LogDebug($"충전 차단 성공: 누적 {chargeAccumulatedDamage:F1} / 필요 {chargeRequiredDamage:F1}");
CombatBalanceTracker.RecordBossEvent("집행 개시 차단 성공");
return Status.Failure;
return FinalizeResolvedPattern(BossPatternExecutionResult.Failed);
}
if (Time.time < chargeEndTime)
@@ -117,16 +131,21 @@ public abstract partial class BossPatternActionBase : Action
isWaiting = false;
}
else if (skillController.IsPlayingAnimation)
else if (isSkillStepExecuting)
{
return Status.Running;
if (skillController.IsPlayingAnimation)
return Status.Running;
isSkillStepExecuting = false;
if (skillController.LastExecutionResult != SkillExecutionResult.Completed)
return FinalizePatternFailure(BossPatternExecutionResult.Cancelled);
}
currentStepIndex++;
if (currentStepIndex >= activePattern.Steps.Count)
{
UsePatternAction.MarkPatternUsed(GameObject, activePattern);
return Status.Success;
runtimeState.SetPatternCooldown(activePattern);
return FinalizeResolvedPattern(BossPatternExecutionResult.Succeeded);
}
return ExecuteCurrentStep();
@@ -205,7 +224,7 @@ public abstract partial class BossPatternActionBase : Action
protected void LogDebug(string message)
{
combatBehaviorContext?.LogDebug(GetType().Name, message);
runtimeState?.LogDebug(GetType().Name, message);
}
private Status ExecuteCurrentStep()
@@ -241,7 +260,7 @@ public abstract partial class BossPatternActionBase : Action
{
if (activePattern != null && activePattern.SkipJumpStepOnNoTarget)
{
UsePatternAction.MarkPatternUsed(GameObject, activePattern);
runtimeState.SetPatternCooldown(activePattern);
LogDebug($"점프 대상 없음, 조합 패턴 조기 종료: {activePattern.PatternName}");
return Status.Success;
}
@@ -253,12 +272,13 @@ public abstract partial class BossPatternActionBase : Action
enemyBase?.SetJumpTarget(skillTarget.transform.position);
}
if (!skillController.ExecuteSkill(step.Skill))
if (!skillController.ExecuteSkill(step.Skill, skillTarget))
{
Debug.LogWarning($"[{GetType().Name}] 스킬 실행 실패: {step.Skill.SkillName}");
return Status.Failure;
return FinalizePatternFailure(BossPatternExecutionResult.Failed);
}
isSkillStepExecuting = true;
LogDebug($"패턴 실행: {activePattern.PatternName} / Step={currentStepIndex} / Skill={step.Skill.SkillName}");
return Status.Running;
}
@@ -308,7 +328,7 @@ public abstract partial class BossPatternActionBase : Action
if (broken && activeChargeData != null)
{
combatBehaviorContext.LastChargeStaggerDuration = activeChargeData.StaggerDuration;
runtimeState.LastChargeStaggerDuration = activeChargeData.StaggerDuration;
}
activeChargeData = null;
@@ -327,7 +347,7 @@ public abstract partial class BossPatternActionBase : Action
private bool IsReady()
{
return bossEnemy != null && enemyBase != null && skillController != null && combatBehaviorContext != null;
return bossEnemy != null && enemyBase != null && skillController != null && runtimeState != null;
}
private void ResolveReferences()
@@ -341,8 +361,8 @@ public abstract partial class BossPatternActionBase : Action
if (skillController == null)
skillController = GameObject.GetComponent<SkillController>();
if (combatBehaviorContext == null)
combatBehaviorContext = GameObject.GetComponent<BossCombatBehaviorContext>();
if (runtimeState == null)
runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
if (navMeshAgent == null)
navMeshAgent = GameObject.GetComponent<UnityEngine.AI.NavMeshAgent>();
@@ -360,6 +380,22 @@ public abstract partial class BossPatternActionBase : Action
activeTarget = null;
currentStepIndex = 0;
isWaiting = false;
isSkillStepExecuting = false;
waitEndTime = 0f;
}
private Status FinalizeResolvedPattern(BossPatternExecutionResult result)
{
runtimeState?.CompletePatternExecution(activePattern, result);
if (result == BossPatternExecutionResult.Failed && ContinueSequenceOnResolvedFailure)
return Status.Success;
return Status.Success;
}
private Status FinalizePatternFailure(BossPatternExecutionResult result)
{
runtimeState?.CompletePatternExecution(activePattern, result);
return Status.Failure;
}
}