Как найти ближайший объект unity 2d

Я сделал NavMeshAgent в Unity. И через for задал ему несколько целей. Уничтожает один объект, затем другой. Но, как я понял, он определяет не ближний объект, а по возрастанию (по индексу массива).

Как сделать так, чтобы персонаж определял ближний объект, затем уничтожил его, а потом переходил к другому ближнему?

Один человек посоветовал мне через magnitude, но работает некорректно.

Вот мой код:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Unit : MonoBehaviour {

private NavMeshAgent agent;
private GameObject[] target;
private Animator anim;
private float distance;

public Transform start;

void Start () {
    agent = GetComponent<NavMeshAgent> ();
    anim = GetComponent<Animator> ();
}

public void Attack(int index){
    switch (index) {
    case 0:
        {
            for (int i = 0; i < target.Length; i++) {
                distance = Vector3.Distance (transform.position, agent.steeringTarget);
                Vector3 move = target [i].transform.position;
                transform.LookAt (new Vector3 (target [i].transform.position.x, 0, target [i].transform.position.z));
                agent.SetDestination (move);
                agent.updateRotation = true;
            }
        }
        break;
    case 1:
        {
            anim.Play ("Idle Firing");
            RaycastHit hit;
            if (Physics.Raycast (start.position, start.forward, out hit, 100.0f)) {
                Debug.Log (hit.point);
                if (hit.collider.tag == "Target") {
                    hit.collider.gameObject.GetComponent<Building> ().health -= 1;
                    if (hit.collider.gameObject.GetComponent<Building> ().health <= 0) {
                        Destroy (hit.collider.gameObject);
                        anim.Play ("Idle");
                    }
                }
            } else {
                Debug.LogError ("Error");
            }
        }
        break;
    case 3:
        {
            anim.Play ("Idle");
        }
        break;
    }
}

public void Navigate(){
    for (int i = 0; i < target.Length; i++) {
        distance = Vector3.Distance (transform.position, agent.steeringTarget);
        if (distance > agent.stoppingDistance) {
            anim.Play ("Run_Forwards");
        }
    }
}

void Shoot(){
    Attack (0);
    if (distance > agent.stoppingDistance) {
        Navigate ();
    } else if (distance <= agent.stoppingDistance) {
        Attack (1);
    } else {
        anim.Play ("Idle");
    }
}

void Update () {
    target = GameObject.FindGameObjectsWithTag ("Target");
    Shoot ();
}

}

Kromster's user avatar

Kromster

13.5k12 золотых знаков43 серебряных знака72 бронзовых знака

задан 31 июл 2017 в 15:36

General2001's user avatar

1

Здравствуйте!

Немного теории.

Допустим есть ваш персонаж и две цели куда идти. Первая цель target1 находится на расстоянии в 2 метра от персонажа, а вторая target2 в 5 метрах. Но есть одно условие, первая цель target1 стоит за длинной стеной, а вторая target2 в зоне прямой видимости от персонажа. Если применять ваши методы сравнения Vector3.Distance или Vector3.Magnitude, то будет выдаваться первая цель target1, но она за стеной и идти до нее 8 метров!

Мораль сей басни такова: вам нужно проверять не расстояние, а длину пути до объекта. Пример кода :

IEnumerator GetClosestTarget() {
    float tmpDist = float.MaxValue;
    GameObject currentTarget = null;
    for (int i = 0; i < targets.Length; i++) {
        if (agent.SetDestination(targets[i].transform.position)) {
            //ждем пока вычислится путь до цели
            while (agent.pathPending) {
                yield return null;
            }
            Debug.Log(agent.pathStatus.ToString());
            // проверяем, можно ли дойти до цели
            if (agent.pathStatus != NavMeshPathStatus.PathInvalid) {
                float pathDistance = 0;
                //вычисляем длину пути
                pathDistance += Vector3.Distance(transform.position, agent.path.corners[0]);
                for (int j = 1; j < agent.path.corners.Length; j++) {
                    pathDistance += Vector3.Distance(agent.path.corners[j - 1], agent.path.corners[j]);
                }

                if (tmpDist > pathDistance) { 
                    tmpDist = pathDistance;
                    currentTarget = targets[i];
                    agent.ResetPath();
                }
            } else {
                Debug.Log("невозможно дойти до "+ targets[i].name);
            }

        }

    }
    if (currentTarget != null) {
        agent.SetDestination(currentTarget.transform.position);
        //... дальше ваша логика движения к цели
    }
}

Вызов этого метода производится так: StartCoroutine(GetClosestTarget());

ответ дан 4 авг 2017 в 13:57

nipercop's user avatar

nipercopnipercop

5212 серебряных знака8 бронзовых знаков

Найти ближний объект можно так:

using System.Collections;
using UnityEngine;
using System.Collections.Generic;

public class Nearest : MonoBehaviour {

    GameObject [] enemy;
    GameObject closest;

    public string nearest;

    void Start() {
        enemy = GameObject.FindGameObjectsWithTag("Enemy");
    }

    GameObject FindClosestEnemy() {
        float distance = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach (GameObject go in enemy) {
            Vector3 diff = go.transform.position - position;
            float curDistance = diff.sqrMagnitude;
            if(curDistance< distance) {
                closest = go;
                distance = curDistance;
            }
        }
        return closest;
    }

    void Update() {
        nearest =FindClosestEnemy().name;
    }
}

Тут:

GameObject [] enemy; – список объектов врагов, среди которых будем искать.

FindClosestEnemy() – ищет ближайший GameObject к объекту на который повешен скрипт.

Надо отметить врагов Tag ("Enemy"). Для формирования списка в котором будем искать ближайший объект, находящийся относительно нашего.

Не стал заморачиваться, чтоб переделывать ваш код. Так как вопрос старый. Решил поделиться кодом который у меня хорошо работает.

ответ дан 15 авг 2019 в 9:34

Ivan Triumphov's user avatar

Ivan TriumphovIvan Triumphov

1,1081 золотой знак14 серебряных знаков37 бронзовых знаков

Идеологически можно подойти к решению 2 способами:

  1. Один раз найти ближайшую цель, запомнить ее и двигаться в ее направлении до тех пор, пока она не будет уничтожена
  2. Каждый раз при вызове Update() пересчитывать расстояния и каждый раз искать ближайшую цель. Это может быть точнее, чем первый вариант, потому что будет учитывать скорость перемещения целей (если они движутся, и если они движутся с разной скоростью), однако требует пересчета при каждом вызове Update, в силу чего менее оптимально, чем первый вариант.

Однако, стоит заметить, что вам посоветовали верный способ поиска ближайшей цели (использование magnitude) Вы также можете попробовать Vector3.Distance, но судя по документации, этот метод работает через magnitude. Или можете попробовать Vector3.sqrMagnitude, он схож по идее работы с magnitude, но работает чуть быстрее, за счет того, что не выполняется операция вычисления квадратного корня, однако в силу этого дает менее точный результат. Вы можете почитать обсуждения на тему методов поиска ближайшего объекта здесь и здесь.

Вам может казаться результат вычисления ближайшей цели неверным, если работаете с вычислениями через Vector3, так как он работает с 3 измерениями (А скорее всего речь идет именно о трехмерной игре, так как вы используете навигацию Unity). Например, если взять игру с видом сверху, то учет разности высот при измерении расстояния может казаться визуально неверным, но точным математически. Таким образом вы можете попробовать считать расстояния с помощью тех же методов magnitude, sqrMagnitude или Distance, но используя Vector2. Тогда при вычислении расстояния учитываться будут только x и y координаты объектов. Такой подход может дать более “верное” решение задачи с точки зрения визуальной части.

ответ дан 31 июл 2017 в 19:29

vmchar's user avatar

vmcharvmchar

4,43117 серебряных знаков25 бронзовых знаков

Вот я написал такой код, но он во-первых ресурсозатратный, а во-вторых – находит расстояние только до ближайшего обьекта.
Добавил обьекты в массив:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rasstoyanie : MonoBehaviour
{
    GameObject[] enemy;
    GameObject player;
    public string nearest;

    private void Start()
    {
        enemy = GameObject.FindGameObjectsWithTag("Enemy");
    }
}

и вот в этой части надо найти вместо 1 обьекта, 3 ближайших:

GameObject Find()
    {
        float dist = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach(GameObject go in enemy)
        {
            Vector3 diff = go.transform.position;
            float currdist = diff.sqrMagnitude;
            if(currdist < dist)
            {
                player = go;
                dist = currdist;
            }        
        }
        return player;  
    }

можете поправить и подсказать, как найти ближайшие 3 обьекта? Просто первый раз с массивами столкнулся.

Поиск по тегам, как найти ближний объект?

Поиск по тегам, как найти ближний объект?

Мне нужно найти объект с тегом Enemy, который находится ближе всего к объекту со скриптом. Я использовать такой код:

Используется csharp

targetEnemy = GameObject.FindWithTag (“Enemy”);
 

Но он находит мне 1-ый Enemy в списке объектов на сцене (во вкладке Hierarchy), по случаю этот объект дальше всех остальных. Подскажите, как мне найти БЛИЖАЙШЕЙ объект с тегом Enemy?

elfinik
UNIверсал
 
Сообщения: 390
Зарегистрирован: 24 фев 2013, 20:03

Re: Поиск по тегам, как найти ближний объект?

Сообщение trololoid 11 авг 2013, 16:18

private float temp=9999;
private GameObject nearest=null;

{
//Получаем любым удобным способом все объекты с тегом и запихиваем в массивлист List;
}

foreach(GameObject go in List){
float tmp2=Vector3.Distance(transform.position,go.position);
if(tmp2<temp){
temp=tmp2;
nearest=go;
}
}

На выходе в nearest будет ближайший :ymparty:

Аватара пользователя
trololoid
Старожил
 
Сообщения: 712
Зарегистрирован: 15 сен 2011, 19:18
Откуда: Туапсе, Краснодарский край, Россия

Re: Поиск по тегам, как найти ближний объект?

Сообщение elfinik 11 авг 2013, 16:23

Сейчас попробую, но такой вопрос… Не сильно ли это затратно? Допустим у меня есть башни, они должны каждый кадр искать таким образом врагов… Есть ли какой то более удобный вариант для этой задачи?

P.S. Враги ходят не по заданной траектории, а по большему плоскому пространству, а башни должны их отстреливать.

elfinik
UNIверсал
 
Сообщения: 390
Зарегистрирован: 24 фев 2013, 20:03

Re: Поиск по тегам, как найти ближний объект?

Сообщение BornFoRdeatH 11 авг 2013, 16:28

Поставить на башню тригер

Не бойся, если ты один, бойся, если ты ноль.

BornFoRdeatH
Адепт
 
Сообщения: 2377
Зарегистрирован: 22 окт 2011, 23:41
Откуда: Украина
Skype: bornfordeath

Re: Поиск по тегам, как найти ближний объект?

Сообщение elfinik 11 авг 2013, 16:33

Ну триггер то это можно, но мне еще и к юнитам нужно это применять, а там с триггером врятли прокатит.

Эх, я не умею создавать массивы и листы, а так же искать ВСЕ теги, только 1 умею искать) В Help’е другого не нашел(

elfinik
UNIверсал
 
Сообщения: 390
Зарегистрирован: 24 фев 2013, 20:03

Re: Поиск по тегам, как найти ближний объект?

Сообщение Левш@ 11 авг 2013, 16:47

Почему не прокатит ? Каждый входящий в триггер враг будет сам себя заносить в списокмассив целей.
Остается только башне обработать список целей и выбрать ближайшего.
Смотри класс Mathf у него есть метод который поможет в отборе (не помню как его звать…).

Аватара пользователя
Левш@
Адепт
 
Сообщения: 4073
Зарегистрирован: 14 окт 2009, 16:34
Откуда: IBERIA
Skype: bars_levsha
  • Сайт

Re: Поиск по тегам, как найти ближний объект?

Сообщение elfinik 11 авг 2013, 16:55

Я полазал, и нашел по массивам только это:

Используется csharp

public GameObject  cam;
        GameObject[] List = new GameObject[100];
List[1] = cam;

Хоть это правильно? Если я так же засуну все объекты в List, все правильно будет?

elfinik
UNIверсал
 
Сообщения: 390
Зарегистрирован: 24 фев 2013, 20:03

Re: Поиск по тегам, как найти ближний объект?

Сообщение Zaic 11 авг 2013, 17:20

Аватара пользователя
Zaic
Старожил
 
Сообщения: 758
Зарегистрирован: 18 июл 2013, 23:13
Откуда: Отсюда
Skype: У меня нет скайпа
  • Сайт


Вернуться в Почемучка

Кто сейчас на конференции

Сейчас этот форум просматривают: Yandex [Bot] и гости: 24



Студворк — интернет-сервис помощи студентам

Скорее всего, данный вопрос задавался много раз, но мне не помогали решения, которые я нашел в интернете… Вместо того, чтобы идти к ближайшему, персонаж-бактерия направляется к самому новому.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
private Transform target;
    public float speed = 2f;
private GameObject targett;
    GameObject[] targets;
    GameObject iol;
    public GameObject Eda;
    public string nearest;
void Update()
    {
        Puk();
        transform.position += (target.position - transform.position).normalized * speed * Time.deltaTime;
        transform.up = Vector3.Lerp(transform.up, (target.position - transform.position), 1.5f * Time.deltaTime);
        if ((target.position - transform.position).sqrMagnitude < 0.1f)
        {
            Spawn();
            Destroy(targett);
            nutrition = nutrition + 1f;
            Puk();
        }
        
    }
 
    void Puk()
    {
        targets = GameObject.FindGameObjectsWithTag("Food");
        targett = GameObject.Find(Find().name);
        target = targett.transform;
    }
 
    GameObject Find()
    {
        float dist = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach (GameObject go in targets)
        {
            Vector3 diff = go.transform.position;
            float currdist = diff.sqrMagnitude;
            if (currdist < dist)
            {
                iol = go;
                dist = currdist;
            }
        }
        return iol;
    }
 void Spawn()
    {
        int x = Random.Range(-10, 10);
        int y = Random.Range(-3, 3);
        int op = Random.Range(1, 100000);
        string ap = op + "";
        GameObject neweda = Instantiate(Eda);
        neweda.transform.position = new Vector3(x, y, 0);
        neweda.name = ap;
 
    }

Для поста, наверно, многовато кода, но я думаю чтобы понять полностью, этого предостаточно.

Буду благодарен за любую помощь, но мне бы код…

{“id”:4034,”url”:”/distributions/4034/click?bit=1&hash=9ae2f9cd299dd62aa01fc66412d456ae03c7e88fa2d2735e5738eb72ed0a3dd6″,”title”:”u0414u0435u043du044cu0433u0438 u043fu043eu043au0443u043fu0430u0442u0435u043bu0435u0439 u043fu0435u0440u0435u0442u0435u043au0430u044eu0442 u043du0430 u043cu0430u0440u043au0435u0442u043fu043bu0435u0439u0441u044b”,”buttonText”:”u041fu043eu0439u043cu0430u0442u044c”,”imageUuid”:”0878293d-0f3c-56d8-886b-1e2bf2da20a9″}

Всем привет. С сегодняшнего дня я решил начать написание цикла статей по работе с ИИ противника. В ходе данного цикла я хочу показать на простых примерах, как можно сделать ИИ в игре, который будет учитывать поле зрения противника, уровень издаваемого шума, умеет патрулировать местность, ставить приоритетные цели и обладать различными типами поведения.

Field of View

В конце цикла статей наш ИИ будет уметь:

  • Получать список целей по FOV;
  • Искать путь до ближайшей цели, будь то враг или что-то другое;
  • Обладать типом (враг, союзник, болванка);
  • Обладать поведением (патруль, поиск цели, бой, следование, убегание);
  • Учитывать уровень шума и освещенности;
  • Работать с инверсной кинематикой (получать Impact конечностей, смотреть на врагов);

Для чего это нужно?

В сегодняшней статье я расскажу о простой реализации поля зрения противника. Дабы исключить архитектурные особенности – мы сделаем все на обычных монобехах.

Итак, начнем с того, что должно делать наше поле зрения:

  • Находить цели, которые попадают в угол обзора;
  • Ставить приоритет на ближайшую цель в области обзора;
  • При выходе текущей цели из поля обзора – сохранять её до определенной дистанции;
  • При полной потере цели – переключиться на другие;

Интерфейс Field of View

Начнем с того, что нам нужен некий интерфейс, который сможет просто обрабатывать наше поле зрения:

public interface IFOV
{
float Radius { get; }
float Angle { get; }
Transform CurrentTargetTransform { get; }

bool HasTargets();
List<Transform> GetAllTargets();
Transform GetNearestTarget();
void ForceRecalculate();
}

Рассмотрим, что содержит наш интерфейс:

  • Параметры Radius, Angle – для того, чтобы получить возможность узнать информацию о FOV;
  • Параметр CurrentTarget (в моем случае для простоты используется Transform, но лучше сделать интерфейс ITarget и работать с ним);
  • Методы для проверки целей – HasTargets, GetAllTargets, GetNearestTarget и ForceRecalculate.

Почему здесь нет метода поиска целей, а сделан только ForceRecalculate? Мы делаем просчет внутри самого компонента FOV, а все его данные получаем через его методы обработки целей. ForceRecalculate нужен нам только тогда, когда к примеру текущая цель умерла и хочет оповестить наш объект об этом.

Базовая реализация FOV

Теперь приступим к самой реализации FOV. По своей сути он работает через оверлап коллайдера, однако вы можете использовать Raycast. Также у компонента FOV есть таймер пересчета целей и чем меньше он будет, тем больше будет нагрузки и тем выше точность поиска. Таймер полезен тогда, когда игрок непосредственно видит ИИ противника и для него выставляется наименьший таймер, а для противников в далеке – наибольший.

internal class FieldOfView : MonoBehaviour, IFOV
{
[Header(“Field of View Parameters”)]
[SerializeField] private float _viewRadius = 3f;
[SerializeField] [Range(0,360)] private float _viewAngle = 90f;
[SerializeField] private float _distanceToLostTarget = 2f;
[SerializeField] private double _updateTimer = 0.2;

[Header(“Layer Mask”)]
[SerializeField] private LayerMask _targetMask;
[SerializeField] private LayerMask _obstructionMask;

// Targets Data
private bool _hasTargets = false;
private bool _isLostTargets = false;
private Transform _currentTarget = null;
private List<Transform> _targetsList = new List<Transform>();
private IDisposable _searchTargets;

public GameEvent<Transform> OnMainTargetChanged = new GameEvent<Transform>();
public GameEvent OnTargetsFound = new GameEvent();
public GameEvent OnTargetsLost = new GameEvent();

// Public Fields
public float Radius => _viewRadius;
public float Angle => _viewAngle;
public Transform CurrentTargetTransform => _currentTarget;

private void Start()
{
_searchTargets = Observable.Interval(TimeSpan.FromSeconds(_updateTimer)).Subscribe(l =>
{
FieldOfViewCheck();
});
}

private void FieldOfViewCheck()
{
// Check Current FOV
if (!_hasTargets || _currentTarget==null)
{
_targetsList.Clear();

Collider[] rangeChecks = Physics.OverlapSphere(transform.position, _viewRadius, _targetMask);
if (rangeChecks.Length > 0)
{
for (int i = 0; i < rangeChecks.Length; i++)
{
bool isSeeTarget = false;
Transform target = rangeChecks[i].transform;
Vector3 directionToTarget = (target.position – transform.position).normalized;

if (Vector3.Angle(transform.forward, directionToTarget) < _viewAngle / 2)
{
float distanceToTarget = Vector3.Distance(transform.position, target.position);
if (!Physics.Raycast(transform.position, directionToTarget, distanceToTarget,
_obstructionMask))
{
isSeeTarget = true;
}
else
isSeeTarget = false;
}
else
isSeeTarget = false;

if (isSeeTarget)
{
_targetsList.Add(target);
}
}
}
}

// Get Nearest Target
if (!_hasTargets && _targetsList.Count > 0)
{
Transform nearestTarget = GetNearestTarget();
if (_currentTarget != nearestTarget && nearestTarget!=null)
{
_hasTargets = true;
_currentTarget = nearestTarget;
OnMainTargetChanged?.Invoke(_currentTarget);
OnTargetsFound?.Invoke();
}
}

// Check Distance to Current Targets
if (_hasTargets)
{
float distanceToTarget = Vector3.Distance(_currentTarget.position, transform.position);
if (distanceToTarget > _distanceToLostTarget)
{
_hasTargets = false;
_currentTarget = null;
return;
}

_isLostTargets = false;
}

if (!_hasTargets && _targetsList.Count < 1)
{
if (!_isLostTargets)
{
_isLostTargets = true;
OnTargetsLost?.Invoke();
}
}
}

public List<Transform> GetAllTargets()
{
return _targetsList;
}

public Transform GetNearestTarget()
{
Transform nearestTarget = null;
float nearestDistance = 0f;

if (_targetsList.Count < 1)
return null;

for (int i = 0; i < _targetsList.Count; i++)
{
float currentDistance = Vector3.Distance(transform.position, _targetsList[i].position);
if (i == 0)
{
nearestTarget = _targetsList[i];
nearestDistance = currentDistance;
continue;
}

if (currentDistance < nearestDistance)
{
nearestTarget = _targetsList[i];
nearestDistance = currentDistance;
}
}

return nearestTarget;
}

public bool HasTargets()
{
return _hasTargets;
}

public void ForceRecalculate()
{
FieldOfViewCheck();
}
}

Теперь разберем подробнее составляющие кода:

  • В методе Start() мы запускаем наш счетчик проверки FieldOfView через интервал. В моем случае используется UniRx, но вы можете сделать реализацию таймера по-другому.
  • Метод FieldOfViewCheck() запускает процесс проверки целей внутри поля зрения. Если изначально у нас нет никаких целей – мы проверяем есть ли кто-то по нужному слою в физике, затем смотрим ближайшую цель и добавляем её в список. Если же цель есть – мы смотрим дистанцию до неё вне зависимости от поля зрения и если главная цель слишком далеко – пересчитываем снова список целей.
  • Дополнительные методы GetAllTargets, GetNearestTarget, HasTargets и ForceRecalculate служат вспомогательными. Они могут использоваться в нашем контроллере ИИ.

Теперь мы научили нашего противника определять ближайшую цель в поле зрения:

Так же важно, что если цель покинет поле зрения, то герой останется на ней сфокусирован, пока её дистанция не увеличиться больше допустимой, в этом случае он опять будет переключаться на ближайшую цель в поле зрения. Так же нужно учесть, что пока здесь не реализовано поведение, которое учитывает урон от ближайших целей (о чем мы поговорим в следующих частях цикла).

Итог

Проверка поля зрения позволяет нам убедиться, что наш ИИ вообще видит кого-то перед собой, цель не перекрывается и задать приоритет по целям исходя из дистанции. В дальнейшем, мы будем комбинировать FieldOfView с показателями шума, издаваемого целями, а также задавать поведение для нашего ИИ.

Следующая часть статьи будет включать в себя обработку целей в зависимости от паттерна поведения нашего ИИ, а также же учитывать инверсную кинематику для того, чтобы наш герой поворачивал голову и туловище к ближайшей цели.

Буду рад пообщаться на эту тему и послушать о ваших реализациях FOV.

Добавить комментарий