Есть ли простой и эффективный способ проверить точный кадр, в котором логическое значение меняется с true на false или наоборот, а затем вызвать функцию?

Я впервые пишу здесь, поэтому прошу прощения, если не предоставил вам достаточно подробностей. У меня такое чувство, будто я схожу с ума, пытаясь сделать что-то, что, как мне казалось, будет таким простым и понятным! Для контекста: сейчас я пишу состояния поведения для вражеского охранника в проекте стелс-игры, над которым я работаю. Основные вещи, такие как патрулирование, расследование, бой и поиск. Я пытаюсь выяснить точный момент, когда игрок выходит из поля зрения противника. Затем в этом же кадре я захвачу положение игрока и сохранит его, чтобы враг мог обыскивать эту область.

Я не уверен, заключается ли моя проблема в недостатке знаний C#, или мне нужен перерыв или что-то в этом роде, возможно, комбинация того и другого, но, тем не менее, я чувствую, что уже должна быть система для этого, встроенная в Unity? Нет?

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

Спасибо!

(Редактировать): Вот мой код видения врага:

// Obstruction raycast check
    if (Physics.Raycast(headObj.position, (player.transform.position - headObj.position).normalized, Vector3.Distance(headObj.position, player.transform.position), obstructionLayer) == false)
    {
        if (playerInVisionCone)
        {
            if (awareness < awarenessCombatThreshold)
            {
                awareness += Time.deltaTime * distanceAwarenessMultiplier;
            }
            
        }
        // Enemy gains less awareness when looking through peripheral vision
        // Enemy can't see player if they are crouching or sliding inside the peripheral vision cone
        else if (playerInPVisionCone && !playerInVisionCone && !isCrouched && !isSliding)
        {
            if (awareness < awarenessCombatThreshold)
            {
                awareness += Time.deltaTime * pVisionAwarenessMultiplier * distanceAwarenessMultiplier;
            }
        }
        // Player is not in vision cone of enemy
        else if (!playerInPVisionCone && !playerInVisionCone)
        {
            if (awareness > 0)
            {
                awareness -= Time.deltaTime * awarenessCooldownMultiplier;
                
            }
        }
    }
    // Player is behind a wall
    else
    {
        if (awareness > 0)
        {
            awareness -= Time.deltaTime * awarenessCooldownMultiplier;
        }
    }

Опубликуйте свой код. В противном случае мы не сможем вам помочь.

Dai 17.08.2024 04:33

ВЫ рисуете все это, верно? Разве вы не можете определить линию обзора для каждого нарисованного кадра?

Tim Roberts 17.08.2024 05:17

@TimRoberts Да, но мне нужен точный кадр, в котором изменяется логическое значение. В этом случае, как только линия видимости нарушена, логическое значение будет истинным ровно для одного кадра, поскольку функция поиска выполняется только один раз. Затем в следующем кадре, даже если игрок все еще не находится в поле зрения, логическое значение должно быть ложным. Vector3 «lastKnownPos» должен быть позицией игрока в том же кадре, в котором линия обзора нарушена. Если функция запускается в каждом кадре, враг будет захватывать позицию игрока в каждом кадре, что приводит к тому, что у врага, по сути, есть «взломы стен». Если это имеет смысл

Lex Butcher 17.08.2024 06:55

Но вы можете проверять это условие для каждого рисуемого кадра, верно? Это все под вашим контролем.

Tim Roberts 17.08.2024 07:18

@ТимРобертс Да

Lex Butcher 17.08.2024 07:47

Рассматривали ли вы возможность использования свойства вместо поля? В противном случае, что мешает вам использовать событие вместо проверки поля опросом?

derHugo 17.08.2024 12:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть ли простой и эффективный способ проверить точный кадр, в котором логическое значение меняется с true на false или наоборот, а затем вызвать функцию?

  1. Классический способ сделать это — сохранить значение в логическом поле, а затем сравнить новое значение со старым значением в следующем кадре:

    bool _oldValue;
    
    void Update() {
        bool newValue = your calculations;
        if (newValue != _oldValue) {
            _oldValue = newValue;
            YourFunction();
        }
    }
    
    void YourFunction() { }
    

    Это правильный способ сделать это, когда вся ваша логика находится в одном классе. Если в какой-то момент у вас появится много таких переменных и это станет беспорядок, вам следует рассмотреть возможность рефакторинга вашего кода на несколько классов.


  1. Вы можете использовать свойство:

    bool _value;
    bool Value {
        set {
            if (_value == value)
               return;
    
            _value = value;
            YourFunction();
        }
    }
    
    void YourFunction() { }
    
    void Update() {
        Value = your calculations;
    }
    

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


  1. Также возможно инкапсулировать значение в классе:

    public class Value {
       bool _value;
       public bool HasChanged { get; private set; }
       public Value(bool value = false) => _value = value;
       public void Update(bool newValue) {
           IsChanged = newValue != _value;
           _value = newValue;
       }
    }
    

    но, как вы можете видеть ниже, это само по себе не дает много преимуществ с точки зрения кода:

    Value _myValue = new();
    
    void Update() {
        bool newValue = your calculations;
        _myValue.Update(newValue);
        if (_myValue.IsChanged) {
            YourFunction();
        }
    }
    

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


  1. Если в вашем коде есть несколько классов, вы можете начать использовать события. Это также правильный способ сделать это, когда некоторые API предоставляют вам события. Вот реализация простого события C#:

    public class Value {
        bool _value;
        public event Action ValueChanged;
        public Value(bool value = false) => _value = value;
        public void Update(bool newValue) {
            if (newValue == _value)
                return;
    
            _value = newValue;
            ValueChanged?.Invoke();
    
        }
    }
    

    И затем вы используете его следующим образом:

    Value _myValue = new();
    void OnEnable() => _myValue.ValueChanged += YourFunction;
    void OnDisable() => _myValue.ValueChanged -= YourFunction;
    void YourFunction() { }
    
    void Update() {
        bool newValue = your calculations;
        _myValue.Update(newValue);
    }
    

    Этот код уже кричит, что часть your calculations должна перейти в Update класса Value, поскольку теперь ясно, что именно этот класс принимает решения. Я не буду здесь углубляться в рефакторинг, но извлечение классов является распространенным инструментом. Также не забывайте переименовывать классы в соответствии с их назначением.

    События также допускают параметры:

    public class Value {
        bool _value;
        public event Action<bool> ValueChanged;
        public Value(bool value = false) => _value = value;
        public void Update(bool newValue) {
            if (newValue == _value)
                return;
    
            _value = newValue;
            ValueChanged?.Invoke(_value);
        }
    }
    
    Value _myValue = new();
    void OnEnable() => _myValue.ValueChanged += YourFunction;
    void OnDisable() => _myValue.ValueChanged -= YourFunction;
    void YourFunction(bool newValue) { }
    
    void Update() {
        bool newValue = your calculations;
        _myValue.Update(newValue);
    }
    

    События C# также работают с MonoBehaviour:

    public class Foo : MonoBehaviour {
        public event Action HasHappened;
    }
    
    public class Bar : MonoBehaviour {
        [SerializeField] Foo _foo;
    
        void OnEnable() => _foo.HasHappened += OnHappened;
        void OnDisable() => _foo.HasHappened -= OnHappened;
    
        void OnHappened() { }
    }
    

    Таким образом, вы можете сделать класс Value классом MonoBehaviour вместо того, чтобы отправлять все параметры в его Update. Или вы можете использовать события для общения между вашими Enemy и Player классами. Распространенной реализацией является то, что один класс напрямую вызывает методы другого и подписывается на его события, чтобы получать от него ответ.

    Кроме того, события C# не доступны инспектору. Для этого вы можете использовать Unity Events


  1. Наконец, существуют более продвинутые инструменты, такие как сигналы (часто входящие в состав фреймворков внедрения зависимостей, например Zenject ) или реактивные расширения, например. UniRx или R3. Эти инструменты реализуют разные подходы к общению между классами.

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

Ого, это вполне могло спасти мой проект. Большое спасибо, чувак! Я использовал первый пример в своем проекте, и он мгновенно устранил все мои проблемы. Я также смог немного изменить его, чтобы он лучше соответствовал моим потребностям, и он работает безупречно. Я оставлю эти два метода в своем репертуаре и планирую потратить пару часов или около того на изучение событий. Я раньше видел их использование в Интернете, но сам никогда в них не вникал. Будь счастлив, чувак!

Lex Butcher 17.08.2024 21:11

Другие вопросы по теме