Pourquoi ne puis-je pas utiliser l'opérateur '> =' avec Vector3s?

9

J'essaie d'obtenir un rectangle pour se déplacer entre deux positions que j'appelle _positionAet _positionB. Les deux sont de type Vector3. Le rectangle se déplace très bien. Cependant, lorsqu'il atteint, _positionBil ne se déplace pas dans la direction opposée, comme il se doit.

Je suis retourné dans le code pour y jeter un œil. J'en suis venu à la conclusion que lorsque l'objet se déplaçait, les ifinstructions du code manquaient le cadre dans lequel la position des rects était égale à _positionB. J'ai décidé de modifier le code pour inverser la direction si la position de rects est supérieure ou égale à _positionB . Mon code n'est pas trop long, je vais donc l'afficher ci-dessous:

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

Cependant, lorsque je l'ai modifié, il m'a averti du message d'erreur suivant:

L'opérateur> = ne peut pas être appliqué aux opérandes de type Vector3 et Vector3.

Cela m'embrouille pour deux raisons; tout d'abord, les deux valeurs sont du même type de données. Deuxièmement, l'utilisation de l'opérateur de comparaison ( ==) sur les deux valeurs fonctionne sans erreur. Pourquoi ne puis-je pas utiliser l'opérateur >=avec Vector3s?

Javier Martinez
la source
Remarque: vous devez éviter d'utiliser 2 Boolscomme _atPosAet _atPosB. Inévitablement, vous ferez une erreur en les synchronisant tous les deux, et cela entraînera des bugs. Il est préférable de créer un enumcontenant toutes les positions (A, B, peut-être d'autres à l'avenir), et d'utiliser cela
Alexander - Rétablir Monica
5
Que devrait >=signifier pour un Vector3? Comparer au niveau des composants? Ce ne serait pas une commande totale. Pensez à utiliserVector3.MoveTowards
rwols
4
Considérez ceci: var vec1 = new Vector3(1, 0, 0)et var vec2 = new Vector3(0, 1 ,0). Est-ce vec1 >= vec2vrai ou faux?
gronostaj

Réponses:

16

Pour simplifier la réponse, Vector3une coutume est structfournie par l' UnityEngineespace de noms. Lorsque nous créons des types classou des structtypes personnalisés , nous devons également définir ses opérateurs . En tant que tel, il n'y a pas de logique par défaut pour l' >=opérateur. Comme l' a souligné Evgeny Vassiliev , _rect_tfm.position == _positionBest logique, comme nous pouvons vérifier directement les Vector3.x, Vector3.yet les Vector3.zvaleurs. _rect_tfm.position >= _positionBn'a pas autant de sens, car a Vector3est représenté par trois valeurs distinctes.

Nous pourrions surcharger la Vector3classe pour contenir les opérateurs appropriés en théorie , mais cela semble plutôt compliqué. Au lieu de cela, il serait plus facile d' étendre simplement la Vector3classe avec une méthode appropriée . Cela étant dit, il semble que vous ayez l'intention d'utiliser cette logique pour le mouvement. En tant que tel, vous pourriez trouver beaucoup plus facile d'utiliser la Vector3.Lerpméthode; si oui, lisez plus loin ci-dessous.

Ajout de méthodes d'extension à Vector3

Comme mentionné précédemment, l'application <=ou >=à un Vector3est souvent illogique. Pour le mouvement, vous voudrez probablement lire plus loin pour la Vector3.Lerpméthode. Cela dit, vous voudrez peut-être appliquer l' <= =>arithmétique pour d'autres raisons, je vais donc vous donner une alternative facile.

Au lieu d'appliquer la logique de Vector3 <= Vector3ou Vector3 >= Vector3, je propose d'étendre la Vector3classe pour inclure les méthodes de isGreaterOrEqual(Vector3 other)et isLesserOrEqual(Vector3). Nous pouvons ajouter des méthodes d'extension à a structou classen les déclarant dans une staticclasse qui n'hérite pas. Nous incluons également la cible classou structcomme premier paramètre, en utilisant le thismot - clé. Notez que dans mon exemple, je suppose que vous voulez vous assurer que les trois valeurs principales ( x, yet z) sont toutes respectivement supérieures ou égales, ou inférieures ou égales. Vous pouvez fournir votre propre logique, ici, selon vos besoins.

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

Lorsque nous tentons d'appeler ces méthodes à partir de la Vector3classe, localreprésentera l' Vector3instance à partir de laquelle nous appelons la méthode. Vous noterez que les méthodes sont static; les méthodes d'extension doivent l' être static, mais vous devez toujours les appeler à partir d'une instance. Compte tenu des méthodes d'extension ci-dessus, vous pouvez désormais les appliquer directement à vos Vector3types.

Vector3 left;
Vector3 right;

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

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

Se déplacer Vector3avecVector3.Lerp

L'appel de la Vector3.Lerpméthode nous permet de déterminer la position exacte entre deux Vector3valeurs à un instant donné. Un autre avantage de cette méthode est que la Vector3ne sera pas dépasser son objectif . Vector3.Lerpprend trois paramètres; la position de départ, la position de fin et la position actuelle représentées comme une valeur comprise entre 0 et 1. Il affiche la position résultante sous la forme a Vector3, que nous pouvons définir directement comme position actuelle.

Pour résoudre votre problème, je propose d'utiliser Vector3.Lerppour passer à a targetPosition. Après avoir appelé la Moveméthode dans chacun Update, nous pouvons vérifier si nous avons atteint ladite cible; Lerp.Vector3sera pas remise des gaz, donc transform.position == targetPositiondevient fiable. Nous pouvons maintenant vérifier la position et modifier le targetPositionen leftPositionou rightPositionpour inverser le mouvement en conséquence.

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

Vous pouvez le voir dans l'animation suivante. Je traduis le cube bleu avec Vector3.LerpUnclamped, ce qui nous donne un résultat similaire à une simple traduction non contrôlée. Je traduis le cube rouge en utilisant Vector3.Lerp. Sans coche, le cube bleu s'éloigne dans l'oubli; tandis que le cube rouge s'arrête exactement là où je le souhaite. Vous pouvez en savoir plus sur ce type de mouvement dans la documentation Stack Overflow .

Sans coche, le cube bleu s'éloigne dans l'oubli;  tandis que le cube rouge s'arrête exactement là où je le souhaite.

Gnemlock
la source
Wow, vous avez vraiment fait un effort supplémentaire, merci beaucoup!
Javier Martinez
27

La définition >=d'un Vector3type n'a aucun sens. Qu'est-ce qui détermine si un vecteur est supérieur à un autre? Leur ampleur ou leurs composantes individuelles x, y, z?

Un vecteur est une grandeur et une direction. Alors, qu'est-ce qui détermine quelle direction est la plus grande?

Si vous devez comparer les grandeurs que vous pouvez utiliser sqrMagnitude.

Dans ce cas Vector3overrides ==de comparer simplement les différents composants pour voir si elles sont les mêmes. (dans un seuil)

C'est la même raison pour laquelle la multiplication de deux vecteurs à l'aide *n'est pas possible. Il n'y a tout simplement aucun moyen mathématique de le faire. Certaines personnes utilisent *pour le produit scalaire, mais il s'agit d'une conception d'API peu claire.

Evgeny Vasilyev
la source
Unity Vector3est un struct, donc le paragraphe sur la comparaison des références n'est pas tout à fait correct.
31eee384
Cela pourrait signifier que la position de chacun des vecteurs sur chaque axe est supérieure à celle de l'autre, similaire à la comparaison de 2 entiers, uniquement en tant que groupe. Son application est un peu plus limitée que la comparaison de chaque propriété individuellement, mais pourrait tout de même être utilisée au moins.
Pysis
Ce n'est pas Java. La comparaison des références n'est pas vraie dans les structures ou les classes où l'opérateur égal est défini
Gustavo Maciel
J'ai modifié ma réponse pour supprimer cette partie. Cependant C # était à un moment donné Java. Pour autant que je sache, le noyau des classes fonctionne toujours de la même manière et si == n'est pas écrasé, il se comporte exactement comme il le ferait en java.
Evgeny Vasilyev
2

C'est une vieille question mais pour mettre en termes moins techniques, un Vector3 est un "conteneur" pour 3 valeurs flottantes - x, y, z.

Vous pouvez comparer des valeurs individuelles, telles que la comparaison des valeurs x de deux Vector3, car ce ne sont que des nombres.

Cependant, un Vector3 entier ne peut pas être comparé à un autre Vector3 car il n'y a pas une seule valeur qui peut être utilisée pour comparer les deux.

Dez Boyle
la source
0

Il suffit d'ajouter à ce que Gnemlock a publié, concernant l'ajout de méthodes d'extension à la classe Vector3. Il y a un problème dans Unity (et je suis sûr que d'autres moteurs de jeu) lors de l'utilisation de certains opérateurs de comparaison ( ==, <=et >=) entre deux valeurs flottantes, en raison de la façon dont le calcul en virgule flottante est géré. Mathf.Approximatelydoit être utilisé à la place, ainsi les méthodes d'extension suivantes peuvent être ajoutées pour vérifier si deux vecteurs sont> = ou <= l'un par rapport à l'autre:

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
la source
Vous pouvez certainement l'utiliser, si vous voulez que les deux tests ≤ & ≥ retournent vrai lorsque la valeur est légèrement inférieure. Cependant, nous n'appliquons généralement la vérification à peu près égale que lors du test d'égalité à une seule valeur particulière. Il "élargit" le contrôle d'un seul point (facile à manquer) à une petite marge d'erreur de chaque côté. ≤ et ≥ ont déjà une marge d'erreur intégrée: tout dépassement respectivement vers le bas ou le haut de gamme est capturé, ils sont donc déjà beaucoup moins susceptibles de manquer un cas souhaité en raison de petites déviations dans le calcul.
DMGregory
0

Je voudrais proposer une manière différente d'interpréter cette question. Un modèle de code comme celui-ci:

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

essaie essentiellement d'utiliser les opérateurs >=/ <=car "le côté gauche a-t-il atteint ou dépassé le côté droit?" tests.

Utiliser >=/ <=pour signifier "atteint ou dépassé" est logique dans un sens unidimensionnel, si ma position n'est qu'un flottant:

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

Mais dans l'espace 3D, nous n'avons pas de ligne pour mesurer, pour décider quel côté est "haut / loin" et quel côté est "bas / proche". Par exemple, nous pourrions essayer de patrouiller entre les points

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

Alors maintenant, nous nous attendons patrolStart <= myPosition <= patrolEndsur l'axe X, mais patrolEnd <= myPosition <= patrolStartsur l'axe Z. Notre opérateur "atteint ou dépassé" est différent d'un axe à l'autre, il n'y a donc plus de correspondance claire entre notre concept de franchissement de seuil et un simple contrôle d'inégalité.

Mais, il y a un moyen de choisir une seule ligne dans l'espace 3D et de faire en sorte que notre >=/ <=se comporte comme le flotteur unique le long de cette ligne que nous avons choisi:

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

En prime, si vous normalisez le vecteur d'axe avant de l'utiliser, tous les produits scalaires représentent des distances, de sorte que vous pouvez mesurer exactement à quelle distance vous êtes de chaque extrémité, le long de l'axe de déplacement.

DMGregory
la source