feat: 드로그 BT 및 전투 패턴 재구성

- 드로그 BT를 페이즈 전환, 부활 트리거, 가중치 근접 패턴 중심으로 재구성

- 땅 울리기 및 콤보-기본기1_3 패턴/스킬/이펙트를 추가하고 기존 평타 파생 자산을 정리

- 드로그 행동 검증용 PlayMode/Editor 테스트와 관련 런타임 상태 추적을 추가
This commit is contained in:
2026-04-09 23:21:38 +09:00
parent 1307123029
commit 7776f7ed05
77 changed files with 449522 additions and 345357 deletions

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using Colosseum.AI;
using Colosseum.Abnormalities;
using Colosseum.Player;
using Colosseum.Skills;
using Unity.Behavior;
@@ -32,6 +33,8 @@ namespace Colosseum.Enemy
[RequireComponent(typeof(SkillController))]
public class BossBehaviorRuntimeState : NetworkBehaviour
{
public static event System.Action<GameObject, GameObject> PlayerRevivedBySkill;
[Header("References")]
[SerializeField] protected BossEnemy bossEnemy;
[SerializeField] protected EnemyBase enemyBase;
@@ -92,6 +95,9 @@ namespace Colosseum.Enemy
protected float currentPhaseStartTime;
protected BossPatternExecutionResult lastPatternExecutionResult;
protected BossPatternData lastExecutedPattern;
protected GameObject lastReviveCaster;
protected GameObject lastRevivedTarget;
protected float lastReviveEventTime = float.NegativeInfinity;
/// <summary>
/// 현재 전투 대상
@@ -202,7 +208,12 @@ namespace Colosseum.Enemy
ResetPhaseState();
if (!IsServer)
{
enabled = false;
return;
}
PlayerRevivedBySkill += HandlePlayerRevivedBySkill;
}
protected virtual void Update()
@@ -267,6 +278,41 @@ namespace Colosseum.Enemy
lastPatternExecutionResult = result;
}
/// <summary>
/// 부활 스킬 사용 사실을 보스 AI에 알립니다.
/// </summary>
public static void ReportPlayerRevivedBySkill(GameObject caster, GameObject revivedTarget)
{
PlayerRevivedBySkill?.Invoke(caster, revivedTarget);
}
/// <summary>
/// 최근 부활 트리거가 아직 유효한지 확인합니다.
/// </summary>
public bool HasRecentReviveTrigger(float maxAge)
{
return ResolveRecentReviveTriggerTarget(maxAge) != null;
}
/// <summary>
/// 최근 부활 트리거에서 우선 공격할 대상을 반환합니다.
/// </summary>
public GameObject ResolveRecentReviveTriggerTarget(float maxAge, bool preferCaster = true, bool fallbackToRevivedTarget = true)
{
if (Time.time - lastReviveEventTime > Mathf.Max(0f, maxAge))
return null;
GameObject preferredTarget = preferCaster ? lastReviveCaster : lastRevivedTarget;
if (IsValidReviveTriggerTarget(preferredTarget))
return preferredTarget;
if (!fallbackToRevivedTarget)
return null;
GameObject fallbackTarget = preferCaster ? lastRevivedTarget : lastReviveCaster;
return IsValidReviveTriggerTarget(fallbackTarget) ? fallbackTarget : null;
}
/// <summary>
/// 페이즈 커스텀 조건을 기록합니다.
/// </summary>
@@ -391,14 +437,41 @@ namespace Colosseum.Enemy
currentPhaseStartTime = Time.time;
lastPatternExecutionResult = BossPatternExecutionResult.None;
lastExecutedPattern = null;
lastReviveCaster = null;
lastRevivedTarget = null;
lastReviveEventTime = float.NegativeInfinity;
customPhaseConditions.Clear();
}
public override void OnNetworkDespawn()
{
if (IsServer)
PlayerRevivedBySkill -= HandlePlayerRevivedBySkill;
base.OnNetworkDespawn();
}
protected void HandlePlayerRevivedBySkill(GameObject caster, GameObject revivedTarget)
{
if (!IsServer)
return;
lastReviveCaster = caster;
lastRevivedTarget = revivedTarget;
lastReviveEventTime = Time.time;
LogDebug(nameof(BossBehaviorRuntimeState), $"부활 트리거 기록: 시전자={caster?.name ?? ""} / 대상={revivedTarget?.name ?? ""}");
}
protected bool IsValidReviveTriggerTarget(GameObject candidate)
{
if (candidate == null || !candidate.activeInHierarchy)
return false;
PlayerNetworkController player = candidate.GetComponent<PlayerNetworkController>();
return player != null && !player.IsDead;
}
private static bool HasAnimatorParameter(Animator animator, string parameterName, AnimatorControllerParameterType parameterType)
{
if (animator == null || string.IsNullOrEmpty(parameterName))