Por que não posso usar o operador '> =' com Vector3s?

9

Estou tentando fazer com que um retângulo se mova entre duas posições às quais me refiro como _positionAe _positionB. Ambos são do tipo Vector3. O retângulo se move muito bem. No entanto, quando chega _positionB, não se move na direção oposta, como deveria.

Voltei ao código para dar uma olhada. Cheguei à conclusão de que, à medida que o objeto se movia, as ifinstruções no código perdiam o quadro no qual a posição rects era igual _positionB. Decidi modificar o código para inverter a direção se a posição rects for maior ou igual a _positionB . Meu código não é muito longo, então eu o mostrarei abaixo:

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

Quando eu mudei, no entanto, ele me avisou da seguinte mensagem de erro:

Operador> = não pode ser aplicado a operandos do tipo Vector3 e Vector3.

Isso me confunde por duas razões; primeiro, ambos os valores são do mesmo tipo de dados. Segundo, o uso do operador de comparação ( ==) nos dois valores funciona sem erros. Por que não posso usar o operador >=com Vector3s?

Javier Martinez
fonte
Nota lateral: você deve evitar usar 2 Boolscomo _atPosAe _atPosB. Inevitavelmente, você cometerá um erro ao manter os dois em sincronia, e isso causará bugs. É melhor criar um enumcontendo todas as posições (A, B, talvez outras no futuro), e usando isso
Alexander - Reinstate Monica
5
O que deveria >=significar para um Vector3? Comparar componentes? Isso não seria um pedido total. Considere usarVector3.MoveTowards
rwols
4
Considere o seguinte: var vec1 = new Vector3(1, 0, 0)e var vec2 = new Vector3(0, 1 ,0). É vec1 >= vec2verdadeira ou falsa?
Gronostaj #

Respostas:

16

Para simplificar a resposta, Vector3é um costume structfornecido pelo UnityEngineespaço para nome. Quando criamos tipos classou personalizados struct, também devemos definir seus operadores . Como tal, não há lógica padrão para o >=operador. Como apontado por Evgeny Vasilyev , _rect_tfm.position == _positionBfaz sentido, como podemos verificar diretamente as Vector3.x, Vector3.ye Vector3.zvalores. _rect_tfm.position >= _positionBnão faz tanto sentido, devido ao fato de que a Vector3é representado por três valores separados.

Poderíamos sobrecarregar a Vector3classe para conter os operadores adequados na teoria , mas isso parece bastante complicado. Em vez disso, seria mais fácil simplesmente estender a Vector3classe com um método adequado . Dito isto, parece que você pretende usar essa lógica para o movimento. Como tal, você pode achar muito mais fácil usar o Vector3.Lerpmétodo; Nesse caso, leia mais abaixo.

Adicionando métodos de extensão a Vector3

Como mencionado anteriormente, aplicar <=ou >=a um Vector3é frequentemente ilógico. Para movimento, você provavelmente deseja ler mais sobre o Vector3.Lerpmétodo. Dito isto, você pode aplicar a <= =>aritmética por outros motivos, portanto, darei uma alternativa fácil.

Em vez de aplicar a lógica de Vector3 <= Vector3ou Vector3 >= Vector3, proponho estender a Vector3classe para incluir métodos para isGreaterOrEqual(Vector3 other)e isLesserOrEqual(Vector3). Podemos adicionar métodos de extensão a structou classdeclarando-os em uma staticclasse que não herda. Também incluímos o destino classou structcomo o primeiro parâmetro, usando a thispalavra - chave Observe que, no meu exemplo, suponho que você queira garantir que todos os três valores principais ( x, ye z) sejam todos maiores ou iguais, ou menores ou iguais, respectivamente. Você pode fornecer sua própria lógica, aqui, conforme necessário.

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

Quando tentamos chamar esses métodos da Vector3classe, localrepresentamos a Vector3instância da qual estamos chamando o método. Você observará que os métodos são static; métodos de extensão devem ser static, mas você ainda precisa chamá-los de uma instância. Dado os métodos de extensão acima, agora você pode aplicá-los diretamente aos seus Vector3tipos.

Vector3 left;
Vector3 right;

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

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

Movendo-se Vector3comVector3.Lerp

Chamar o Vector3.Lerpmétodo nos permite determinar a posição exata entre dois Vector3valores em um determinado momento. Um benefício adicional desse método é que o Vector3não ultrapassará seu objetivo . Vector3.Lerpleva três parâmetros; a posição inicial, a posição final e a posição atual representada como um valor entre 0 e 1. Ela gera a posição resultante como a Vector3, a qual podemos definir diretamente como a posição atual.

Resolvendo o seu problema, proponho usar Vector3.Lerpa mudança para a targetPosition. Depois de chamar o Movemétodo em cada um Update, podemos verificar se atingimos o objetivo; Lerp.Vector3vai não ultrapassagem, por isso transform.position == targetPositiontorna-se confiável. Agora podemos verificar a posição e alterar targetPositionpara leftPositionou rightPositionpara reverter o movimento, de acordo.

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

Você pode ver isso demonstrado na animação a seguir. Traduzo o cubo azul com Vector3.LerpUnclamped, o que nos dá um resultado semelhante à tradução desmarcada simples. Eu traduzo o cubo vermelho usando Vector3.Lerp. Deixado desmarcado, o cubo azul se move para o esquecimento; enquanto o cubo vermelho para exatamente onde eu pretendo. Você pode ler mais sobre esse tipo de movimento na documentação Estouro de pilha .

Deixado desmarcado, o cubo azul se move para o esquecimento;  enquanto o cubo vermelho para exatamente onde eu pretendo.

Gnemlock
fonte
Uau, você realmente se superou, muito obrigado!
Javier Martinez
27

Definir >=para um Vector3tipo não faz sentido. O que determina se um vetor é maior que outro? Sua magnitude ou seus componentes individuais x, y, z?

Um vetor é uma magnitude e uma direção. Então, o que determina qual direção é maior?

Se você precisar comparar as magnitudes, poderá usar sqrMagnitude.

Nesse caso, Vector3substitui-o ==para simplesmente comparar os diferentes componentes para ver se são iguais. (dentro de um limite)

Esta é a mesma razão pela qual multiplicar dois vetores usando *não é possível. Simplesmente não existe uma maneira matemática de fazê-lo. Algumas pessoas usam *para produtos pontuais, mas esse é um design de API pouco claro.

Evgeny Vasilyev
fonte
Unidade Vector3é um struct, então o parágrafo sobre comparação de referências não está certo.
31eee384
Ele poderia significar cada uma a posição de um vector em cada eixo é maior do que os outros de, semelhante à comparação de 2 inteiros, apenas como um grupo. Sua aplicação é um pouco mais limitada do que a comparação de cada propriedade individualmente, mas ainda pode ser usada pelo menos.
Pysis
Isto não é Java. Comparação de referência não é verdade em estruturas ou classes onde a é igual operador é definido
Gustavo Maciel
Modifiquei minha resposta para remover essa parte. No entanto, o C # estava em um ponto Java. Tanto quanto eu sei, o núcleo das classes ainda funciona da mesma forma e se == não tiver sido escrito em excesso, ele se comporta exatamente como faria em java.
Evgeny Vasilyev
2

Essa é uma pergunta antiga, mas para termos menos técnicos, um Vector3 é um "contêiner" para três valores flutuantes - x, y, z.

Você pode comparar valores individuais, como comparar os valores x de dois Vector3s, porque são apenas números.

No entanto, um Vector3 inteiro não pode ser comparado a outro Vector3 porque não há um valor único que possa ser usado para comparar os dois.

Dez Boyle
fonte
0

Apenas adicionando ao que o Gnemlock postou, sobre a adição de métodos de extensão à classe Vector3. Há um problema no Unity (e eu tenho certeza que outros mecanismos de jogos) ao usar certos operadores de comparação ( ==, <=e >=) entre dois valores flutuantes, devido à maneira como o cálculo do ponto flutuante é tratado. Mathf.Approximatelydeve ser usado, portanto, os seguintes métodos de extensão podem ser adicionados para verificar se dois vetores são> = ou <= um ao outro:

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;
    }
}
Anthony
fonte
Certamente, você pode usar isso se desejar que os testes ≤ & ≥ retornem true quando o valor ultrapassar um pouco. Geralmente, porém, aplicamos a verificação aproximadamente igual apenas ao testar a igualdade em um único valor específico. Ele "amplia" a verificação de um único ponto (fácil de perder) para uma pequena margem de erro de ambos os lados. ≤ e ≥ já possuem uma margem de erro embutida: qualquer superação para a extremidade baixa ou alta é capturada, portanto, elas são muito menos suscetíveis a perder um caso desejado devido a pequenos desvios no cálculo.
DMGregory
0

Eu gostaria de propor uma maneira diferente de interpretar essa pergunta. Um padrão de código como este:

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

está basicamente tentando usar os operadores >=/ <=como "o lado esquerdo alcançou ou passou pelo lado direito?" testes.

Usar >=/ <=para significar "alcançado ou ultrapassado" faz sentido no sentido unidimensional, se minha posição for apenas uma flutuação:

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

Porém, no espaço 3D, não temos uma única linha para medir, para decidir qual lado é "alto / distante" e qual lado é "baixo / próximo". Por exemplo, poderíamos estar tentando patrulhar entre os pontos

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

Então agora esperamos patrolStart <= myPosition <= patrolEndno eixo X, mas patrolEnd <= myPosition <= patrolStartno eixo Z. Nosso operador "alcançado ou passado" é diferente de um eixo para outro, portanto, não há mais um mapeamento claro entre nosso conceito de passar um limite e uma simples verificação de desigualdade.

Mas há uma maneira de escolher apenas uma linha no espaço 3D e fazer com que nosso >=/ <=se comporte como o caso de flutuação única ao longo dessa linha que escolhemos:

// 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 bônus, se você normalizar o vetor do eixo antes de usá-lo, todos os produtos pontuais representam distâncias, para que você possa medir exatamente a que distância está de cada extremidade, ao longo do eixo da viagem.

DMGregory
fonte