¿Por qué no puedo usar el operador '> =' con Vector3s?

9

Estoy tratando de hacer que un rectángulo se mueva entre dos posiciones a las que me refiero como _positionAy _positionB. Ambos son de tipo Vector3. El rectángulo se mueve bien. Sin embargo, cuando llega _positionBno se mueve en la dirección opuesta, como debería.

Regresé al código para echar un vistazo. Llegué a la conclusión de que a medida que el objeto se movía, las ifdeclaraciones en el código perdieron el marco en el que la posición de los rectificadores era igual _positionB. Decidí modificar el código para invertir la dirección si la posición de los rectificadores es mayor o igual que _positionB . Mi código no es demasiado largo, así que lo mostraré a continuación:

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;
            }
        }    
    }
}

Cuando lo cambié, sin embargo, me advirtió del siguiente mensaje de error:

Operador> = no se puede aplicar a operandos de tipo Vector3 y Vector3.

Esto me confunde por dos razones; primero, ambos valores son del mismo tipo de datos. Segundo, usar el operador de comparación ( ==) en los dos valores funciona sin error. ¿Por qué no puedo usar el operador >=con Vector3s?

Javier Martinez
fuente
Nota al margen: debe evitar usar 2 me Boolsgusta _atPosAy _atPosB. Inevitablemente, cometerá un error manteniéndolos sincronizados a ambos, y dará lugar a errores. Es mejor hacer un que enumcontenga todas las posiciones (A, B, quizás otras en el futuro), y usar eso
Alexander - Restablecer a Monica
55
¿Qué debería >=significar para a Vector3? ¿Comparar componentes sabios? Eso no sería un pedido total. Considere usarVector3.MoveTowards
rwols
44
Considere esto: var vec1 = new Vector3(1, 0, 0)y var vec2 = new Vector3(0, 1 ,0). ¿Es vec1 >= vec2verdadero o falso?
gronostaj

Respuestas:

16

Para simplificar la respuesta, Vector3es una costumbre structproporcionada por el UnityEngineespacio de nombres. Cuando creamos tipos classo structtipos personalizados , también debemos definir sus operadores . Como tal, no existe una lógica predeterminada para el >=operador. Como ha señalado Evgeny Vasilyev , _rect_tfm.position == _positionBtiene sentido, como podemos comprobar directamente los Vector3.x, Vector3.yy Vector3.zlos valores. _rect_tfm.position >= _positionBno tiene tanto sentido, debido al hecho de que a Vector3está representado por tres valores separados.

Podríamos sobrecargar la Vector3clase para contener los operadores adecuados en teoría , pero eso parece bastante complicado. En cambio, sería más fácil simplemente extender la Vector3clase con un método adecuado . Dicho esto, parece que tienes la intención de utilizar esta lógica para el movimiento. Como tal, puede resultarle mucho más fácil usar el Vector3.Lerpmétodo; Si es así, lea más abajo.

Agregar métodos de extensión a Vector3

Como se mencionó anteriormente, aplicar <=o >=a Vector3menudo es ilógico. Para el movimiento, probablemente desee leer más sobre el Vector3.Lerpmétodo. Dicho esto, es posible que desee aplicar la <= =>aritmética por otras razones, por lo que le daré una alternativa fácil.

En lugar de aplicar la lógica de Vector3 <= Vector3o Vector3 >= Vector3, propongo extender la Vector3clase para incluir métodos para isGreaterOrEqual(Vector3 other)y isLesserOrEqual(Vector3). Podemos añadir los métodos de extensión a una structo classdeclarándolos en una staticclase que no hereda. También incluimos el objetivo classo structcomo primer parámetro, usando la thispalabra clave. Tenga en cuenta que en mi ejemplo, supongo que quiere asegurarse de que los tres valores principales ( x, yy z) son todos mayores o iguales, o menores o iguales, respectivamente. Puede proporcionar su propia lógica, aquí, según lo requiera.

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;
        }
    }
}

Cuando intentemos llamar a estos métodos desde la Vector3clase, localrepresentaremos la Vector3instancia desde la que estamos llamando al método. Notarás que los métodos son static; los métodos de extensión deben ser static, pero aún debe llamarlos desde una instancia. Dado los métodos de extensión anteriores, ahora puede aplicarlos directamente a sus Vector3tipos.

Vector3 left;
Vector3 right;

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

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

Moviéndose Vector3conVector3.Lerp

Llamar al Vector3.Lerpmétodo nos permite determinar la posición exacta entre dos Vector3valores en un momento dado. Un beneficio adicional de este método es que el Vector3no sobrepase su objetivo . Vector3.Lerptoma tres parámetros; la posición inicial, la posición final y la posición actual representada como un valor entre 0 y 1. Produce la posición resultante como a Vector3, que podemos establecer directamente como la posición actual.

Para resolver su problema, propongo usar Vector3.Lerppara pasar a a targetPosition. Después de llamar al Movemétodo en cada uno Update, podemos verificar si hemos alcanzado dicho objetivo; Lerp.Vector3lo hará sin exceso, por lo que transform.position == targetPositionse convierte en fiable. Ahora podemos comprobar la posición y cambiar el targetPositiona leftPositiono rightPositioninvertir el movimiento, en consecuencia.

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);
}

Puede ver esto demostrado en la siguiente animación. Traduzco el cubo azul con Vector3.LerpUnclamped, lo que nos da un resultado similar a la traducción simple no verificada. Traduzco el cubo rojo usando Vector3.Lerp. Si no se controla, el cubo azul se va al olvido; mientras que el cubo rojo se detiene exactamente donde lo pretendo. Puede leer más sobre este tipo de movimiento en la documentación de Stack Overflow .

Si no se controla, el cubo azul se va al olvido;  mientras que el cubo rojo se detiene exactamente donde lo pretendo.

Gnemlock
fuente
Wow, realmente hiciste un esfuerzo adicional, ¡muchas gracias!
Javier Martínez
27

Definir >=un Vector3tipo no tiene sentido. ¿Qué determina si un vector es mayor que otro? ¿Su magnitud o sus componentes individuales x, y, z?

Un vector es una magnitud y una dirección. Entonces, ¿qué determina qué dirección es mayor?

Si necesita comparar las magnitudes que puede usar sqrMagnitude.

En este caso, se Vector3reemplaza ==simplemente comparar los diferentes componentes para ver si son iguales. (dentro de un umbral)

Esta es la misma razón por la que *no es posible multiplicar dos vectores usando . Simplemente no hay forma matemática de hacerlo. Algunas personas usan *productos de punto, pero ese es un diseño de API poco claro.

Evgeny Vasilyev
fuente
Unity Vector3es un struct, así que el párrafo sobre comparación de referencias no es del todo correcto.
31eee384
Se podría decir cada una de la posición de un vector en cada eje son mayores que los demás de, similar a la comparación de los números enteros 2, sólo como un grupo. Es un poco más limitado en la aplicación en comparación con la comparación de cada propiedad individualmente, pero al menos podría usarse.
Pysis
Esto no es Java. La comparación de referencia no es verdadera en estructuras o clases donde se define el operador igual
Gustavo Maciel
Modifiqué mi respuesta para eliminar esa parte. Sin embargo, C # estaba en un punto de Java. Hasta donde sé, el núcleo de las clases sigue funcionando igual y si == no se sobrescribe, se comporta exactamente como lo haría en Java.
Evgeny Vasilyev
2

Esta es una vieja pregunta, pero para ponerla en términos menos técnicos, un Vector3 es un "contenedor" para 3 valores flotantes: x, y, z.

Puede comparar valores individuales, como comparar los valores de x de dos Vector3, porque son solo números.

Sin embargo, un Vector3 completo no se puede comparar con otro Vector3 porque no hay un solo valor que se pueda usar para comparar los dos.

Dez Boyle
fuente
0

Simplemente agregue a lo que Gnemlock publicó, con respecto a agregar métodos de extensión a la clase Vector3. Hay un problema en la unidad (y estoy seguro que otros motores de juego) cuando se utilizan ciertos operadores de comparación ( ==, <=y >=) entre dos valores de coma flotante, debido a cómo se maneja el cálculo de coma flotante. Mathf.Approximatelydebe usarse en su lugar, por lo tanto, se pueden agregar los siguientes métodos de extensión para verificar si dos vectores son> = o <= entre sí:

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;
    }
}
Antonio
fuente
Ciertamente puede usar esto, si desea que ambas pruebas ≤ & ≥ devuelvan verdadero cuando el valor subestima un poco. Por lo general, aplicamos la verificación aproximadamente igual solo cuando probamos la igualdad a un solo valor en particular. Se "amplía" la verificación desde un solo punto (fácil de omitir) a un pequeño margen de error en ambos lados. ≤ y ≥ ya tienen un margen de error incorporado: cualquier sobreimpulso al extremo inferior o superior respectivamente se captura, por lo que ya son mucho menos susceptibles de perder un caso deseado debido a pequeñas desviaciones en el cálculo.
DMGregory
0

Me gustaría proponer una forma diferente de interpretar esta pregunta. Un patrón de código como este:

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

básicamente está tratando de usar los operadores >=/ <=como "¿el lado izquierdo alcanzó o pasó el lado derecho?" pruebas

Usar >=/ <=para significar "alcanzado o pasado" tiene sentido en un sentido unidimensional, si mi posición es solo un flotador:

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

Pero en el espacio 3D no tenemos una sola línea para medir, para decidir qué lado es "alto / lejano" y qué lado es "bajo / cercano". Por ejemplo, podríamos estar intentando patrullar entre los puntos

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

Así que ahora esperamos patrolStart <= myPosition <= patrolEnden el eje X, pero patrolEnd <= myPosition <= patrolStarten el eje Z. Nuestro operador "alcanzado o pasado" es diferente de un eje a otro, por lo que ya no hay un mapeo claro entre nuestro concepto de pasar un umbral y una simple verificación de desigualdad.

Pero, hay una forma en que podemos elegir una sola línea en el espacio 3D y hacer que nuestro >=/ se <=comporte como el caso de flotación única a lo largo de esta línea que elegimos:

// 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();

Como beneficio adicional, si normaliza el vector del eje antes de usarlo, todos los productos de puntos representan distancias, por lo que puede medir exactamente qué tan lejos está de cada extremo, a lo largo del eje de desplazamiento.

DMGregory
fuente