Я сделал 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
13.5k12 золотых знаков43 серебряных знака72 бронзовых знака
задан 31 июл 2017 в 15:36
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
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 TriumphovIvan Triumphov
1,1081 золотой знак14 серебряных знаков37 бронзовых знаков
Идеологически можно подойти к решению 2 способами:
- Один раз найти ближайшую цель, запомнить ее и двигаться в ее направлении до тех пор, пока она не будет уничтожена
- Каждый раз при вызове Update() пересчитывать расстояния и каждый раз искать ближайшую цель. Это может быть точнее, чем первый вариант, потому что будет учитывать скорость перемещения целей (если они движутся, и если они движутся с разной скоростью), однако требует пересчета при каждом вызове Update, в силу чего менее оптимально, чем первый вариант.
Однако, стоит заметить, что вам посоветовали верный способ поиска ближайшей цели (использование magnitude) Вы также можете попробовать Vector3.Distance, но судя по документации, этот метод работает через magnitude. Или можете попробовать Vector3.sqrMagnitude, он схож по идее работы с magnitude, но работает чуть быстрее, за счет того, что не выполняется операция вычисления квадратного корня, однако в силу этого дает менее точный результат. Вы можете почитать обсуждения на тему методов поиска ближайшего объекта здесь и здесь.
Вам может казаться результат вычисления ближайшей цели неверным, если работаете с вычислениями через Vector3, так как он работает с 3 измерениями (А скорее всего речь идет именно о трехмерной игре, так как вы используете навигацию Unity). Например, если взять игру с видом сверху, то учет разности высот при измерении расстояния может казаться визуально неверным, но точным математически. Таким образом вы можете попробовать считать расстояния с помощью тех же методов magnitude, sqrMagnitude или Distance, но используя Vector2. Тогда при вычислении расстояния учитываться будут только x и y координаты объектов. Такой подход может дать более “верное” решение задачи с точки зрения визуальной части.
ответ дан 31 июл 2017 в 19:29
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 будет ближайший
-
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# | ||
|
Для поста, наверно, многовато кода, но я думаю чтобы понять полностью, этого предостаточно.
Буду благодарен за любую помощь, но мне бы код…
{“id”:4034,”url”:”/distributions/4034/click?bit=1&hash=9ae2f9cd299dd62aa01fc66412d456ae03c7e88fa2d2735e5738eb72ed0a3dd6″,”title”:”u0414u0435u043du044cu0433u0438 u043fu043eu043au0443u043fu0430u0442u0435u043bu0435u0439 u043fu0435u0440u0435u0442u0435u043au0430u044eu0442 u043du0430 u043cu0430u0440u043au0435u0442u043fu043bu0435u0439u0441u044b”,”buttonText”:”u041fu043eu0439u043cu0430u0442u044c”,”imageUuid”:”0878293d-0f3c-56d8-886b-1e2bf2da20a9″}
Всем привет. С сегодняшнего дня я решил начать написание цикла статей по работе с ИИ противника. В ходе данного цикла я хочу показать на простых примерах, как можно сделать ИИ в игре, который будет учитывать поле зрения противника, уровень издаваемого шума, умеет патрулировать местность, ставить приоритетные цели и обладать различными типами поведения.
В конце цикла статей наш ИИ будет уметь:
- Получать список целей по 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.