Чому я не можу використовувати оператор '> =' з Vector3s?


9

Я намагаюся отримати прямокутник для переміщення між двома позиціями, які я називаю як _positionAі _positionB. Обидва мають тип Vector3. Прямокутник рухається просто чудово. Однак, коли вона досягає, _positionBвона не рухається у зворотному напрямку, як слід.

Я повернувся до коду, щоб подивитися. Я прийшов до висновку, що в міру переміщення об'єкта ifзаяви в коді пропускали кадр, в якому позиція ректів була дорівнює _positionB. Я вирішив змінити код у зворотному напрямку, якщо позиція прямої точки перевищує або дорівнює _positionB . Мій код не надто довгий, тому я покажу його нижче:

using UnityEngine;
using System.Collections;

public class Rectangle : MonoBehaviour 
{
    private Vector3 _positionA = new Vector3(-0.97f, -4.28f); //Start position
    private Vector3 _positionB = new Vector3(11.87f, -4.28f); //End position
    private Transform _rect_tfm;
    private bool _atPosA = false, _atPosB = false;

    public Vector2 speed = new Vector2(1f, 0f);

    private void Start()
    {
        _rect_tfm = gameObject.GetComponent<Transform>();
        _rect_tfm.position = _positionA;
        _atPosA = true;
    }

    private void Update()
    {
        /*NOTE: Infinite loops can cause Unity to crash*/
        Move();
    }

    private void Move()
    {
        if (_atPosA)
        {
            _rect_tfm.Translate(speed * Time.deltaTime);

            if (_rect_tfm.position == _positionB)
            {
                _atPosA = false;
                _atPosB = true;
            }
        }

        if (_atPosB)
        {
            _rect_tfm.Translate(-speed * Time.deltaTime);

            if (_rect_tfm.position == _positionA)
            {
                _atPosA = true;
                _atPosB = false;
            }
        }    
    }
}

Однак, коли я змінив його, він попередив мене про таке повідомлення про помилку:

Оператор> = не можна застосувати до операндів типу Vector3 та Vector3.

Це бентежить мене з двох причин; по-перше, обидва значення мають один і той же тип даних. По-друге, використання оператора порівняння ( ==) для двох значень працює без помилок. Чому я не можу використовувати оператора >=з Vector3s?


Бічна примітка: вам слід уникати використання 2 Boolsподібних _atPosAта _atPosB. Неминуче ви помилитесь синхронізуючи їх, і це призведе до помилок. Краще скласти enumмістять усі позиції (A, B, можливо, інші в майбутньому), і використовуючи це
Олександр - Відновити Моніку

5
Що має >=означати для a Vector3? Порівняйте компонентно? Це не було б тотальним замовленням. Подумайте про використанняVector3.MoveTowards
rwols

4
Врахуйте це: var vec1 = new Vector3(1, 0, 0)і var vec2 = new Vector3(0, 1 ,0). Це vec1 >= vec2правда чи неправда?
gronostaj

Відповіді:


16

Для спрощення відповіді Vector3- це звичай, що structнадається UnityEngineпростором імен. Коли ми створюємо власні classабо structтипи, ми також повинні визначати його операторів . Таким чином, для >=оператора немає логіки за замовчуванням . Як зазначив Євген Васильєв , _rect_tfm.position == _positionBмає сенс, тому що ми можемо безпосередньо перевірити Vector3.x, Vector3.yі Vector3.zзначення. _rect_tfm.position >= _positionBне має великого сенсу через те, що а Vector3представлений трьома окремими значеннями.

Ми могли б перевантажити Vector3клас таким чином, щоб містити відповідні оператори в теорії , але це здається досить складним. Замість цього, було б простіше просто розширити на Vector3клас з відповідним способом . Попри це, здається, що ви збираєтесь використовувати цю логіку для руху. Таким чином, вам може бути набагато простіше використовувати Vector3.Lerpметод; якщо так, читайте далі нижче.

Додавання методів розширення до Vector3

Як вже було сказано раніше, застосування <=або >=використання Vector3атрибутів часто є нелогічним. Для руху ви, ймовірно, хочете прочитати далі про Vector3.Lerpметод. При цьому, ви можете застосувати <= =>арифметику з інших причин, тому я дам вам просту чергу.

Замість застосування логіки Vector3 <= Vector3або Vector3 >= Vector3, я пропоную розширити Vector3клас, щоб включити методи для isGreaterOrEqual(Vector3 other)та isLesserOrEqual(Vector3). Ми можемо додати методи розширення до structабо class, оголосивши їх у staticкласі, який не успадковує. Ми також включаємо ціль classабо structяк перший параметр, використовуючи thisключове слово. Зверніть увагу , що в моєму прикладі, я припускаю , що ви маєте в виду , щоб переконатися , що всі три основних значення ( x, yі z) є все більше або рівні, або менше або дорівнюють, відповідно. Тут ви можете надати власну логіку, як вам потрібно.

public static class ExtendingVector3
{
    public static bool IsGreaterOrEqual(this Vector3 local, Vector3 other)
    {
        if(local.x >= other.x && local.y >= other.y && local.z >= other.z)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public static bool IsLesserOrEqual(this Vector3 local, Vector3 other)
    {
        if(local.x <= other.x && local.y <= other.y && local.z <= other.z)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Коли ми намагатимемося викликати ці методи з Vector3класу, localбуде представляти Vector3екземпляр, з якого ми викликаємо метод. Ви зауважите, що методи є static; Методи розширення повинні бути static, але ви все одно повинні викликати їх з екземпляра. Враховуючи наведені вище методи розширення, тепер ви можете застосовувати їх безпосередньо до Vector3типів.

Vector3 left;
Vector3 right;

// Is left >= right?
bool isGreaterOrEqual = left.IsGreaterOrEqual(right);

// Is left <= right?
bool isLesserOrEqual = left.IsLesserOrEqual(right);

Переїзд Vector3сVector3.Lerp

Виклик на Vector3.Lerpметод дозволяє визначити точне положення між двома Vector3значеннями в даний момент часу. Додатковою перевагою цього методу є те, що Vector3воля не перевищить мету . Vector3.Lerpприймає три параметри; початкове положення, кінцеве положення та поточне положення, представлені у вигляді значень між 0 і 1. Це виводить отримане положення у вигляді a Vector3, яке ми можемо безпосередньо встановити як поточне положення.

Вирішуючи вашу проблему, пропоную скористатися Vector3.Lerpпереходом до а targetPosition. Після виклику Moveметоду в кожному Update, ми можемо перевірити, чи досягли ми зазначеної мети; неLerp.Vector3 буде промахуватися, тому стає надійним. Тепер ми можемо перевірити положення і відповідно змінити рух на або назад.transform.position == targetPositiontargetPositionleftPositionrightPosition

public Vector3 leftPosition, rightPosition;
public float speed;
public Vector3 targetPosition;

private void Awake()
{
    targetPosition = rightPosition;
}

private void Update()
{
    Move();

    if(transform.position == targetPosition)
    {
        // We have arrived at our intended position. Move towards the other position.
        if(targetPosition == rightPosition)
        {
            // We were moving to the right; time to move to the left.
            targetPosition = leftPosition;
        }
        else
        {
            // We were moving to the left; time to move to the right.
            targetPosition = rightPosition;
        }
    }
}

private void Move()
{
    // First, we need to find out the total distance we intend to move.
    float distance = Vector3.Distance(transform.position, targetPosition);

    // Next, we need to find out how far we intend to move.
    float movement = speed * Time.deltaTime;

    // We find the increment by simply dividing movement by distance.
    // This will give us a decimal value. If the decimal is greater than
    // 1, we are moving more than the remaining distance. Lerp 
    // caps this number at 1, which in turn, returns the end position.
    float increment = movement / distance;

    // Lerp gives us the absolute position, so we pass it straight into our transform.
    transform.position = Vector3.Lerp(transform.position, targetPosition, increment);
}

Це ви можете побачити в наступній анімації. Я перекладаю синій кубик Vector3.LerpUnclamped, що дає нам подібний результат, як простий неперевірений переклад. Я перекладаю червоний кубик, використовуючи Vector3.Lerp. Залишившись без перевірки, синій куб відходить у небуття; поки червоний куб зупиняється саме там, де я маю намір це зробити. Детальніше про цей тип руху ви можете прочитати в документації про переповнення стека .

Залишившись без перевірки, синій куб відходить у небуття;  поки червоний куб зупиняється саме там, де я маю намір це зробити.


Ух ти, ти справді пройшов зайву милю, дуже дякую!
Хав'єр Мартінес

27

Визначення >=для Vector3типу не має сенсу. Що визначає, чи один вектор більший за інший? Їх величина або їх окремі компоненти x, y, z?

Вектор - це величина та напрямок. Отже, що визначає, який напрямок більший?

Якщо вам потрібно порівняти величини, які ви можете використовувати sqrMagnitude.

У цьому випадку Vector3відміняється ==просто порівняння різних компонентів, щоб побачити, чи вони однакові. (в межах порогу)

Це та сама причина, що множення двох векторів за допомогою *неможливо. Математичного способу зробити це просто немає. Деякі люди використовують *для крапкового продукту, але це незрозумілий дизайн API.


Unity's Vector3є struct, тому абзац про порівняння посилань не зовсім правильний.
31eee384

Це може означати, що положення кожного вектора на кожній осі більше, ніж у іншого, подібне до порівняння двох цілих чисел, лише як група. Він трохи обмежений у застосуванні порівняно з порівнянням кожної властивості окремо, але все одно може бути використаний принаймні.
Pysis

Це не Java. Порівняння посилань не відповідає дійсності в структурах або класах, де визначено оператора рівнянь
Gustavo Maciel

Я змінив свою відповідь, щоб видалити цю частину. Однак C # був у один момент Java. Наскільки я знаю, ядро ​​класів все одно працює так само, і якщо == не над написаним, він поводиться саме так, як це було б у Java.
Євгеній Васильєв

2

Це старе питання, але якщо говорити про менш технічні терміни, Vector3 - це "контейнер" для трьох знаків з плаваючою точкою - x, y, z.

Ви можете порівнювати окремі значення, наприклад, порівнюючи значення x двох Vector3, тому що це просто числа.

Однак цілий Vector3 не можна порівняти з іншим Vector3, оскільки не існує жодного значення, яке можна використати для порівняння двох.


0

Просто додавання до публікації Gnemlock щодо методів розширення до класу Vector3. Там є проблема в єдності (і я впевнений , що інші двигуни гри) при використанні певних операторів порівняння ( ==, <=а >=) між двома значеннями з плаваючою точкою, з - за того , як обробляється розрахунок з плаваючою точкою. Mathf.Approximatelyслід використовувати замість цього, таким чином, наступні методи розширення можуть бути додані для перевірки, чи два вектори є> = або <= один для одного:

using UnityEngine;

public static class ExtendingVector3
{
    public static bool IsGreaterOrEqual(this Vector3 local, Vector3 other)
    {
        bool xCond = local.x > other.x || Mathf.Approximately(local.x, other.x);
        bool yCond = local.y > other.y || Mathf.Approximately(local.y, other.y);
        bool zCond = local.z > other.z || Mathf.Approximately(local.z, other.z);

        if(xCond && yCond && zCond)
            return true;

        return false;
    }

    public static bool IsLesserOrEqual(this Vector3 local, Vector3 other)
    {
        bool xCond = local.x < other.x || Mathf.Approximately(local.x, other.x);
        bool yCond = local.y < other.y || Mathf.Approximately(local.y, other.y);
        bool zCond = local.z < other.z || Mathf.Approximately(local.z, other.z);

        if(xCond && yCond && zCond)
            return true;

        return false;
    }
}

Ви, звичайно, можете скористатись цим, якщо ви хочете, щоб обидва тести ≤ & ≥ повернули справжнє значення, коли значення трохи підкреслює. Зазвичай ми застосовуємо приблизно рівну перевірку лише під час тестування рівності на одне конкретне значення. Це "розширює" перевірку від однієї точки (легко пропустити) до невеликої помилки з обох сторін. ≤ і ≥ вже мають вбудовані похибки: будь-який перевищення відповідно до нижнього або високого кінця потрапляє відповідно, тому вони вже набагато менш сприйнятливі до пропуску потрібного випадку через невеликі відхилення в обчисленні.
DMGregory

0

Я хотів би запропонувати інший спосіб тлумачення цього питання. Такий зразок коду:

if(myPosition >= patrolEnd || myPosition <= patrolStart)
    TurnAround();

в основному намагається використовувати >=/ <=операторів, оскільки "досягла ліва сторона або перейшла праву сторону?" тести.

Використовувати >=/ <=означати "досягнутий або пройдений" має сенс в одновимірному сенсі, якщо моя позиція є лише поплавком:

if(myX >= rightEnd || myX <= leftEnd)
    TurnAround();

Але в тривимірному просторі у нас немає жодної лінії для вимірювання, щоб визначити, яка сторона "висока / далека", а яка - "низька / близька". Наприклад, ми можемо намагатися патрулювати між пунктами

patrolStart = (-10,  0,  5)
patrolEnd   = ( 10,  0, -5)

Тож тепер ми очікуємо patrolStart <= myPosition <= patrolEndна осі X, але patrolEnd <= myPosition <= patrolStartна осі Z. Наш оператор "досягнутий або пройдений" відрізняється від однієї осі до іншої, тому більше немає чіткого відображення між нашою концепцією проходження порогу та простою перевіркою нерівності.

Але є спосіб, як ми можемо виділити лише один рядок у тривимірному просторі та змусити >=/ <=поводитись так, як єдиний поплавковий випадок уздовж цієї лінії, яку ми обрали:

// Here we select the directed line from our start point to our end point.
Vector3 axis = patrolEnd - patrolStart;

// We can make a single number representing the "low" end of our range
// by taking the dot product of this axis with our start point.
float low = Vector3.Dot(axis, patrolStart);

// And the "high" end by dotting this axis with the end point.
float high = Vector3.Dot(axis, patrolEnd);

// And our progress between is the dot product of the axis with our position.
float progress = Vector3.Dot(axis, myPosition);

// Now we can use our turn-around logic just like we were in the 1D case:
if(progress >= high || progress <= low)
    TurnAround();

Як бонус, якщо ви нормалізуєте вектор осі перед його використанням, то всі крапкові добутки представляють відстані, тож ви можете точно виміряти, наскільки ви віддаляєтеся від будь-якого кінця, вздовж осі ходу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.