using System; using Unity.Behavior; using Unity.Properties; using UnityEngine; using Colosseum; using Colosseum.Combat; using Colosseum.Enemy; using Action = Unity.Behavior.Action; /// /// 거리 조건에 맞는 대상을 선택하는 Behavior Action입니다. /// 가장 가까운 대상, 가장 먼 대상, 후보 중 랜덤 선택을 지원합니다. /// [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 Target; [SerializeReference] public BlackboardVariable Tag = new BlackboardVariable("Player"); [SerializeReference] public BlackboardVariable MinRange = new BlackboardVariable(0f); [SerializeReference] public BlackboardVariable MaxRange = new BlackboardVariable(20f); [SerializeReference] public BlackboardVariable SelectionMode = new BlackboardVariable(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); BossBehaviorRuntimeState runtimeState = GameObject.GetComponent(); if (maxRange <= minRange) { EnemyBase enemyBase = GameObject.GetComponent(); 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; runtimeState?.SetCurrentTarget(selectedTarget); runtimeState?.LogDebug(nameof(SelectTargetByDistanceAction), $"거리 기반 대상 선택: {selectedTarget.name} / Mode={SelectionMode.Value}"); 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 validTargets = new System.Collections.Generic.List(); 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(); if (damageable != null && damageable.IsDead) return false; EnemyBase enemyBase = GameObject.GetComponent(); 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; } }