Unity C# Поиск объекта в радиусе
Всем привет. Есть необходимость при нажатии на кнопку удара, проверять, есть ли в радиусе поражения противник, и если есть - повернуть к нему персонажа и произвести удар, а если в радиусе поражения никого нет - не производить удар.
Собственно, из всего этого вопросы вызывает только поиск противника в радиусе поражения.
Сейчас реализовал перебор всех противников через цикл. Выбираю самого ближнего из них и проверяю расстояние до игрока.
Вот код:
private GameObject FindClosestEnemy(float maxDistance)
{
GameObject closest = null;
List<GameObject> enemyes = new List<GameObject>(GameObject.FindGameObjectsWithTag("Enemy"));
float distance = Mathf.Infinity;
float curDistance;
if (enemyes.Count > 0)
{
foreach (GameObject go in enemyes)
{
Vector3 diff = go.transform.position - transform.position;
curDistance = diff.sqrMagnitude;
if (curDistance < distance)
{
closest = go;
distance = curDistance;
}
}
if (distance <= maxDistance) return closest;
else return null;
}
else return null;
}
Я бы так и оставил, но я переживаю за производительность, если противников на сцене будет 100+. Причем игра "каждый сам за себя", противники тоже будут атаковать и каждый из них будет перебирать 100+ объектов…
Какой самый оптимальный способ решения этой задачи?
Ответы (2 шт):
Самый экономичный способ скорее всего будет создать два Rect с зонами поражения атаки и перебором через Rect.Overlaps проверить с Rect телами врагов.
Rect.Overlaps это очень простые вычисления, буквально + - > <, в отличие от Vector.Distance(magnitude), который есть квадратный корень из разниц квадратов осей.
public class Unit : MonoBehaviour {
public Rect Body {
get {
Rect BodyRect = new Rect(Vector2.zero, _bodyRectSize);
BodyRect.center = (Vector2)_transform.localPosition;
return BodyRect;
}
}
public int Fraction {
get { return _fraction; }
}
[SerializeField] private Vector2 _bodyRectSize = new Vector2(10, 20);
[SerializeField] private Vector2 _attackRectSize = new Vector2(10, 10);
[SerializeField] private int _fraction; // Фракция
private Transform _transform;
public bool Attack () {
// Зоны атаки справо и слево
Rect SelfBody = Body;
Vector2 Offset = new Vector2((SelfBody.width+_attackRectSize.x)*0.5f, 0);
Rect RightZone = new Rect(Vector2.zero, _attackRectSize);
RightZone.center = (Vector2)_transform.localPosition+Offset;
Rect LeftZone = new Rect(Vector2.zero, _attackRectSize);
LeftZone.center = (Vector2)_transform.localPosition-Offset;
// Перебор
List<Unit> RightTargets = new List<Unit>();
List<Unit> LeftTargets = new List<Unit>();
foreach (Unit unit in UnitManager.Units) {
if (unit != this && _fraction != unit.Fraction) {
Rect TargetBody = unit.Body;
if (RightZone.Overlaps(TargetBody))
RightTargets.Add(unit);
if (RightZone.Overlaps(TargetBody))
LeftTargets.Add(unit);
}
}
if (RightTargets.Count == 0 && LeftTargets.Count == 0)
// нет целей
return false;
else {
// есть цели
// ... [раелизация атаки]
return true;
}
}
private void Awake () {
_transform = transform;
UnitManager.Units.Add(this); // некий статический класс или синглтон
}
private void OnDestroy () {
UnitManager.Units.Remove(this);
}
}
Вот решение проблемы, этот скрипт помогает искать компоненты в радиусе, а также самый ближний и самый дальний из них.
ComponentSearcher.cs:
using System;
using System.Collections.Generic;
using UnityEngine;
public static class ComponentSearcher<T> where T : Component
{
public static T Closest(Vector3 pos, List<T> components) => Search(pos, components, Desired.Closest);
public static T Closest(Vector3 pos, float radius) => Search(pos, radius, Desired.Closest);
public static T Furthest(Vector3 pos, List<T> components) => Search(pos, components, Desired.Furthest);
public static T Furthest(Vector3 pos, float radius) => Search(pos, radius, Desired.Furthest);
private static T Search(Vector3 pos, List<T> components, Desired type)
{
if (components == null)
throw new NullReferenceException("Components is null, cant search");
float maxDistanceToComponent;
if (type == Desired.Closest)
maxDistanceToComponent = Mathf.Infinity;
else
maxDistanceToComponent = Mathf.NegativeInfinity;
T component = null;
for (int i = 0; i < components.Count; i++)
{
float distanceToComponent = Vector3.Distance(components[i].transform.position, pos);
if (type == Desired.Closest)
{
if (distanceToComponent < maxDistanceToComponent)
{
maxDistanceToComponent = distanceToComponent;
component = components[i];
}
}
else
{
if (distanceToComponent > maxDistanceToComponent)
{
maxDistanceToComponent = distanceToComponent;
component = components[i];
}
}
}
return component;
}
private static T Search(Vector3 pos, float radius, Desired type)
{
InRadius(pos, radius, out List<T> components);
return Search(pos, components, type);
}
public static int InRadius(Vector3 pos, float radius, out List<T> results, bool findOnlyEnabled = true)
{
Collider[] colliders = Physics.OverlapSphere(pos, radius);
results = new List<T>();
for (int i = 0; i < colliders.Length; i++)
{
if (colliders[i].TryGetComponent(out T component))
{
if (findOnlyEnabled)
{
Behaviour behaviour = component as Behaviour;
if (behaviour)
{
if (behaviour.enabled)
results.Add(component);
}
else
{
findOnlyEnabled = false;
results.Add(component);
}
}
else
{
results.Add(component);
}
}
}
return results.Count;
}
private enum Desired { Closest, Furthest }
}
пример использования:
using System.Collections.Generic;
using UnityEngine;
public class ExampleUsage : MonoBehaviour
{
private List<MyComponentType> components;
private void Start()
{
// Populate the 'components' list with your MyComponentType components
}
private void Update()
{
Vector3 currentPosition = transform.position;
MyComponentType closestComponent = ComponentSearcher<MyComponentType>.Closest(currentPosition, components);
MyComponentType furthestComponent = ComponentSearcher<MyComponentType>.Furthest(currentPosition, components);
float searchRadius = 10f;
List<MyComponentType> componentsInRange = new List<MyComponentType>();
ComponentSearcher<MyComponentType>.InRadius(currentPosition, searchRadius, out componentsInRange);
// Now you can interact with the found components
}
}