Perché non posso usare l'operatore '> =' con Vector3s?


9

Sto cercando di far spostare un rettangolo tra due posizioni a cui mi riferisco come _positionAe _positionB. Entrambi sono di tipo Vector3. Il rettangolo si muove bene. Tuttavia, quando raggiunge _positionBnon si muove nella direzione opposta, come dovrebbe.

Sono tornato nel codice per dare un'occhiata. Sono giunto alla conclusione che mentre l'oggetto si spostava, le ifistruzioni nel codice mancavano il frame in cui la posizione dei rects era uguale _positionB. Ho deciso di modificare il codice per invertire la direzione se la posizione di rects è maggiore o uguale a _positionB . Il mio codice non è troppo lungo, quindi lo mostrerò di seguito:

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 l'ho modificato, tuttavia, mi ha avvisato del seguente messaggio di errore:

Operatore> = non può essere applicato agli operandi di tipo Vector3 e Vector3.

Questo mi confonde per due motivi; in primo luogo, entrambi i valori sono dello stesso tipo di dati. In secondo luogo, l'utilizzo dell'operatore di confronto ( ==) sui due valori funziona senza errori. Perché non posso usare l'operatore >=con Vector3s?


Nota a margine: dovresti evitare di usare 2 Boolslike _atPosAe _atPosB. Inevitabilmente, commetterai un errore mantenendo entrambi sincronizzati e porterai a bug. È meglio fare un enumcontenente tutte le posizioni (A, B, forse altre in futuro), e usarlo
Alexander - Reinstate Monica

5
Cosa dovrebbe >=significare per un Vector3? Confronta i componenti? Non sarebbe un ordinamento totale. Prendi in considerazione di usareVector3.MoveTowards
rwols

4
Considera questo: var vec1 = new Vector3(1, 0, 0)e var vec2 = new Vector3(0, 1 ,0). È vec1 >= vec2vero o falso?
gronostaj,

Risposte:


16

Per semplificare la risposta, Vector3è un'abitudine structfornita dallo UnityEnginespazio dei nomi. Quando creiamo personalizzazioni classo structtipi, dobbiamo anche definire i suoi operatori . Pertanto, non esiste una logica predefinita per l' >=operatore. Come sottolineato da Evgeny Vasilyev , _rect_tfm.position == _positionBha senso, come si può controllare direttamente i Vector3.x, Vector3.ye Vector3.zvalori. _rect_tfm.position >= _positionBnon ha molto senso, a causa del fatto che a Vector3è rappresentato da tre valori separati.

Potremmo sovraccaricare la Vector3classe per contenere in teoria gli operatori adatti , ma sembra piuttosto complicato. Invece, sarebbe più semplice estendere semplicemente la Vector3classe con un metodo adatto . Detto questo, sembra che tu abbia intenzione di usare questa logica per il movimento. Pertanto, potrebbe essere molto più semplice utilizzare il Vector3.Lerpmetodo; in tal caso, leggi di seguito.

Aggiunta di metodi di estensione a Vector3

Come accennato in precedenza, l'applicazione <=o >=a Vector3è spesso illogica. Per il movimento, probabilmente vorrai leggere più avanti per il Vector3.Lerpmetodo. Detto questo, potresti voler applicare l' <= =>aritmetica per altri motivi, quindi ti darò una facile alternativa.

Invece di applicare la logica di Vector3 <= Vector3o Vector3 >= Vector3, propongo di estendere la Vector3classe per includere metodi per isGreaterOrEqual(Vector3 other)e isLesserOrEqual(Vector3). È possibile aggiungere metodi di estensione a structo classdichiarandoli in una staticclasse che non eredita. Includiamo anche il target classo structcome primo parametro, usando la thisparola chiave. Si noti che nel mio esempio, suppongo che si intende far sì che tutti i tre valori principali ( x, ye z) sono tutte maggiore o uguale, o minore o uguale, rispettivamente. Puoi fornire la tua logica, qui, come richiesto.

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 proviamo a chiamare questi metodi dalla Vector3classe, localrappresenterà l' Vector3istanza da cui stiamo chiamando il metodo. Noterai che i metodi sono static; i metodi di estensione devono essere static, ma è comunque necessario chiamarli da un'istanza. Dati i metodi di estensione di cui sopra, ora puoi applicarli direttamente ai tuoi Vector3tipi.

Vector3 left;
Vector3 right;

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

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

Muoversi Vector3conVector3.Lerp

La chiamata del Vector3.Lerpmetodo ci consente di determinare la posizione esatta tra due Vector3valori in un determinato momento. Un ulteriore vantaggio di questo metodo è che il Vector3non supererà il suo obiettivo . Vector3.Lerpaccetta tre parametri; la posizione iniziale, la posizione finale e la posizione corrente rappresentata come un valore compreso tra 0 e 1. Emette la posizione risultante come a Vector3, che possiamo impostare direttamente come posizione corrente.

Risolvendo il tuo problema, propongo di utilizzare Vector3.Lerpper passare a targetPosition. Dopo aver chiamato il Movemetodo in ciascuno Update, possiamo verificare se abbiamo raggiunto tale obiettivo; nonLerp.Vector3 supererà , quindi diventa affidabile. Ora possiamo controllare la posizione e cambiare in o per invertire il movimento, di conseguenza.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);
}

Puoi vederlo dimostrato nella seguente animazione. Traduco il cubo blu con Vector3.LerpUnclamped, che ci dà un risultato simile alla semplice traduzione non selezionata. Traduco usando il cubo rosso Vector3.Lerp. Lasciato deselezionato, il cubo blu si sposta nell'oblio; mentre il cubo rosso si ferma esattamente dove intendo. Puoi leggere ulteriori informazioni su questo tipo di movimento nella documentazione Stack Overflow .

Lasciato deselezionato, il cubo blu si sposta nell'oblio;  mentre il cubo rosso si ferma esattamente dove intendo.


Wow, hai davvero fatto il possibile, grazie mille!
Javier Martinez,

27

Definire >=per un Vector3tipo non ha senso. Cosa determina se un vettore è maggiore di un altro? La loro grandezza o i loro singoli componenti x, y, z?

Un vettore è una grandezza e una direzione. Quindi cosa determina quale direzione è maggiore?

Se hai bisogno di confrontare le magnitudini che puoi usare sqrMagnitude.

In questo caso, Vector3sostituisce ==semplicemente il confronto dei diversi componenti per vedere se sono uguali. (entro una soglia)

Questo è lo stesso motivo per cui *non è possibile moltiplicare due vettori usando . Semplicemente non esiste un modo matematico di farlo. Alcune persone usano *per il prodotto punto, ma questo è un design API poco chiaro.


Unity's Vector3è un struct, quindi il paragrafo sul confronto dei riferimenti non è del tutto corretto.
31eee384,

Si potrebbe significare ciascuna posizione proprio vettoriale su ciascun asse sono maggiori rispetto alle altre di, simile a confronto 2 numeri interi, solo come un gruppo. È un po 'più limitato nell'applicazione rispetto al confronto di ciascuna proprietà singolarmente, ma potrebbe comunque essere usato almeno.
Pysis,

Questo non è Java. Il confronto di riferimento non è vero nelle strutture o nelle classi in cui è definito l'operatore di uguaglianza
Gustavo Maciel,

Ho modificato la mia risposta per rimuovere quella parte. Comunque C # era ad un certo punto Java. Per quanto ne so, il nucleo delle classi funziona ancora allo stesso modo e se == non è scritto, si comporta esattamente come sarebbe in Java.
Evgeny Vasilyev,

2

Questa è una vecchia domanda ma, in termini meno tecnici, un Vector3 è un "contenitore" per 3 valori float: x, y, z.

È possibile confrontare singoli valori, ad esempio confrontando i valori x di due Vector3, poiché sono solo numeri.

Tuttavia, un intero Vector3 non può essere confrontato con un altro Vector3 perché non esiste un singolo valore che può essere utilizzato per confrontare i due.


0

Basta aggiungere ciò che Gnemlock ha pubblicato, per quanto riguarda l'aggiunta di metodi di estensione alla classe Vector3. C'è un problema in Unity (e sono sicuro che gli altri motori di gioco) quando si utilizzano determinati operatori di confronto ( ==, <=e >=) tra due valori float, a causa di come il calcolo in virgola mobile viene gestito. Mathf.Approximatelydovrebbe invece essere utilizzato, quindi è possibile aggiungere i seguenti metodi di estensione per verificare se due vettori sono> = o <= tra loro:

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

Puoi certamente usarlo, se vuoi che entrambi i test ≤ & ≥ restituiscano true quando il valore scende un po '. Di solito, tuttavia, applichiamo il controllo approssimativamente uguale solo quando testiamo l'uguaglianza su un singolo valore particolare. "Allarga" il controllo da un singolo punto (facile da perdere) a un piccolo margine di errore su entrambi i lati. ≤ e ≥ hanno già un margine di errore incorporato: ogni superamento del limite inferiore o superiore viene acquisito rispettivamente, quindi sono già molto meno suscettibili di perdere un caso desiderato a causa di piccole deviazioni nel calcolo.
DMGregory

0

Vorrei proporre un modo diverso di interpretare questa domanda. Un modello di codice come questo:

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

sta fondamentalmente cercando di usare gli operatori >=/ <=come "il lato sinistro ha raggiunto o superato il lato destro?" test.

Usare >=/ <=per indicare "raggiunto o superato" ha senso in un senso unidimensionale, se la mia posizione è solo un float:

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

Ma nello spazio 3D non abbiamo una linea su cui misurare, per decidere quale lato è "alto / lontano" e quale lato è "basso / vicino". Ad esempio, potremmo provare a pattugliare tra i punti

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

Quindi ora ci aspettiamo patrolStart <= myPosition <= patrolEndsull'asse X, ma patrolEnd <= myPosition <= patrolStartsull'asse Z. Il nostro operatore "raggiunto o superato" è diverso da un asse all'altro, quindi non esiste più una chiara mappatura tra il nostro concetto di superamento di una soglia e un semplice controllo della disuguaglianza.

Ma c'è un modo in cui possiamo scegliere solo una riga nello spazio 3D e far sì che il nostro >=/ si <=comporti come il singolo caso float lungo questa linea che abbiamo scelto:

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

Come bonus, se normalizzi il vettore dell'asse prima di usarlo, allora tutti i prodotti punto rappresentano le distanze, quindi puoi misurare esattamente quanto sei lontano da entrambe le estremità, lungo l'asse della corsa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.