feat: 드로그 공통 보스 BT 프레임워크 정리
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
|
||||
using Unity.Behavior;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Enemy;
|
||||
|
||||
using Action = Unity.Behavior.Action;
|
||||
|
||||
/// <summary>
|
||||
/// 거리 조건에 맞는 대상을 선택하는 Behavior Action입니다.
|
||||
/// 가장 가까운 대상, 가장 먼 대상, 후보 중 랜덤 선택을 지원합니다.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public enum DistanceTargetSelectionMode
|
||||
{
|
||||
Nearest = 0,
|
||||
Farthest = 1,
|
||||
Random = 2,
|
||||
}
|
||||
|
||||
[Serializable, GeneratePropertyBag]
|
||||
[NodeDescription(
|
||||
name: "Select Target By Distance",
|
||||
story: "[Tag] 대상 중 [MinRange] 이상 [MaxRange] 이하 범위에서 [SelectionMode] 방식으로 [Target] 선택",
|
||||
category: "Action",
|
||||
id: "4f6a830df3ff4ff5bf8bd2c8b433aa41")]
|
||||
public partial class SelectTargetByDistanceAction : Action
|
||||
{
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<GameObject> Target;
|
||||
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<string> Tag = new BlackboardVariable<string>("Player");
|
||||
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<float> MinRange = new BlackboardVariable<float>(0f);
|
||||
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<float> MaxRange = new BlackboardVariable<float>(20f);
|
||||
|
||||
[SerializeReference]
|
||||
public BlackboardVariable<DistanceTargetSelectionMode> SelectionMode =
|
||||
new BlackboardVariable<DistanceTargetSelectionMode>(DistanceTargetSelectionMode.Farthest);
|
||||
|
||||
protected override Status OnStart()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Tag.Value))
|
||||
return Status.Failure;
|
||||
|
||||
GameObject[] candidates = GameObject.FindGameObjectsWithTag(Tag.Value);
|
||||
if (candidates == null || candidates.Length == 0)
|
||||
return Status.Failure;
|
||||
|
||||
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;
|
||||
|
||||
if (maxRange <= minRange)
|
||||
{
|
||||
EnemyBase enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
maxRange = enemyBase != null && enemyBase.Data != null ? enemyBase.Data.AggroRange : 20f;
|
||||
}
|
||||
|
||||
GameObject selectedTarget = SelectionMode.Value switch
|
||||
{
|
||||
DistanceTargetSelectionMode.Nearest => FindNearestTarget(candidates, minRange, maxRange),
|
||||
DistanceTargetSelectionMode.Random => FindRandomTarget(candidates, minRange, maxRange),
|
||||
_ => FindFarthestTarget(candidates, minRange, maxRange),
|
||||
};
|
||||
|
||||
if (selectedTarget == null)
|
||||
return Status.Failure;
|
||||
|
||||
Target.Value = selectedTarget;
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
private GameObject FindNearestTarget(GameObject[] candidates, float minRange, float maxRange)
|
||||
{
|
||||
GameObject bestTarget = null;
|
||||
float bestDistance = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < candidates.Length; i++)
|
||||
{
|
||||
GameObject candidate = candidates[i];
|
||||
if (!IsValidTarget(candidate, minRange, maxRange, out float distance))
|
||||
continue;
|
||||
|
||||
if (distance >= bestDistance)
|
||||
continue;
|
||||
|
||||
bestDistance = distance;
|
||||
bestTarget = candidate;
|
||||
}
|
||||
|
||||
return bestTarget;
|
||||
}
|
||||
|
||||
private GameObject FindFarthestTarget(GameObject[] candidates, float minRange, float maxRange)
|
||||
{
|
||||
GameObject bestTarget = null;
|
||||
float bestDistance = minRange;
|
||||
|
||||
for (int i = 0; i < candidates.Length; i++)
|
||||
{
|
||||
GameObject candidate = candidates[i];
|
||||
if (!IsValidTarget(candidate, minRange, maxRange, out float distance))
|
||||
continue;
|
||||
|
||||
if (distance <= bestDistance)
|
||||
continue;
|
||||
|
||||
bestDistance = distance;
|
||||
bestTarget = candidate;
|
||||
}
|
||||
|
||||
return bestTarget;
|
||||
}
|
||||
|
||||
private GameObject FindRandomTarget(GameObject[] candidates, float minRange, float maxRange)
|
||||
{
|
||||
System.Collections.Generic.List<GameObject> validTargets = new System.Collections.Generic.List<GameObject>();
|
||||
|
||||
for (int i = 0; i < candidates.Length; i++)
|
||||
{
|
||||
GameObject candidate = candidates[i];
|
||||
if (!IsValidTarget(candidate, minRange, maxRange, out _))
|
||||
continue;
|
||||
|
||||
validTargets.Add(candidate);
|
||||
}
|
||||
|
||||
if (validTargets.Count == 0)
|
||||
return null;
|
||||
|
||||
int randomIndex = UnityEngine.Random.Range(0, validTargets.Count);
|
||||
return validTargets[randomIndex];
|
||||
}
|
||||
|
||||
private bool IsValidTarget(GameObject candidate, float minRange, float maxRange, out float distance)
|
||||
{
|
||||
distance = 0f;
|
||||
|
||||
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;
|
||||
|
||||
EnemyBase enemyBase = GameObject.GetComponent<EnemyBase>();
|
||||
float sightRange = enemyBase != null && enemyBase.Data != null
|
||||
? enemyBase.Data.AggroRange
|
||||
: maxRange;
|
||||
|
||||
distance = Vector3.Distance(GameObject.transform.position, candidate.transform.position);
|
||||
if (distance < minRange || distance > maxRange || distance > sightRange)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user