Dlaczego nie mogę używać operatora '> =' z Vector3s?

9

Próbuję uzyskać prostokąt, aby poruszać się między dwiema pozycjami, które nazywam _positionAi _positionB. Oba są tego typu Vector3. Prostokąt porusza się dobrze. Kiedy jednak się osiągnie _positionB, nie porusza się w przeciwnym kierunku, jak powinien.

Wróciłem do kodu, żeby się przyjrzeć. Doszedłem do wniosku, że gdy obiekt się porusza, ifinstrukcje w kodzie nie mieszczą się w ramce, w której pozycja rektyfikacji była równa _positionB. Postanowiłem zmodyfikować kod, aby odwrócić kierunek, jeśli pozycja prostokątów jest większa lub równa _positionB . Mój kod nie jest zbyt długi, więc wyświetlę go poniżej:

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

Kiedy go jednak zmieniłem, ostrzegł mnie przed następującym komunikatem o błędzie:

Operator> = nie można zastosować do argumentów typu Vector3 i Vector3.

Myli mnie to z dwóch powodów; po pierwsze, obie wartości są tego samego typu danych. Po drugie, użycie operatora porównania ( ==) na dwóch wartościach działa bez błędów. Dlaczego nie mogę używać operatora >=z Vector3s?

Javier Martinez
źródło
Uwaga dodatkowa: należy unikać używania 2 Boolstakich jak _atPosAi _atPosB. Nieuchronnie popełnisz błąd, utrzymując je obie w synchronizacji, co doprowadzi do błędów. Lepiej zrobić enumzawierający wszystkie pozycje (A, B, być może inne w przyszłości) i używając tego
Alexander - Przywróć Monikę
5
Co powinno >=znaczyć dla Vector3? Porównać pod względem komponentów? To nie byłoby całkowite zamówienie. Rozważ użycieVector3.MoveTowards
rwols
4
Rozważ to: var vec1 = new Vector3(1, 0, 0)i var vec2 = new Vector3(0, 1 ,0). Czy vec1 >= vec2prawda czy fałsz?
gronostaj

Odpowiedzi:

16

Aby uprościć odpowiedź, Vector3jest to zwyczaj structdostarczony przez UnityEngineprzestrzeń nazw. Kiedy tworzymy niestandardowe classlub structtypy, musimy również zdefiniować ich operatorów . W związku z tym >=operator nie ma domyślnej logiki . Jak podkreślił Evgeny Wasiliew , _rect_tfm.position == _positionBma sens, jak możemy bezpośrednio sprawdzić Vector3.x, Vector3.yi Vector3.zwartości. _rect_tfm.position >= _positionBnie ma większego sensu, ponieważ a Vector3jest reprezentowane przez trzy oddzielne wartości.

Moglibyśmy przeciążyć Vector3klasę, aby zawierała odpowiednie operatory w teorii , ale wydaje się to dość skomplikowane. Zamiast tego łatwiej byłoby po prostu przedłużyć o Vector3klasę z odpowiednim sposobem . Biorąc to pod uwagę, wydaje się, że zamierzasz użyć tej logiki do poruszania się. W związku z tym korzystanie z tej Vector3.Lerpmetody może być znacznie łatwiejsze ; jeśli tak, czytaj dalej poniżej.

Dodanie metod rozszerzenia do Vector3

Jak wcześniej wspomniano, zastosowanie <=lub >=do Vector3jest często nielogiczne. Jeśli chodzi o ruch, prawdopodobnie chcesz przeczytać więcej na temat tej Vector3.Lerpmetody. To powiedziawszy, możesz chcieć zastosować <= =>arytmetykę z innych powodów, więc dam ci łatwą alternatywę.

Zamiast stosowania logiki Vector3 <= Vector3lub Vector3 >= Vector3proponuję rozszerzenie Vector3klasy o metody dla isGreaterOrEqual(Vector3 other)i isLesserOrEqual(Vector3). Możemy dodać metody rozszerzenia do a structlub classdeklarując je w staticklasie, która nie dziedziczy. Uwzględniamy również cel classlub structjako pierwszy parametr za pomocą thissłowa kluczowego. Należy zauważyć, że w moim przykładzie zakładamy, że masz na myśli, aby upewnić się, że wszystkie trzy główne wartości ( x, yi z) są wszystkie większe lub równe lub mniejsze lub równe, odpowiednio. Tutaj możesz podać własną logikę, zgodnie z wymaganiami.

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

Kiedy spróbujemy wywołać te metody z Vector3klasy, localbędzie reprezentować Vector3instancję, z której wywołujemy metodę. Zauważysz, że metody są static; metody rozszerzenia muszą być static, ale nadal musisz wywoływać je z instancji. Biorąc pod uwagę powyższe metody rozszerzenia, możesz teraz zastosować je bezpośrednio do swoich Vector3typów.

Vector3 left;
Vector3 right;

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

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

Przeprowadzka Vector3zVector3.Lerp

Nazywając ten Vector3.Lerpsposób pozwala nam określić dokładną pozycję między dwoma Vector3wartościami w danym czasie. Dodatkową zaletą tej metody jest to, że nie wykroczy swój cel . przyjmuje trzy parametry; pozycja początkowa, końcowa i bieżąca pozycja reprezentowane jako wartość od 0 do 1. Wyprowadza wynikową pozycję jako a , którą możemy bezpośrednio ustawić jako bieżącą pozycję.Vector3Vector3.LerpVector3

Rozwiązując problem, proponuję użyć, Vector3.Lerpaby przejść do targetPosition. Po wywołaniu Movemetody w każdym z nich Updatemożemy sprawdzić, czy osiągnęliśmy wspomniany cel; Lerp.Vector3będzie nie przeregulowanie, więc transform.position == targetPositionstaje się wiarygodne. Możemy teraz sprawdzić pozycję i odpowiednio zmienić ruch targetPositionna leftPositionlub rightPositionodwrócić.

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

Można to zobaczyć na poniższej animacji. Tłumaczę niebieski sześcian za pomocą Vector3.LerpUnclamped, co daje nam podobny wynik do prostego niesprawdzonego tłumaczenia. Tłumaczę czerwony sześcian za pomocą Vector3.Lerp. Pozostawiony bez zaznaczenia, niebieski sześcian rusza w zapomnienie; podczas gdy czerwony sześcian zatrzymuje się dokładnie tam, gdzie zamierzam. Możesz przeczytać więcej o tym rodzaju ruchu w dokumentacji przepełnienia stosu .

Pozostawiony bez zaznaczenia, niebieski sześcian rusza w zapomnienie;  podczas gdy czerwony sześcian zatrzymuje się dokładnie tam, gdzie zamierzam.

Gnemlock
źródło
Wow, naprawdę poszedłeś o krok dalej, dziękuję bardzo!
Javier Martinez
27

Definiowanie >=dla Vector3typu nie ma sensu. Co decyduje o tym, czy jeden wektor jest większy od drugiego? Ich wielkość czy poszczególne składniki x, y, z?

Wektor jest wielkością i kierunkiem. Co decyduje o tym, który kierunek jest większy?

Jeśli musisz porównać wielkości, których możesz użyć sqrMagnitude.

W takim przypadku Vector3zastępuje ==po prostu porównanie różnych komponentów, aby sprawdzić, czy są one takie same. (w ramach progu)

Z tego samego powodu pomnożenie dwóch wektorów *nie jest możliwe. Po prostu nie ma matematycznego sposobu na zrobienie tego. Niektóre osoby używają *produktu kropkowego, ale jest to niejasny projekt interfejsu API.

Jewgienij Wasiljew
źródło
Unity's Vector3jest struct, więc akapit dotyczący porównania odniesień nie jest całkiem poprawny.
31eee384
Może to oznaczać, że pozycja każdego wektora na każdej osi jest większa niż drugiej, podobnie jak w przypadku porównywania 2 liczb całkowitych, tylko jako grupy. Jest nieco bardziej ograniczony w aplikacji w porównaniu z porównywaniem każdej właściwości z osobna, ale nadal może być używany przynajmniej.
Pysis
To nie jest Java. Porównanie referencji nie jest prawdziwe w strukturach lub klasach, w których zdefiniowano operator równości
Gustavo Maciel
Zmodyfikowałem swoją odpowiedź, aby usunąć tę część. Jednak C # był w pewnym momencie Java. O ile mi wiadomo, rdzeń klas nadal działa tak samo, a jeśli == nie jest już napisany, zachowuje się dokładnie tak, jak w Javie.
Jewgienij Wasiljew
2

To stare pytanie, ale mówiąc inaczej, Vector3 to „pojemnik” na 3 wartości zmiennoprzecinkowe - x, y, z.

Możesz porównać poszczególne wartości, takie jak porównanie wartości x dwóch Vector3, ponieważ są to tylko liczby.

Jednak całego Vector3 nie można porównać do innego Vector3, ponieważ nie ma jednej wartości, której można by porównać.

Dez Boyle
źródło
0

Dodanie do tego, co opublikował Gnemlock , dotyczące dodawania metod rozszerzenia do klasy Vector3. Jest to problem w Jedności (i jestem pewien, inne silniki gry) podczas korzystania z niektórych operatorów porównania ( ==, <=i >=) dwóch liczb rzeczywistych, ze względu na sposób kalkulacji pływający punkt jest obsługiwane. Mathf.Approximatelyzamiast tego należy użyć następujących metod rozszerzenia, aby sprawdzić, czy dwa wektory są względem siebie> = lub <=:

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
źródło
Z pewnością możesz tego użyć, jeśli chcesz, aby oba testy ≤ & ≥ zwracały wartość true, gdy wartość nieco się obniży. Zazwyczaj jednak sprawdzamy w przybliżeniu równość tylko podczas testowania równości do pojedynczej konkretnej wartości. „Rozszerza” czek z jednego punktu (łatwo go przeoczyć) do małego marginesu błędu po obu stronach. ≤ i ≥ mają już wbudowany margines błędu: każde przekroczenie odpowiednio dolnej lub górnej granicy zostaje przechwycone, więc są już znacznie mniej podatne na pominięcie pożądanego przypadku z powodu małych odchyleń w obliczeniach.
DMGregory
0

Chciałbym zaproponować inny sposób interpretacji tego pytania. Wzorzec kodu taki jak ten:

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

w zasadzie próbuje użyć operatorów >=/ <=, ponieważ „czy lewa strona osiągnęła lub minęła prawą?” testy.

Użycie >=/ <=do oznaczenia „osiągnięto lub przekazano” ma sens w sensie jednowymiarowym, jeśli moja pozycja jest zmienna:

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

Ale w przestrzeni 3D nie mamy żadnej linii do zmierzenia wzdłuż, aby zdecydować, która strona jest „wysoka / daleka”, a która strona „niska / bliska”. Na przykład moglibyśmy próbować patrolować między punktami

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

Więc teraz oczekujemy patrolStart <= myPosition <= patrolEndna osi X, ale patrolEnd <= myPosition <= patrolStartna osi Z. Nasz operator „osiągnięty lub przekazany” różni się od jednej osi do drugiej, więc nie ma już jasnego mapowania między naszą koncepcją przekroczenia progu a prostą kontrolą nierówności.

Istnieje jednak sposób, w jaki możemy wybrać tylko jedną linię w przestrzeni 3D i sprawić, aby nasz >=/ <=zachowywał się jak pojedynczy przypadek zmiennoprzecinkowy wzdłuż tej linii, którą wybraliśmy:

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

Jako bonus, jeśli znormalizujesz wektor osi przed jego użyciem, wówczas wszystkie produkty kropkowe reprezentują odległości, dzięki czemu możesz dokładnie zmierzyć, jak daleko jesteś od dowolnego końca, wzdłuż osi podróży.

DMGregory
źródło