173 lines
5.4 KiB
C#
173 lines
5.4 KiB
C#
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;
|
|
}
|
|
}
|