Warum kann ich den Operator '> =' nicht mit Vector3s verwenden?

9

Ich versuche, ein Rechteck zwischen zwei Positionen zu bewegen, die ich als _positionAund bezeichne _positionB. Beide sind vom Typ Vector3. Das Rechteck bewegt sich gut. Wenn es jedoch erreicht _positionBwird, bewegt es sich nicht in die entgegengesetzte Richtung, wie es sollte.

Ich ging zurück in den Code, um einen Blick darauf zu werfen. Ich kam zu dem Schluss, dass die ifAnweisungen im Code beim Bewegen des Objekts den Rahmen verfehlten, in dem die Position der Rechte gleich war _positionB. Ich habe beschlossen, den Code so zu ändern, dass die Richtung umgekehrt wird, wenn die Position der Rechtecke größer oder gleich ist _positionB . Mein Code ist nicht zu lang, daher werde ich ihn unten anzeigen:

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

Als ich es änderte, warnte es mich jedoch vor der folgenden Fehlermeldung:

Operator> = kann nicht auf Operanden vom Typ Vector3 und Vector3 angewendet werden.

Das verwirrt mich aus zwei Gründen; Erstens sind beide Werte vom gleichen Datentyp. Zweitens ==funktioniert die Verwendung des Vergleichsoperators ( ) für die beiden Werte fehlerfrei. Warum kann ich den Operator nicht >=mit Vector3s verwenden?

Javier Martinez
quelle
Randnotiz: Sie sollten vermeiden, 2 Boolswie _atPosAund zu verwenden _atPosB. Es ist unvermeidlich, dass Sie einen Fehler machen, wenn Sie beide synchron halten, und dies führt zu Fehlern. Es ist besser enum, alle Positionen (A, B, vielleicht andere in der Zukunft) zu enthalten und diese zu verwenden
Alexander - Reinstate Monica
5
Was soll ein >=bedeuten Vector3? Komponentenbezogen vergleichen? Das wäre keine Gesamtbestellung. Vector3.MoveTowards
Erwägen
4
Bedenken Sie Folgendes: var vec1 = new Vector3(1, 0, 0)und var vec2 = new Vector3(0, 1 ,0). Ist vec1 >= vec2wahr oder falsch?
Gronostaj

Antworten:

16

Um die Antwort zu vereinfachen, Vector3wird eine benutzerdefinierte Funktion structvom UnityEngineNamespace bereitgestellt . Wenn wir benutzerdefinierte classoder structTypen erstellen , müssen wir auch deren Operatoren definieren . Daher gibt es keine Standardlogik für den >=Bediener. Wie durch wies darauf hin , Evgeny Vasilyev , _rect_tfm.position == _positionBmacht Sinn, da wir direkt die überprüfen können Vector3.x, Vector3.yund Vector3.zWerte. _rect_tfm.position >= _positionBmacht nicht so viel Sinn, da a Vector3durch drei separate Werte dargestellt wird.

Wir könnten die Vector3Klasse überladen , um theoretisch die geeigneten Operatoren zu enthalten , aber das scheint ziemlich kompliziert zu sein. Stattdessen wäre es einfacher , die Vector3Klasse einfach mit einer geeigneten Methode zu erweitern . Davon abgesehen scheint es, dass Sie beabsichtigen, diese Logik für die Bewegung zu verwenden. Daher ist es möglicherweise viel einfacher, die Vector3.LerpMethode zu verwenden. Wenn ja, lesen Sie weiter unten.

Hinzufügen von Erweiterungsmethoden zu Vector3

Wie bereits erwähnt, die Anwendung <=oder >=auf ein Vector3oft unlogisch. Für die Bewegung möchten Sie wahrscheinlich weiter für die Vector3.LerpMethode lesen . Das heißt, Sie möchten die <= =>Arithmetik vielleicht aus anderen Gründen anwenden , also werde ich Ihnen eine einfache Alternative geben.

Anstatt die Logik von Vector3 <= Vector3oder anzuwenden Vector3 >= Vector3, schlage ich vor, die Vector3Klasse um Methoden für isGreaterOrEqual(Vector3 other)und zu erweitern isLesserOrEqual(Vector3). Wir können einem oder Erweiterungsmethoden hinzufügen, indem wir sie in einer Klasse deklarieren , die nicht erbt. Wir geben auch das Ziel oder als ersten Parameter mit dem Schlüsselwort an. Beachten Sie, dass ich in meinem Beispiel davon ausgehe, dass Sie sicherstellen möchten, dass alle drei Hauptwerte ( , und ) alle größer oder gleich oder kleiner oder gleich sind. Hier können Sie nach Bedarf Ihre eigene Logik bereitstellen.structclassstaticclassstructthisxyz

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

Wenn wir versuchen, diese Methoden aus der Vector3Klasse aufzurufen , localwird die Vector3Instanz dargestellt, von der aus wir die Methode aufrufen. Sie werden feststellen, dass die Methoden sind static; Erweiterungsmethoden müssen vorhanden sein static, Sie müssen sie jedoch weiterhin von einer Instanz aus aufrufen. Mit den oben genannten Erweiterungsmethoden können Sie sie jetzt direkt auf Ihre Vector3Typen anwenden .

Vector3 left;
Vector3 right;

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

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

Umzug Vector3mitVector3.Lerp

Durch Aufrufen der Vector3.LerpMethode können wir die genaue Position zwischen zwei Vector3Werten zu einem bestimmten Zeitpunkt bestimmen . Ein zusätzlicher Vorteil dieser Methode ist, dass das Vector3Ziel nicht überschritten wird . Vector3.Lerpnimmt drei Parameter; Die Startposition, die Endposition und die aktuelle Position werden als Wert zwischen 0 und 1 dargestellt. Sie gibt die resultierende Position als a aus Vector3, die wir direkt als aktuelle Position festlegen können.

Um Ihr Problem zu lösen, schlage ich vor Vector3.Lerp, zu einem zu wechseln targetPosition. Nach dem Aufruf der MoveMethode in jedem Updatekönnen wir überprüfen, ob wir das Ziel erreicht haben; Lerp.Vector3wird nicht überschießen, transform.position == targetPositionwird also zuverlässig. Wir können nun die Position überprüfen und ändern die targetPositionzu leftPositionoder rightPositiondie Bewegung umgekehrt, entsprechend.

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

Sie können dies in der folgenden Animation demonstrieren. Ich übersetze den blauen Würfel mit Vector3.LerpUnclamped, was uns ein ähnliches Ergebnis wie eine einfache ungeprüfte Übersetzung gibt. Ich übersetze den roten Würfel mit Vector3.Lerp. Wenn das Kontrollkästchen nicht aktiviert ist, gerät der blaue Würfel in Vergessenheit. während der rote Würfel genau dort anhält, wo ich es beabsichtige. Weitere Informationen zu dieser Art von Bewegung finden Sie in der Dokumentation zum Stapelüberlauf .

Wenn das Kontrollkästchen nicht aktiviert ist, gerät der blaue Würfel in Vergessenheit.  während der rote Würfel genau dort anhält, wo ich es beabsichtige.

Gnemlock
quelle
Wow, du bist wirklich die Extrameile gegangen, vielen Dank!
Javier Martinez
27

Das Definieren >=eines Vector3Typs macht keinen Sinn. Was bestimmt, ob ein Vektor größer als ein anderer ist? Ihre Größe oder ihre einzelnen x-, y-, z-Komponenten?

Ein Vektor ist eine Größe und eine Richtung. Was bestimmt also, welche Richtung größer ist?

Wenn Sie die Größen vergleichen müssen, können Sie verwenden sqrMagnitude.

In diesem Fall werden Vector3Überschreibungen vorgenommen, ==um einfach die verschiedenen Komponenten zu vergleichen und festzustellen, ob sie identisch sind. (innerhalb einer Schwelle)

Dies ist der gleiche Grund, warum das Multiplizieren von zwei Vektoren mit *nicht möglich ist. Es gibt einfach keinen mathematischen Weg, dies zu tun. Einige Leute verwenden *für Punktprodukt, aber das ist ein unklares API-Design.

Evgeny Vasilyev
quelle
Unity's Vector3ist a struct, daher ist der Absatz über den Referenzvergleich nicht ganz richtig.
31eee384
Dies könnte bedeuten, dass jede Position eines Vektors auf jeder Achse größer ist als die des anderen, ähnlich wie beim Vergleich von zwei ganzen Zahlen, nur als Gruppe. Es ist in der Anwendung etwas eingeschränkter als der Vergleich jeder Eigenschaft einzeln, könnte aber zumindest noch verwendet werden.
Pysis
Dies ist nicht Java. Referenzvergleich ist nicht wahr in Strukturen oder Klassen, in denen der Gleichheitsoperator definiert ist
Gustavo Maciel
Ich habe meine Antwort geändert, um diesen Teil zu entfernen. Allerdings war C # an einem Punkt Java. Soweit ich weiß, funktioniert der Kern der Klassen immer noch genauso und wenn == nicht überschrieben ist, verhält es sich genau so, wie es in Java wäre.
Evgeny Vasilyev
2

Dies ist eine alte Frage, aber um es weniger technisch auszudrücken: Ein Vector3 ist ein "Container" für 3 Float-Werte - x, y, z.

Sie können einzelne Werte vergleichen, z. B. die x-Werte von zwei Vector3s, da es sich nur um Zahlen handelt.

Ein ganzer Vector3 kann jedoch nicht mit einem anderen Vector3 verglichen werden, da es keinen einzigen Wert gibt, mit dem die beiden verglichen werden können.

Dez Boyle
quelle
0

Fügen Sie einfach das hinzu, was Gnemlock veröffentlicht hat, und fügen Sie der Vector3-Klasse Erweiterungsmethoden hinzu. Es gibt ein Problem in der Einheit (und ich bin sicher , dass andere Spiele - Engines) , wenn bestimmte Vergleichsoperatoren verwenden ( ==, <=und >=) zwischen zwei Float - Werte, durch, wie Fließkommaberechnung behandelt wird. Mathf.Approximatelysollte stattdessen verwendet werden, daher können die folgenden Erweiterungsmethoden hinzugefügt werden, um zu überprüfen, ob zwei Vektoren> = oder <= zueinander sind:

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
quelle
Sie können dies sicherlich verwenden, wenn beide ≤ & ≥ Tests true zurückgeben sollen, wenn der Wert ein wenig unterschreitet. Normalerweise wenden wir die ungefähr gleiche Prüfung jedoch nur an, wenn die Gleichheit auf einen einzelnen bestimmten Wert geprüft wird. Es "erweitert" die Prüfung von einem einzelnen Punkt (leicht zu übersehen) auf eine kleine Fehlerquote auf beiden Seiten. ≤ und ≥ haben bereits eine eingebaute Fehlerquote: Jedes Überschwingen zum unteren bzw. oberen Ende wird erfasst, sodass sie aufgrund kleiner Abweichungen in der Berechnung bereits viel weniger anfällig dafür sind, einen gewünschten Fall zu verpassen.
DMGregory
0

Ich möchte eine andere Art der Interpretation dieser Frage vorschlagen. Ein Codemuster wie dieses:

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

versucht im Grunde, die Operatoren >=/ <=als "hat die linke Seite die rechte Seite erreicht oder passiert ?" zu verwenden. Tests.

Die Verwendung von >=/ <=für "erreicht oder bestanden" ist in einem eindimensionalen Sinne sinnvoll, wenn meine Position nur ein Float ist:

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

Aber im 3D-Raum haben wir keine Linie, an der wir messen können, um zu entscheiden, welche Seite "hoch / fern" und welche Seite "niedrig / nah" ist. Zum Beispiel könnten wir versuchen, zwischen den Punkten zu patrouillieren

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

Nun erwarten wir also patrolStart <= myPosition <= patrolEndauf der X-Achse, aber patrolEnd <= myPosition <= patrolStartauf der Z-Achse. Unser Operator "erreicht oder bestanden" unterscheidet sich von einer Achse zur anderen, sodass es keine eindeutige Zuordnung mehr zwischen unserem Konzept des Überschreitens eines Schwellenwerts und einer einfachen Ungleichheitsprüfung gibt.

Es gibt jedoch eine Möglichkeit, nur eine Linie im 3D-Raum auszuwählen und unser >=/ <=Verhalten wie das einzelne Float-Gehäuse entlang dieser von uns gewählten Linie zu verhalten:

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

Wenn Sie den Achsenvektor vor der Verwendung normalisieren, stellen alle Punktprodukte als Bonus Entfernungen dar, sodass Sie genau messen können, wie weit Sie von beiden Enden entlang der Bewegungsachse entfernt sind.

DMGregory
quelle