feat: 드로그 공통 보스 BT 프레임워크 정리
- 보스 공통 전투 컨텍스트와 패턴 역할 기반 BT 액션을 추가 - 드로그 패턴 선택을 다운 추가타, 도약, 기본 및 보조 패턴 우선순위 브랜치로 이관 - BT_Drog authoring 그래프를 공통 구조에 맞게 재구성 - 드로그 전용 BT 헬퍼를 정리하고 공통 베이스 액션으로 통합 - 플레이 검증으로 도약, 기본 패턴, 내려찍기, 다운 추가타 루프를 확인
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
using System;
|
||||
|
||||
using Colosseum;
|
||||
using Colosseum.AI;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Enemy;
|
||||
using Colosseum.Skills;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
using Action = Unity.Behavior.Action;
|
||||
|
||||
/// <summary>
|
||||
@@ -17,11 +23,13 @@ public partial class UsePatternAction : Action
|
||||
[SerializeReference] public BlackboardVariable<BossPatternData> Pattern;
|
||||
[SerializeReference] public BlackboardVariable<GameObject> Target;
|
||||
|
||||
private static readonly System.Collections.Generic.Dictionary<string, float> patternReadyTimes =
|
||||
new System.Collections.Generic.Dictionary<string, float>();
|
||||
|
||||
private SkillController skillController;
|
||||
private int currentStepIndex;
|
||||
private float waitEndTime;
|
||||
private bool isWaiting;
|
||||
private float lastUsedTime = float.MinValue;
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
@@ -31,13 +39,21 @@ public partial class UsePatternAction : Action
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
if (Time.time - lastUsedTime < Pattern.Value.Cooldown)
|
||||
if (!IsPatternReady(GameObject, Pattern.Value))
|
||||
{
|
||||
LogDebug($"쿨다운 중: {Pattern.Value.PatternName}");
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
if (Pattern.Value.Steps.Count == 0)
|
||||
{
|
||||
LogDebug("스텝이 비어 있는 패턴이라 실패합니다.");
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
if (RequiresJumpTarget(Pattern.Value) && ResolveJumpTarget() == null)
|
||||
{
|
||||
LogDebug($"점프 타겟을 찾지 못해 실패: {Pattern.Value.PatternName}");
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
@@ -76,7 +92,7 @@ public partial class UsePatternAction : Action
|
||||
|
||||
if (currentStepIndex >= Pattern.Value.Steps.Count)
|
||||
{
|
||||
lastUsedTime = Time.time;
|
||||
MarkPatternUsed(GameObject, Pattern.Value);
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
@@ -106,6 +122,16 @@ public partial class UsePatternAction : Action
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
GameObject jumpTarget = null;
|
||||
if (step.Skill.JumpToTarget)
|
||||
{
|
||||
jumpTarget = ResolveJumpTarget();
|
||||
if (jumpTarget == null)
|
||||
{
|
||||
return Status.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
bool success = skillController.ExecuteSkill(step.Skill);
|
||||
if (!success)
|
||||
{
|
||||
@@ -113,15 +139,101 @@ public partial class UsePatternAction : Action
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
LogDebug($"패턴 실행: {Pattern.Value.PatternName} / Step={currentStepIndex} / Skill={step.Skill.SkillName}");
|
||||
|
||||
// jumpToTarget 스킬이면 타겟 위치 전달
|
||||
if (step.Skill.JumpToTarget)
|
||||
{
|
||||
if (Target?.Value == null)
|
||||
Debug.LogWarning($"[UsePatternAction] '{step.Skill.SkillName}'은 JumpToTarget 스킬이지만 Target이 바인딩되지 않았습니다.");
|
||||
else
|
||||
GameObject.GetComponent<Colosseum.Enemy.EnemyBase>()?.SetJumpTarget(Target.Value.transform.position);
|
||||
GameObject.GetComponent<EnemyBase>()?.SetJumpTarget(jumpTarget.transform.position);
|
||||
}
|
||||
|
||||
return Status.Running;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 점프 대상이 근접 상태라면, 더 멀리 있는 유효 타겟으로 재선택합니다.
|
||||
/// 도약 패턴이 전방 탱커 대신 원거리 플레이어를 징벌할 수 있도록 합니다.
|
||||
/// </summary>
|
||||
private GameObject ResolveJumpTarget()
|
||||
{
|
||||
GameObject currentTarget = Target?.Value;
|
||||
EnemyBase enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
float maxJumpDistance = enemyBase?.Data != null ? enemyBase.Data.AggroRange : 20f;
|
||||
return IsValidJumpTarget(currentTarget, maxJumpDistance) ? currentTarget : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 점프 타겟의 생존 여부, 팀, 거리 조건을 검사합니다.
|
||||
/// </summary>
|
||||
private bool IsValidJumpTarget(GameObject candidate, float maxDistance)
|
||||
{
|
||||
if (candidate == null || !candidate.activeInHierarchy)
|
||||
return false;
|
||||
|
||||
if (Team.IsSameTeam(GameObject, candidate))
|
||||
return false;
|
||||
|
||||
IDamageable damageable = candidate.GetComponent<IDamageable>();
|
||||
if (damageable != null && damageable.IsDead)
|
||||
return false;
|
||||
|
||||
float distance = Vector3.Distance(GameObject.transform.position, candidate.transform.position);
|
||||
return distance <= maxDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패턴 안에 점프 타겟이 필요한 스텝이 있는지 확인합니다.
|
||||
/// </summary>
|
||||
private static bool RequiresJumpTarget(BossPatternData pattern)
|
||||
{
|
||||
if (pattern == null || pattern.Steps == null)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < pattern.Steps.Count; i++)
|
||||
{
|
||||
PatternStep step = pattern.Steps[i];
|
||||
if (step.Type == PatternStepType.Skill && step.Skill != null && step.Skill.JumpToTarget)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 동일한 보스 오브젝트가 같은 패턴을 다시 사용할 수 있는지 확인합니다.
|
||||
/// </summary>
|
||||
public static bool IsPatternReady(GameObject owner, BossPatternData pattern)
|
||||
{
|
||||
if (owner == null || pattern == null)
|
||||
return false;
|
||||
|
||||
string cooldownKey = BuildCooldownKey(owner, pattern);
|
||||
if (!patternReadyTimes.TryGetValue(cooldownKey, out float readyTime))
|
||||
return true;
|
||||
|
||||
return Time.time >= readyTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패턴 사용 직후 다음 사용 가능 시점을 기록합니다.
|
||||
/// </summary>
|
||||
public static void MarkPatternUsed(GameObject owner, BossPatternData pattern)
|
||||
{
|
||||
if (owner == null || pattern == null)
|
||||
return;
|
||||
|
||||
string cooldownKey = BuildCooldownKey(owner, pattern);
|
||||
patternReadyTimes[cooldownKey] = Time.time + pattern.Cooldown;
|
||||
}
|
||||
|
||||
private static string BuildCooldownKey(GameObject owner, BossPatternData pattern)
|
||||
{
|
||||
return $"{owner.GetInstanceID()}_{pattern.GetInstanceID()}";
|
||||
}
|
||||
|
||||
private void LogDebug(string message)
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
context?.LogDebug(nameof(UsePatternAction), message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user