Почему я не могу использовать оператор '> =' с Vector3s?

9

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

Я вернулся в код, чтобы посмотреть. Я пришел к выводу, что когда объект перемещается, ifоператоры в коде пропускают кадр, в котором позиция rects была равна _positionB. Я решил изменить код в обратном направлении, если позиция rects больше или равна _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
Что должно >=означать для Vector3? Сравнить по компонентам? Это не было бы полным заказом. Рассмотрите возможность использованияVector3.MoveTowards
rwols
4
Учтите это: var vec1 = new Vector3(1, 0, 0)а var vec2 = new Vector3(0, 1 ,0). Это vec1 >= vec2правда или ложь?
Гроностай

Ответы:

16

Чтобы упростить ответ, Vector3это обычай, structпредоставляемый UnityEngineпространством имен. Когда мы создаем custom classили structтипы, мы также должны определить его операторы . Таким образом, для >=оператора нет логики по умолчанию . Как отметил Евгений Васильев , _rect_tfm.position == _positionBимеет смысл, так как мы можем непосредственно проверить Vector3.x, Vector3.yи Vector3.zзначение. _rect_tfm.position >= _positionBне имеет особого смысла из-за того, что a 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 == targetPositionстановится надежным. Теперь мы можем проверить положение и изменить , targetPositionчтобы leftPositionили rightPositionизменить направление движения, соответственно.

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. Оставленный непроверенным, синий куб уходит в забвение; в то время как красный куб останавливается именно там, где я собираюсь. Вы можете прочитать больше об этом типе движения в документации по переполнению стека .

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

Gnemlock
источник
Вау, ты действительно прошел лишнюю милю, спасибо тебе большое!
Хавьер Мартинес
27

Определение >=для Vector3типа не имеет смысла. Что определяет, если один вектор больше другого? Их величина или их отдельные компоненты x, y, z?

Вектор - это величина и направление. Так что определяет, какое направление больше?

Если вам нужно сравнить величины, которые вы можете использовать sqrMagnitude .

В этом случае Vector3 переопределяет, ==чтобы просто сравнить различные компоненты, чтобы увидеть, являются ли они одинаковыми. (в пределах порога)

Это та же самая причина, по которой умножение двух векторов *невозможно. Там просто нет математического способа сделать это. Некоторые люди используют *для точечного продукта, но это неясный дизайн API.

Евгений Васильев
источник
Юнити Vector3являются struct, таким образом , пункт о эталонном сравнении не совсем верно.
31eee384
Это может означать, что каждое положение одного вектора на каждой оси больше, чем положение другого, аналогично сравнению двух целых чисел только в группе. Он немного более ограничен в применении по сравнению со сравнением каждого свойства в отдельности, но все же может использоваться по крайней мере.
Пизис
Это не Java. Сравнение ссылок неверно в структурах или классах, где определен оператор равенства
Густав
Я изменил свой ответ, чтобы удалить эту часть. Однако 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;
    }
}
Энтони
источник
Вы, безусловно, можете использовать это, если хотите, чтобы оба теста ≤ & ≥ возвращали значение true, когда значение немного меньше значения. Обычно, однако, мы применяем примерно равную проверку только при проверке на равенство с одним конкретным значением. Он «расширяет» проверку от одной точки (легко пропустить) до небольшого погрешности с обеих сторон. ≤ и ≥ уже имеют встроенный предел погрешности: любое превышение на нижний или верхний предел соответственно фиксируется, поэтому они уже гораздо менее подвержены отсутствию требуемого случая из-за небольших отклонений в расчете.
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();

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

ДМГригорий
источник