Как можно оптимизировать этот код для ИИ / робота?

Я сделал ИИ / робота и его дальность / радиус обзора с помощью триггера, его передвижение я сделал с помощью NavMeshAgent и NavMeshObctacle (вроде так называется), можно ли как то проверить достиг ли _agent цели или нет, имею ввиду что если _agent достиг например: 100, 1, 100 и только тогда заново задавать новую цель, только вместо 100, 1, 100 будет игрок? Я уверен что есть ещё какие то способы оптимизировать код, спасибо всем!


using System.Collections;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;

public class FindPlayer : MonoBehaviour
{
    // private
    private int _enemyStatus = 3;
    private int _change;
    private Animator _animator;

    // public static
    public static float enemyHealth = 100;

    [Header("Враг")] 
    [SerializeField] private Material _material;
    [SerializeField] private float _enemyDamage = 20;

    [Header("Игрок")] 
    [SerializeField] private GameObject _player;

    [Header("Цель")] 
    [SerializeField] private NavMeshAgent _agent;

    private void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
        _material = gameObject.GetComponent<MeshRenderer>().material;
        _agent = GetComponent<NavMeshAgent>();
    }

    private void OnTriggerEnter(Collider _collider)
    {
        switch (_collider.gameObject.tag)
        {
            case "Player":
            {
                if (_agent.isActiveAndEnabled)
                {
                    _change = Random.Range(1, 3);
                }
                break;
            }
        }
    }

    private void OnTriggerStay(Collider _collider)
    {
        switch (_collider.gameObject.tag)
        {
            case "Player":
            {
                if (_agent.isActiveAndEnabled)
                {
                    if (_enemyStatus == 3)
                    {
                        if (_change == 1)
                        {
                            print(_change);
                            
                            _material.color = Color.green;
                            _animator.SetBool("Look", false);
                            
                            transform.LookAt(_player.transform);
                            _agent.SetDestination(Vector3.back);

                            _enemyStatus = 1;
                        }
                        else
                        {
                            print(_change);
                        
                            _material.color = Color.red;
                            _animator.SetBool("Look", true);
                    
                            transform.LookAt(_player.transform);
                            _agent.SetDestination(_player.transform.position);

                            _enemyStatus = 2;
                        }
                    }
                    else
                    {
                        switch (_enemyStatus)
                        {
                            case 1:
                            {
                                _material.color = Color.red;
                                _animator.SetBool("Look", true);
                    
                                transform.LookAt(_player.transform);
                                _agent.SetDestination(_player.transform.position);
                                
                                break;
                            }

                            case 2:
                            {
                                _animator.SetBool("Look", false);
                                _material.color = Color.green;
                                
                                transform.LookAt(_player.transform);
                                _agent.SetDestination(Vector3.back);
                                
                                break;
                            }
                        }
                    }
                }
                break;
            }
        }
    }

    private IEnumerator OnTriggerExit(Collider _collider)
    {
        if (_agent.isActiveAndEnabled)
        {
            _animator.SetBool("Look", false);
            _material.color = Color.white;
            
            _agent.SetDestination(transform.position);

            yield return new WaitForSeconds(5);
            
            var whatPosition = Random.Range(1, 3);

            switch (whatPosition)
            {
                case 1:
                {
                    var randomRange = Random.Range(-80, 81);
                    var agentNewPosition = new Vector3(transform.position.x + randomRange, transform.position.y, transform.position.z);
                    _agent.SetDestination(agentNewPosition);
                    break;
                }
                case 2:
                {
                    var randomRange = Random.Range(-80, 81);
                    var agentNewPosition = new Vector3(transform.position.x, transform.position.y, transform.position.z + randomRange);
                    _agent.SetDestination(agentNewPosition); 
                    break;
                }
            }
        }
    }
    
    private void OnCollisionEnter(Collision _collision)
    {
        switch (_collision.gameObject.tag)
        {
            case "Player":
            {
                if (_enemyStatus == 1)
                {
                    GetTarget.playerHealth -= _enemyDamage;
                }
                break;
            }
        }
    }
}    



Ответы (1 шт):

Автор решения: Yaroslav

п.с. пиал комменты идя по строкам кода

using System.Collections;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;

// судя по имени FindPlayer, класс занимается нахождением игрок...
// а почему игрока нужно искать? может просто не терять его? потом и искать не придется
public class FindPlayer : MonoBehaviour
{
    // почему Status цифрами? нужно вангавать что каждая цифра значит? 
    // для кого enum пидумали? 3 это лучше чем 2? теплее чем 9? веселее чем -1?
    // что делает Status врагов в поисковике игрока?
    private int _enemyStatus = 3;
    // change который int... понять и простить...
    private int _change;
    // Animator?... в FindPlayer?... боюсь к методам переходить
    private Animator _animator;

    // public static давай до свидания...
    // жизнь врага... какого-то там врага... в классе поиске игрока? бросай наркотики!
    public static float enemyHealth = 100;

    [Header("Враг")] 
    // Material ?... в FindPlayer?...
    [SerializeField] private Material _material;
    // урон врага...  в классе поиске игрока? завязывай с тяжелыми наркотиками!
    // а почему жизни публично-статичные а у урон приватный? а в прочем какая разница... лол)
    [SerializeField] private float _enemyDamage = 20;

    [Header("Игрок")] 
    // тип GameObject? В 99.99% нужен либо Transform, либо скрипт, а GameObject не нужен
    [SerializeField] private GameObject _player;

    [Header("Цель")] 
    // ты в Header написал "Цель", что не плохой нейминг... но поле почему то, 
    // в итоге названа в честь его типа, а не _target... что случилось по дороге?
    [SerializeField] private NavMeshAgent _agent;

    private void Start()
    {
        // начинаю подозревать что скрипт весит на Enemy, что не капли не оправдывает написанное выше
        // GameObject.Find это боковые колеса для детских велосипедов, 
        // когда нет ни то что архитектуры, даже клятого всеми синглтона
        _player = GameObject.FindGameObjectWithTag("Player");
        _animator = GetComponent<Animator>();
        _material = gameObject.GetComponent<MeshRenderer>().material;
        _agent = GetComponent<NavMeshAgent>();
    }

    private void OnTriggerEnter(Collider _collider)
    {
        // switch с одним case? во имя... науки видимо
        switch (_collider.gameObject.tag)
            case "Player":
                if (_agent.isActiveAndEnabled)
                    // и сразу стало ясно для чего нужен _change... не, показалось
                    _change = Random.Range(1, 3);
                    break;
    }

    private void OnTriggerStay(Collider _collider)
    {
        switch (_collider.gameObject.tag)
        {
            case "Player":
            {
                if (_agent.isActiveAndEnabled)
                {
                    if (_enemyStatus == 3) // если стейт равен чему-то там
                    {
                        if (_change == 1) // и что-то это равно чему-то зачем то то...
                        {
                            // 7ой уровень вложенности операционных скобок... ДА... МЫ ЭТО СДЕЛАЛИ
                            // уде на 3-4 можно было-бы записать все отдельным методом с именем объясняющим что в нем происходит
                            _material.color = Color.green;
                            _animator.SetBool("Look", false);
                            
                            transform.LookAt(_player.transform);
                            _agent.SetDestination(Vector3.back);

                            _enemyStatus = 1;
                            // класс поиска игрока меняет матеилы, управляет аниматором, вращает трансформ, 
                            // прокладывает пути, задает статусы, заказывает еду, выносит мусор, скачивает сериалы,
                            // но точно не ищет игрока
                        }
                        else
                        {
                            дублирование кода
                            ...
                        }
                    }
                    else
                    {
                        switch (_enemyStatus)
                        {
                            case 1:
                            {
                                дублирование кода
                                ...
                                break;
                            }
                            case 2:
                            {
                                дублирование кода
                                ...
                                break;
                            }
                        }
                    }
                }
                break;
            }
        }
    }
     ¦
    ¦
      ¦
   ヾ○シ BREAK 
   ヘ/     FAAAaaa... ll
   ノ

    // почему IEnumerator? боюсь спрашивать
    private IEnumerator OnTriggerExit(Collider _collider) 
    {
        дублированием кода
        ответ на вопрос который я боялся спросить и не зря
        очередное дублированием не понятного кода, который делает что-то там точно не связанное с поиском игрока
        ...
    }
    
    ...
}    

Как оптимизировать? Ctrl+A, Backspace!

Никаких классов типа Player или Enemy не пишут в силу того что они не отличаются. Есть некая UnitModel где куча сущьностей типа статы, пак плавающих показателей вроде хп/мп/стамина, класс передвижения (могут быть разные), атаки, обработки урона, обработчик скина (единственный кто работает с Animator!), ссылка на TargetUnit тоже может быть и т.д. и во всём этом персонаж игрока ничем не отличается от врагов которых он бьет.

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

Вижу что начал с правильного форматирования по стандарту, теперь учись писать осмысленный код, где у классов всего одна ответственность с говорящим названием.

→ Ссылка