feat: 드로그 보스 AI 및 런타임 상태 구조 재구성
- 드로그 전투 컨텍스트를 BossBehaviorRuntimeState 중심 구조로 정리하고 BossEnemy, 패턴 액션, 조건 노드가 마지막 실행 결과와 phase 상태를 직접 사용하도록 갱신 - BT_Drog와 재빌드 에디터 스크립트를 확장해 phase 전환, 집행 결과 분기, 거리/쿨타임 기반 패턴 선택을 드로그 전용 자산과 노드 파라미터로 재구성 - 드로그 패턴/스킬/이펙트/애니메이션 플레이스홀더 자산을 재생성하고 보스 프리팹이 새 런타임 상태 및 등록 클립 구성을 참조하도록 정리
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using Action = Unity.Behavior.Action;
|
||||
/// <summary>
|
||||
/// 보스를 경직시키는 BT 액션입니다.
|
||||
/// 충전 차단 성공 등의 상황에서 사용됩니다.
|
||||
/// 경직 시간은 BossCombatBehaviorContext.LastChargeStaggerDuration에서 읽습니다.
|
||||
/// 경직 시간은 BossBehaviorRuntimeState.LastChargeStaggerDuration에서 읽습니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[NodeDescription(
|
||||
@@ -21,21 +21,21 @@ using Action = Unity.Behavior.Action;
|
||||
id: "d4e5f6a7-1111-2222-3333-888899990000")]
|
||||
public partial class BossStaggerAction : Action
|
||||
{
|
||||
private BossCombatBehaviorContext combatBehaviorContext;
|
||||
private BossBehaviorRuntimeState runtimeState;
|
||||
private BossEnemy bossEnemy;
|
||||
private EnemyBase enemyBase;
|
||||
private float staggerEndTime;
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
combatBehaviorContext = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
bossEnemy = GameObject.GetComponent<BossEnemy>();
|
||||
enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
|
||||
if (combatBehaviorContext == null || bossEnemy == null)
|
||||
if (runtimeState == null || bossEnemy == null)
|
||||
return Status.Failure;
|
||||
|
||||
float staggerDuration = combatBehaviorContext.LastChargeStaggerDuration;
|
||||
float staggerDuration = runtimeState.LastChargeStaggerDuration;
|
||||
if (staggerDuration <= 0f)
|
||||
return Status.Success;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public partial class ChaseTargetAction : Action
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (context != null && context.IsBehaviorSuppressed)
|
||||
{
|
||||
return Status.Failure;
|
||||
@@ -54,7 +54,7 @@ public partial class ChaseTargetAction : Action
|
||||
|
||||
protected override Status OnUpdate()
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (context != null && context.IsBehaviorSuppressed)
|
||||
{
|
||||
if (agent != null)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
|
||||
using Colosseum;
|
||||
using Colosseum.Enemy;
|
||||
using Colosseum.Player;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
@@ -22,10 +24,13 @@ public partial class RefreshPrimaryTargetAction : Action
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<GameObject> Target;
|
||||
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<float> SearchRange = new BlackboardVariable<float>(0f);
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
if (context != null && context.IsBehaviorSuppressed)
|
||||
BossBehaviorRuntimeState runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (runtimeState != null && runtimeState.IsBehaviorSuppressed)
|
||||
return Status.Failure;
|
||||
|
||||
EnemyBase enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
@@ -33,17 +38,55 @@ public partial class RefreshPrimaryTargetAction : Action
|
||||
return Status.Failure;
|
||||
|
||||
GameObject currentTarget = Target != null ? Target.Value : null;
|
||||
float aggroRange = enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity;
|
||||
GameObject resolvedTarget = enemyBase.GetHighestThreatTarget(currentTarget, null, aggroRange);
|
||||
float searchRange = ResolveSearchRange(enemyBase);
|
||||
GameObject resolvedTarget = enemyBase.GetHighestThreatTarget(currentTarget, null, searchRange);
|
||||
|
||||
if (resolvedTarget == null)
|
||||
{
|
||||
resolvedTarget = context != null ? context.FindNearestLivingTarget() : null;
|
||||
}
|
||||
resolvedTarget = FindNearestLivingTarget(enemyBase, currentTarget, searchRange);
|
||||
|
||||
if (Target != null)
|
||||
Target.Value = resolvedTarget;
|
||||
|
||||
runtimeState?.SetCurrentTarget(resolvedTarget);
|
||||
runtimeState?.LogDebug(nameof(RefreshPrimaryTargetAction), resolvedTarget != null
|
||||
? $"주 대상 갱신: {resolvedTarget.name}"
|
||||
: "주 대상 갱신 실패");
|
||||
|
||||
return resolvedTarget != null ? Status.Success : Status.Failure;
|
||||
}
|
||||
|
||||
private float ResolveSearchRange(EnemyBase enemyBase)
|
||||
{
|
||||
if (SearchRange != null && SearchRange.Value > 0f)
|
||||
return SearchRange.Value;
|
||||
|
||||
return enemyBase != null && enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity;
|
||||
}
|
||||
|
||||
private GameObject FindNearestLivingTarget(EnemyBase enemyBase, GameObject currentTarget, float searchRange)
|
||||
{
|
||||
PlayerNetworkController[] players = UnityEngine.Object.FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||
GameObject nearestTarget = null;
|
||||
float nearestDistance = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
{
|
||||
PlayerNetworkController player = players[i];
|
||||
if (player == null || player.IsDead || !player.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
|
||||
GameObject candidate = player.gameObject;
|
||||
if (Team.IsSameTeam(GameObject, candidate))
|
||||
continue;
|
||||
|
||||
float distance = Vector3.Distance(GameObject.transform.position, candidate.transform.position);
|
||||
if (distance > searchRange || distance >= nearestDistance)
|
||||
continue;
|
||||
|
||||
nearestDistance = distance;
|
||||
nearestTarget = candidate;
|
||||
}
|
||||
|
||||
return nearestTarget != null ? nearestTarget : currentTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,30 +34,32 @@ public partial class SelectAlternateTargetByDistanceAction : Action
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
if (context == null)
|
||||
BossBehaviorRuntimeState runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (runtimeState == null)
|
||||
return Status.Failure;
|
||||
|
||||
float minRange = MinRange.Value > 0f ? MinRange.Value : context.UtilityTriggerDistance;
|
||||
float minRange = Mathf.Max(0f, MinRange.Value);
|
||||
float maxRange = MaxRange.Value > 0f
|
||||
? MaxRange.Value
|
||||
: (context.EnemyBase != null && context.EnemyBase.Data != null ? context.EnemyBase.Data.AggroRange : 20f);
|
||||
: (runtimeState.EnemyBase != null && runtimeState.EnemyBase.Data != null ? runtimeState.EnemyBase.Data.AggroRange : 20f);
|
||||
|
||||
GameObject selectedTarget = SelectTarget(context, minRange, maxRange);
|
||||
GameObject selectedTarget = SelectTarget(runtimeState, minRange, maxRange);
|
||||
if (selectedTarget == null)
|
||||
return Status.Failure;
|
||||
|
||||
Target.Value = selectedTarget;
|
||||
runtimeState.SetCurrentTarget(selectedTarget);
|
||||
runtimeState.LogDebug(nameof(SelectAlternateTargetByDistanceAction), $"보조 대상 선택: {selectedTarget.name}");
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
private GameObject SelectTarget(BossCombatBehaviorContext context, float minRange, float maxRange)
|
||||
private GameObject SelectTarget(BossBehaviorRuntimeState runtimeState, float minRange, float maxRange)
|
||||
{
|
||||
PlayerNetworkController[] players = UnityEngine.Object.FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||
if (players == null || players.Length == 0)
|
||||
return null;
|
||||
|
||||
GameObject primaryTarget = context.ResolvePrimaryTarget();
|
||||
GameObject primaryTarget = ResolvePrimaryTarget(runtimeState);
|
||||
List<GameObject> validTargets = new List<GameObject>();
|
||||
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
@@ -70,7 +72,7 @@ public partial class SelectAlternateTargetByDistanceAction : Action
|
||||
if (candidate == primaryTarget)
|
||||
continue;
|
||||
|
||||
if (!context.IsValidHostileTarget(candidate))
|
||||
if (!IsValidHostileTarget(candidate))
|
||||
continue;
|
||||
|
||||
float distance = Vector3.Distance(GameObject.transform.position, candidate.transform.position);
|
||||
@@ -82,7 +84,7 @@ public partial class SelectAlternateTargetByDistanceAction : Action
|
||||
|
||||
if (validTargets.Count == 0)
|
||||
{
|
||||
if (primaryTarget != null && context.IsValidHostileTarget(primaryTarget))
|
||||
if (primaryTarget != null && IsValidHostileTarget(primaryTarget))
|
||||
{
|
||||
float primaryDistance = Vector3.Distance(GameObject.transform.position, primaryTarget.transform.position);
|
||||
if (primaryDistance >= minRange && primaryDistance <= maxRange)
|
||||
@@ -95,4 +97,52 @@ public partial class SelectAlternateTargetByDistanceAction : Action
|
||||
int randomIndex = UnityEngine.Random.Range(0, validTargets.Count);
|
||||
return validTargets[randomIndex];
|
||||
}
|
||||
|
||||
private GameObject ResolvePrimaryTarget(BossBehaviorRuntimeState runtimeState)
|
||||
{
|
||||
EnemyBase enemyBase = runtimeState.EnemyBase;
|
||||
GameObject currentTarget = runtimeState.CurrentTarget;
|
||||
float aggroRange = enemyBase != null && enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity;
|
||||
GameObject highestThreatTarget = enemyBase != null
|
||||
? enemyBase.GetHighestThreatTarget(currentTarget, null, aggroRange)
|
||||
: null;
|
||||
|
||||
if (highestThreatTarget != null)
|
||||
return highestThreatTarget;
|
||||
|
||||
PlayerNetworkController[] players = UnityEngine.Object.FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||
GameObject nearestTarget = null;
|
||||
float nearestDistance = float.MaxValue;
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
{
|
||||
PlayerNetworkController player = players[i];
|
||||
if (player == null || player.IsDead || !player.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
|
||||
GameObject candidate = player.gameObject;
|
||||
if (!IsValidHostileTarget(candidate))
|
||||
continue;
|
||||
|
||||
float distance = Vector3.Distance(GameObject.transform.position, candidate.transform.position);
|
||||
if (distance > aggroRange || distance >= nearestDistance)
|
||||
continue;
|
||||
|
||||
nearestDistance = distance;
|
||||
nearestTarget = candidate;
|
||||
}
|
||||
|
||||
return nearestTarget;
|
||||
}
|
||||
|
||||
private bool IsValidHostileTarget(GameObject candidate)
|
||||
{
|
||||
if (candidate == null || !candidate.activeInHierarchy)
|
||||
return false;
|
||||
|
||||
if (Team.IsSameTeam(GameObject, candidate))
|
||||
return false;
|
||||
|
||||
IDamageable damageable = candidate.GetComponent<IDamageable>();
|
||||
return damageable == null || !damageable.IsDead;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,29 +65,19 @@ public partial class SelectNearestDownedTargetAction : Action
|
||||
return Status.Failure;
|
||||
|
||||
Target.Value = nearestTarget;
|
||||
GameObject.GetComponent<BossBehaviorRuntimeState>()?.SetCurrentTarget(nearestTarget);
|
||||
LogDebug($"다운 대상 선택: {nearestTarget.name}");
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
private float ResolveSearchRadius()
|
||||
{
|
||||
if (SearchRadius.Value > 0f)
|
||||
return SearchRadius.Value;
|
||||
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
if (context != null)
|
||||
return context.PunishSearchRadius;
|
||||
|
||||
EnemyBase enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
if (enemyBase != null && enemyBase.Data != null)
|
||||
return enemyBase.Data.AttackRange + 4f;
|
||||
|
||||
return 6f;
|
||||
return Mathf.Max(0f, SearchRadius.Value);
|
||||
}
|
||||
|
||||
private void LogDebug(string message)
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
context?.LogDebug(nameof(SelectNearestDownedTargetAction), message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,7 @@ public partial class SelectTargetByDistanceAction : Action
|
||||
|
||||
float minRange = Mathf.Max(0f, MinRange.Value);
|
||||
float maxRange = Mathf.Max(minRange, MaxRange.Value);
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
|
||||
if (minRange <= 0f && context != null)
|
||||
minRange = context.MobilityTriggerDistance;
|
||||
BossBehaviorRuntimeState runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
|
||||
if (maxRange <= minRange)
|
||||
{
|
||||
@@ -79,6 +76,8 @@ public partial class SelectTargetByDistanceAction : Action
|
||||
return Status.Failure;
|
||||
|
||||
Target.Value = selectedTarget;
|
||||
runtimeState?.SetCurrentTarget(selectedTarget);
|
||||
runtimeState?.LogDebug(nameof(SelectTargetByDistanceAction), $"거리 기반 대상 선택: {selectedTarget.name} / Mode={SelectionMode.Value}");
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
using Action = Unity.Behavior.Action;
|
||||
|
||||
/// <summary>
|
||||
/// BT가 보스의 현재 페이즈 값을 직접 설정합니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[NodeDescription(
|
||||
name: "Set Boss Phase",
|
||||
story: "[TargetPhase] 페이즈로 설정",
|
||||
category: "Action",
|
||||
id: "931c24f8-761f-4d07-a7a0-23c7a7678d6b")]
|
||||
public partial class SetBossPhaseAction : Action
|
||||
{
|
||||
[SerializeReference]
|
||||
[Tooltip("설정할 목표 페이즈 (1부터 시작)")]
|
||||
public BlackboardVariable<int> TargetPhase = new BlackboardVariable<int>(1);
|
||||
|
||||
[SerializeReference]
|
||||
[Tooltip("true면 페이즈 경과 시간도 함께 초기화합니다.")]
|
||||
public BlackboardVariable<bool> ResetTimer = new BlackboardVariable<bool>(true);
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (context == null)
|
||||
return Status.Failure;
|
||||
|
||||
context.SetCurrentPatternPhase(TargetPhase?.Value ?? 1, ResetTimer?.Value ?? true);
|
||||
context.LogDebug(nameof(SetBossPhaseAction), $"현재 페이즈 설정: {context.CurrentPatternPhase}");
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d6f3b56c0073c6fda51d5837ffc49a7
|
||||
@@ -14,7 +14,7 @@ using Action = Unity.Behavior.Action;
|
||||
|
||||
/// <summary>
|
||||
/// 충전 차단에 실패하여 패턴이 완료되었을 때, 전체 플레이어에게 범위 효과를 적용합니다.
|
||||
/// 기존 BossCombatBehaviorContext.ExecuteSignatureFailure()의 BT 노드 이관 버전입니다.
|
||||
/// 런타임 상태에 저장된 시그니처 실패 수치를 읽어 범위 효과를 적용하는 BT 노드입니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[NodeDescription(
|
||||
@@ -24,14 +24,14 @@ using Action = Unity.Behavior.Action;
|
||||
id: "c3d4e5f6-1111-2222-3333-777788889999")]
|
||||
public partial class SignatureFailureEffectsAction : Action
|
||||
{
|
||||
private BossCombatBehaviorContext combatBehaviorContext;
|
||||
private BossBehaviorRuntimeState runtimeState;
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
combatBehaviorContext = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
if (combatBehaviorContext == null)
|
||||
runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (runtimeState == null)
|
||||
{
|
||||
Debug.LogWarning("[SignatureFailureEffects] BossCombatBehaviorContext를 찾을 수 없습니다.");
|
||||
Debug.LogWarning("[SignatureFailureEffects] BossBehaviorRuntimeState를 찾을 수 없습니다.");
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ public partial class SignatureFailureEffectsAction : Action
|
||||
|
||||
private void ApplyFailureEffects()
|
||||
{
|
||||
float failureDamage = combatBehaviorContext.SignatureFailureDamage;
|
||||
AbnormalityData failureAbnormality = combatBehaviorContext.SignatureFailureAbnormality;
|
||||
float knockbackRadius = combatBehaviorContext.SignatureFailureKnockbackRadius;
|
||||
float downRadius = combatBehaviorContext.SignatureFailureDownRadius;
|
||||
float knockbackSpeed = combatBehaviorContext.SignatureFailureKnockbackSpeed;
|
||||
float knockbackDuration = combatBehaviorContext.SignatureFailureKnockbackDuration;
|
||||
float downDuration = combatBehaviorContext.SignatureFailureDownDuration;
|
||||
float failureDamage = runtimeState.SignatureFailureDamage;
|
||||
AbnormalityData failureAbnormality = runtimeState.SignatureFailureAbnormality;
|
||||
float knockbackRadius = runtimeState.SignatureFailureKnockbackRadius;
|
||||
float downRadius = runtimeState.SignatureFailureDownRadius;
|
||||
float knockbackSpeed = runtimeState.SignatureFailureKnockbackSpeed;
|
||||
float knockbackDuration = runtimeState.SignatureFailureKnockbackDuration;
|
||||
float downDuration = runtimeState.SignatureFailureDownDuration;
|
||||
|
||||
PlayerNetworkController[] players = UnityEngine.Object.FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
@@ -57,9 +57,6 @@ public partial class SignatureFailureEffectsAction : Action
|
||||
continue;
|
||||
|
||||
GameObject target = player.gameObject;
|
||||
if (!combatBehaviorContext.IsValidHostileTarget(target))
|
||||
continue;
|
||||
|
||||
player.TakeDamage(failureDamage, GameObject);
|
||||
|
||||
if (failureAbnormality != null)
|
||||
|
||||
@@ -30,6 +30,7 @@ public partial class UsePatternAction : Action
|
||||
private int currentStepIndex;
|
||||
private float waitEndTime;
|
||||
private bool isWaiting;
|
||||
private bool isSkillStepExecuting;
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
@@ -66,6 +67,7 @@ public partial class UsePatternAction : Action
|
||||
|
||||
currentStepIndex = 0;
|
||||
isWaiting = false;
|
||||
isSkillStepExecuting = false;
|
||||
return ExecuteCurrentStep();
|
||||
}
|
||||
|
||||
@@ -83,9 +85,17 @@ public partial class UsePatternAction : Action
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skillController.IsPlayingAnimation)
|
||||
return Status.Running;
|
||||
if (isSkillStepExecuting)
|
||||
{
|
||||
if (skillController.IsPlayingAnimation)
|
||||
return Status.Running;
|
||||
|
||||
isSkillStepExecuting = false;
|
||||
if (skillController.LastExecutionResult != SkillExecutionResult.Completed)
|
||||
return Status.Failure;
|
||||
}
|
||||
else if (skillController.IsPlayingAnimation)
|
||||
return Status.Running;
|
||||
}
|
||||
|
||||
currentStepIndex++;
|
||||
@@ -102,6 +112,7 @@ public partial class UsePatternAction : Action
|
||||
protected override void OnEnd()
|
||||
{
|
||||
skillController = null;
|
||||
isSkillStepExecuting = false;
|
||||
}
|
||||
|
||||
private Status ExecuteCurrentStep()
|
||||
@@ -132,13 +143,17 @@ public partial class UsePatternAction : Action
|
||||
}
|
||||
}
|
||||
|
||||
bool success = skillController.ExecuteSkill(step.Skill);
|
||||
GameObject skillTarget = step.Skill.JumpToTarget ? jumpTarget : Target?.Value;
|
||||
bool success = skillTarget != null
|
||||
? skillController.ExecuteSkill(step.Skill, skillTarget)
|
||||
: skillController.ExecuteSkill(step.Skill);
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogWarning($"[UsePatternAction] 스킬 실행 실패: {step.Skill.SkillName} (index {currentStepIndex})");
|
||||
return Status.Failure;
|
||||
}
|
||||
|
||||
isSkillStepExecuting = true;
|
||||
LogDebug($"패턴 실행: {Pattern.Value.PatternName} / Step={currentStepIndex} / Skill={step.Skill.SkillName}");
|
||||
|
||||
// jumpToTarget 스킬이면 타겟 위치 전달
|
||||
@@ -233,7 +248,7 @@ public partial class UsePatternAction : Action
|
||||
|
||||
private void LogDebug(string message)
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
context?.LogDebug(nameof(UsePatternAction), message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ public partial class UsePatternByRoleAction : BossPatternActionBase
|
||||
[Tooltip("실행할 패턴")]
|
||||
public BlackboardVariable<BossPatternData> Pattern;
|
||||
|
||||
[SerializeReference]
|
||||
[Tooltip("패턴이 실패 결과로 끝나도 BT 시퀀스를 다음 노드까지 진행합니다.")]
|
||||
public BlackboardVariable<bool> ContinueOnResolvedFailure = new BlackboardVariable<bool>(false);
|
||||
|
||||
/// <summary>
|
||||
/// 결과 분기용 패턴은 실패 결과도 다음 노드에서 처리할 수 있게 시퀀스를 유지합니다.
|
||||
/// </summary>
|
||||
protected override bool ContinueSequenceOnResolvedFailure => ContinueOnResolvedFailure?.Value ?? false;
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
BossPatternData pattern = Pattern?.Value;
|
||||
@@ -36,7 +45,7 @@ public partial class UsePatternByRoleAction : BossPatternActionBase
|
||||
// 여기서는 RegisterPatternUse만 호출 (근접 패턴 전용)
|
||||
if (pattern.IsMelee)
|
||||
{
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
context?.RegisterPatternUse(pattern);
|
||||
}
|
||||
|
||||
@@ -66,7 +75,8 @@ public partial class UsePatternByRoleAction : BossPatternActionBase
|
||||
if (pattern == null)
|
||||
return false;
|
||||
|
||||
if (!UsePatternAction.IsPatternReady(GameObject, pattern))
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (context == null || !context.IsPatternReady(pattern))
|
||||
return false;
|
||||
|
||||
if (target == null)
|
||||
@@ -77,26 +87,6 @@ public partial class UsePatternByRoleAction : BossPatternActionBase
|
||||
|
||||
protected override GameObject ResolveStepTarget(GameObject fallbackTarget)
|
||||
{
|
||||
BossPatternData pattern = Pattern?.Value;
|
||||
if (pattern == null)
|
||||
return base.ResolveStepTarget(fallbackTarget);
|
||||
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
if (context == null)
|
||||
return base.ResolveStepTarget(fallbackTarget);
|
||||
|
||||
TargetResolveMode targetMode = pattern.TargetMode;
|
||||
|
||||
if (targetMode == TargetResolveMode.Mobility)
|
||||
return context.IsValidMobilityTarget(fallbackTarget)
|
||||
? fallbackTarget
|
||||
: context.FindMobilityTarget();
|
||||
|
||||
if (targetMode == TargetResolveMode.Utility)
|
||||
return context.IsValidUtilityTarget(fallbackTarget)
|
||||
? fallbackTarget
|
||||
: context.FindUtilityTarget();
|
||||
|
||||
return base.ResolveStepTarget(fallbackTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ public partial class UseSkillAction : Action
|
||||
}
|
||||
|
||||
// 스킬 실행 시도
|
||||
bool success = skillController.ExecuteSkill(스킬.Value);
|
||||
bool success = Target?.Value != null
|
||||
? skillController.ExecuteSkill(스킬.Value, Target.Value)
|
||||
: skillController.ExecuteSkill(스킬.Value);
|
||||
if (!success)
|
||||
{
|
||||
// 이미 다른 스킬 사용 중이거나 쿨타임
|
||||
@@ -63,9 +65,7 @@ public partial class UseSkillAction : Action
|
||||
|
||||
// 스킬 애니메이션이 종료되면 성공
|
||||
if (!skillController.IsPlayingAnimation)
|
||||
{
|
||||
return Status.Success;
|
||||
}
|
||||
return skillController.LastExecutionResult == SkillExecutionResult.Completed ? Status.Success : Status.Failure;
|
||||
|
||||
return Status.Running;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
{
|
||||
/// <summary>
|
||||
/// 마지막 대형 패턴 이후 누적된 기본 루프 횟수가 기준 이상인지 확인합니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[Condition(name: "Is Basic Loop Count At Least", story: "기본 루프 누적 횟수가 [Count]회 이상인가?", id: "5c54d42c-780b-4334-bf58-1f7d4c79f4ea")]
|
||||
[NodeDescription(
|
||||
name: "Is Basic Loop Count At Least",
|
||||
story: "기본 루프 누적 횟수가 [Count]회 이상인가?",
|
||||
category: "Condition/Pattern")]
|
||||
public partial class IsBasicLoopCountAtLeastCondition : Unity.Behavior.Condition
|
||||
{
|
||||
[SerializeReference]
|
||||
[Tooltip("필요한 최소 기본 루프 횟수")]
|
||||
public BlackboardVariable<int> Count = new BlackboardVariable<int>(0);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
BossBehaviorRuntimeState runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return runtimeState != null && runtimeState.BasicLoopCountSinceLastBigPattern >= Mathf.Max(0, Count?.Value ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6aa918a0d90f67399b6cfb594edc31b
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
{
|
||||
/// <summary>
|
||||
/// 보스가 보유한 커스텀 조건 플래그가 활성화되었는지 확인합니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[Condition(name: "Is Boss Custom Condition True", story: "커스텀 조건 [ConditionId] 가 참인가?", id: "0c4a5f77-a599-40a7-80fb-d22c4bb27f19")]
|
||||
[NodeDescription(
|
||||
name: "Is Boss Custom Condition True",
|
||||
story: "커스텀 조건 [ConditionId] 가 참인가?",
|
||||
category: "Condition/Phase")]
|
||||
public partial class IsBossCustomConditionTrueCondition : Unity.Behavior.Condition
|
||||
{
|
||||
[SerializeReference]
|
||||
[Tooltip("확인할 커스텀 조건 ID")]
|
||||
public BlackboardVariable<string> ConditionId = new BlackboardVariable<string>(string.Empty);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return context != null && !string.IsNullOrEmpty(ConditionId?.Value) && context.CheckPhaseCustomCondition(ConditionId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 125ba0ac5df532b00b25e8bfc3f556e3
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
using Condition = Unity.Behavior.Condition;
|
||||
|
||||
namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
{
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[Condition(name: "Is Charge Broken", story: "충전이 차단되었는가?", id: "e5f6a7b8-1111-2222-3333-aaaaaaaa0001")]
|
||||
[NodeDescription(
|
||||
name: "Is Charge Broken",
|
||||
story: "Charge was broken by accumulated damage",
|
||||
category: "Condition/Pattern")]
|
||||
public partial class IsChargeBrokenCondition : Condition
|
||||
{
|
||||
public override bool IsTrue()
|
||||
{
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return context != null && context.WasChargeBroken;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe107cb64ea2a7c07adc2fc4db48a4d1
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
{
|
||||
/// <summary>
|
||||
/// 현재 보스 페이즈가 지정한 값과 같은지 확인합니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[Condition(name: "Is Current Phase", story: "현재 페이즈가 [Phase] 인가?", id: "6dc82e39-6f84-43df-b8ce-5b7c0ac8e390")]
|
||||
[NodeDescription(
|
||||
name: "Is Current Phase",
|
||||
story: "현재 페이즈가 [Phase] 인가?",
|
||||
category: "Condition/Phase")]
|
||||
public partial class IsCurrentPhaseCondition : Unity.Behavior.Condition
|
||||
{
|
||||
[SerializeReference]
|
||||
[Tooltip("확인할 현재 페이즈 값 (1부터 시작)")]
|
||||
public BlackboardVariable<int> Phase = new BlackboardVariable<int>(1);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return context != null && context.CurrentPatternPhase == Mathf.Max(1, Phase?.Value ?? 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c664951a5687590b593a30936378b8d2
|
||||
@@ -23,13 +23,13 @@ namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
category: "Condition/Pattern")]
|
||||
public partial class IsDownedTargetInRangeCondition : Condition
|
||||
{
|
||||
[Min(0f)]
|
||||
[SerializeReference]
|
||||
[Tooltip("다운된 대상을 탐색할 최대 반경")]
|
||||
[SerializeField]
|
||||
private float searchRadius = 6f;
|
||||
public BlackboardVariable<float> SearchRadius = new BlackboardVariable<float>(6f);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
float searchRadius = Mathf.Max(0f, SearchRadius.Value);
|
||||
HitReactionController[] controllers = UnityEngine.Object.FindObjectsByType<HitReactionController>(FindObjectsSortMode.None);
|
||||
|
||||
for (int i = 0; i < controllers.Length; i++)
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
if (minPhase <= 1)
|
||||
return true;
|
||||
|
||||
BossCombatBehaviorContext context = GameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return context != null && context.CurrentPatternPhase >= minPhase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
{
|
||||
/// <summary>
|
||||
/// 마지막 패턴 실행 결과가 지정한 값과 일치하는지 확인합니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[Condition(name: "Is Pattern Execution Result", story: "마지막 패턴 결과가 [Result] 인가?", id: "4ffbf07b-3fa4-42cc-9a61-75fd07b05db6")]
|
||||
[NodeDescription(
|
||||
name: "Is Pattern Execution Result",
|
||||
story: "마지막 패턴 결과가 [Result] 인가?",
|
||||
category: "Condition/Pattern")]
|
||||
public partial class IsPatternExecutionResultCondition : Unity.Behavior.Condition
|
||||
{
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<BossPatternExecutionResult> Result = new BlackboardVariable<BossPatternExecutionResult>(BossPatternExecutionResult.Succeeded);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
BossBehaviorRuntimeState runtimeState = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return runtimeState != null && runtimeState.LastPatternExecutionResult == (Result?.Value ?? BossPatternExecutionResult.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47a54f9591003b10db94afd51bf8cb54
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
{
|
||||
/// <summary>
|
||||
/// 현재 페이즈의 경과 시간이 기준 이상인지 확인합니다.
|
||||
/// </summary>
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[Condition(name: "Is Phase Elapsed Time Above", story: "페이즈 경과 시간이 [Seconds]초 이상인가?", id: "f0e0f5b3-3cb7-4991-ae8a-e89efcc0dbca")]
|
||||
[NodeDescription(
|
||||
name: "Is Phase Elapsed Time Above",
|
||||
story: "페이즈 경과 시간이 [Seconds]초 이상인가?",
|
||||
category: "Condition/Phase")]
|
||||
public partial class IsPhaseElapsedTimeAboveCondition : Unity.Behavior.Condition
|
||||
{
|
||||
[SerializeReference]
|
||||
[Tooltip("확인할 최소 경과 시간(초)")]
|
||||
public BlackboardVariable<float> Seconds = new BlackboardVariable<float>(0f);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
BossBehaviorRuntimeState context = GameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
return context != null && context.PhaseElapsedTime >= Mathf.Max(0f, Seconds?.Value ?? 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dceca864920c7cd5f8fdcf971355f380
|
||||
@@ -23,13 +23,13 @@ namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
category: "Condition/Pattern")]
|
||||
public partial class IsTargetBeyondDistanceCondition : Condition
|
||||
{
|
||||
[Min(0f)]
|
||||
[SerializeReference]
|
||||
[Tooltip("이 거리 이상 떨어진 대상이 있는지 확인")]
|
||||
[SerializeField]
|
||||
private float minDistance = 8f;
|
||||
public BlackboardVariable<float> MinDistance = new BlackboardVariable<float>(8f);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
float minDistance = Mathf.Max(0f, MinDistance.Value);
|
||||
IDamageable[] targets = UnityEngine.Object.FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None)
|
||||
.OfType<IDamageable>()
|
||||
.ToArray();
|
||||
|
||||
@@ -17,20 +17,22 @@ namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
[Condition(name: "Is Target In Attack Range", story: "타겟이 공격 사거리 안에 있는가?", id: "57370b5b23f82a54dabc4f189a23286a")]
|
||||
[NodeDescription(
|
||||
name: "Is Target In Attack Range",
|
||||
story: "Is [Target] in attack range",
|
||||
story: "Is [Target] in [AttackRange]m attack range",
|
||||
category: "Condition/Combat")]
|
||||
public partial class IsTargetInAttackRangeCondition : Condition
|
||||
{
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<GameObject> Target;
|
||||
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<float> AttackRange = new BlackboardVariable<float>(2f);
|
||||
|
||||
public override bool IsTrue()
|
||||
{
|
||||
if (Target?.Value == null)
|
||||
return false;
|
||||
|
||||
EnemyBase enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
float attackRange = enemyBase != null && enemyBase.Data != null ? enemyBase.Data.AttackRange : 2f;
|
||||
float attackRange = Mathf.Max(0f, AttackRange.Value);
|
||||
float distance = Vector3.Distance(GameObject.transform.position, Target.Value.transform.position);
|
||||
return distance <= attackRange + 0.25f;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
if (pattern == null)
|
||||
return false;
|
||||
|
||||
BossCombatBehaviorContext context = gameObject.GetComponent<BossCombatBehaviorContext>();
|
||||
BossBehaviorRuntimeState context = gameObject.GetComponent<BossBehaviorRuntimeState>();
|
||||
if (context == null)
|
||||
return false;
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Colosseum.AI.BehaviorActions.Conditions
|
||||
if (!context.IsPatternGracePeriodAllowed(pattern))
|
||||
return false;
|
||||
|
||||
return UsePatternAction.IsPatternReady(gameObject, pattern);
|
||||
return context.IsPatternReady(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user