9
\$\begingroup\$

I am trying to get a rectangle to move between two positions which I refer to as _positionA and _positionB. Both are of type Vector3. The rectangle moves just fine. However, when it reaches _positionB it does not move in the opposite direction, like it should.

I went back into the code to take a look. I came to the conclusion that as the object moved, the if statements in the code missed the frame in which the rects position was equal to _positionB. I decided to modify the code to reverse direction if the rects position is greater than or equal to _positionB. My code is not too long, so I will display it below:

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

When I changed it, however, it warned me of the following error message:

Operator >= cannot be applied to operands of type Vector3 and Vector3.

This confuses me for two reasons; first, both values are of the same data type. Second, using the comparison operator(==) on the two values works without error. Why can't I use the operator >= with Vector3s?

\$\endgroup\$
  • \$\begingroup\$ Side note: you should avoid using 2 Bools like _atPosA and _atPosB. Inevitably, you'll make a mistake keeping them both in sync, and it'll lead to bugs. It's better to make an enum containing all the positions (A, B, perhaps others in the future), and using that \$\endgroup\$ – Alexander May 1 '17 at 4:22
  • 5
    \$\begingroup\$ What should >= mean for a Vector3? Compare component-wise? That would not be a total ordering. Consider using Vector3.MoveTowards \$\endgroup\$ – rwols May 1 '17 at 14:17
  • 4
    \$\begingroup\$ Consider this: var vec1 = new Vector3(1, 0, 0) and var vec2 = new Vector3(0, 1 ,0). Is vec1 >= vec2 true or false? \$\endgroup\$ – gronostaj May 1 '17 at 15:53
16
\$\begingroup\$

To simplify the answer, Vector3 is a custom struct provided by the UnityEngine namespace. When we create custom class or struct types, we must also define its operators. As such, there is no default logic for the >= operator. As pointed out by Evgeny Vasilyev, _rect_tfm.position == _positionB makes sense, as we can directly check the Vector3.x, Vector3.y and Vector3.z values. _rect_tfm.position >= _positionB does not make as much sense, due to the fact that a Vector3 is represented by three separate values.

We could overload the Vector3 class to contain the suitable operators in theory, but that seems rather complicated. Instead, it would be easier to simply extend the Vector3 class with a suitable method. That being said, it seems that your intending to use this logic for movement. As such, you might find it much easier to use the Vector3.Lerp method; if so, read further below.

Adding extension methods to Vector3

As previously mentioned, applying <= or >= to a Vector3 is often illogical. For movement, you probably want to read further for the Vector3.Lerp method. That said, you might want to apply the <= => arithmetic for other reasons, so I will give you an easy alternate.

Instead of applying the logic of Vector3 <= Vector3 or Vector3 >= Vector3, I propose extending the Vector3 class to include methods for isGreaterOrEqual(Vector3 other) and isLesserOrEqual(Vector3). We can add extension methods to a struct or class by declaring them in a static class that does not inherit. We also include the target class or struct as the first parameter, using the this keyword. Note that in my example, I assume that you mean to ensure that all three main values (x, y and z) are all greater or equal, or lesser or equal, respectively. You can provide your own logic, here, as you require.

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

When we attempt to call these methods from the Vector3 class, local will represent the Vector3 instance we are calling the method from. You will note that the methods are static; extension methods must be static, but you still have to call them from an instance. Given the above extension methods, you can now apply them directly to your Vector3 types.

Vector3 left;
Vector3 right;

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

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

Moving Vector3 with Vector3.Lerp

Calling the Vector3.Lerp method allows us to determine the exact position between two Vector3 values at a given time. An added benefit of this method is that the Vector3 will not overshoot its target. Vector3.Lerp takes three parameters; the start position, the end position, and the current position represented as a value between 0 and 1. It outputs the resulting position as a Vector3, which we can directly set as the current position.

Solving your problem, I propose using Vector3.Lerp to move to a targetPosition. After calling the Move method in each Update, we can check if we have reached said target; Lerp.Vector3 will not overshoot, so transform.position == targetPosition becomes reliable. We can now check the position, and change the targetPosition to leftPosition or rightPosition to reverse the movement, accordingly.

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

You can see this demonstrated in the following animation. I translate the blue cube with Vector3.LerpUnclamped, which gives us a similar result to simple unchecked translation. I translate the red cube using Vector3.Lerp. Left unchecked, the blue cube moves off into oblivion; while the red cube stops exactly where I intend it to. You can read more about this type of movement in the Stack Overflow documentation.

Left unchecked, the blue cube moves off into oblivion; while the red cube stops exactly where I intend it to.

\$\endgroup\$
  • \$\begingroup\$ Wow, you really went the extra mile, thank you so much! \$\endgroup\$ – Javier Martinez May 2 '17 at 20:12
27
\$\begingroup\$

Defining >= for a Vector3 type makes no sense. What determines if one vector is greater than another? Their magnitude or their individual x, y, z components?

A vector is a magnitude & a direction. So what determines what direction is greater?

If you need to compare the magnitudes you can use sqrMagnitude.

In this case Vector3 overrides == to simply compare the different components to see if they are the same. (within a threshold)

This is the same reason multiplying two vectors using * is not possible. There is simply no mathematical way of doing it. Some people use * for dot product, but that is an unclear API design.

\$\endgroup\$
  • \$\begingroup\$ Unity's Vector3 is a struct, so the paragraph about reference comparison isn't quite right. \$\endgroup\$ – 31eee384 May 1 '17 at 21:41
  • \$\begingroup\$ It could mean each of one vector's position on each axis are greater than the other's, similar to comparing 2 integers, only as a group. It's a bit more limited in application versus comparing each property individually, but could still be used at least. \$\endgroup\$ – Pysis May 2 '17 at 17:42
  • \$\begingroup\$ This isn't Java. Reference comparison is not true in structs or classes where the equals operator is defined \$\endgroup\$ – Gustavo Maciel May 3 '17 at 14:13
  • \$\begingroup\$ I modified my answer to remove that part. However C# was at one point Java. As far as I know the core of classes still works the same and if == is not over written it behaves exactly how it would in java. \$\endgroup\$ – Evgeny Vasilyev May 3 '17 at 14:20
1
\$\begingroup\$

This is an old question but to put in to less technical terms, a Vector3 is a "container" for 3 float values - x,y,z.

You can compare individual values, such as comparing the x values of two Vector3s, because they are just numbers.

However, an entire Vector3 cannot be compared to another Vector3 because there isn't a single value that can be used to compare the two.

New contributor
Dez Boyle is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
0
\$\begingroup\$

Just adding onto what Gnemlock posted, regarding adding extension methods to the Vector3 class. There's an issue in Unity (and I'm sure other game engines) when using certain comparison operators (==,<= and >=) between two float values, due to how floating point calculation is handled. Mathf.Approximately should be used instead, thus the following extension methods can be added for checking if two vectors are >= or <= to each other:

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;
    }
}
\$\endgroup\$
  • \$\begingroup\$ You can certainly use this, if you want both ≤ & ≥ tests to return true when the value undershoots a little. Usually though, we apply the approximately equal check only when testing for equality to a single particular value. It "widens" the check from a single point (easy to miss) to a small margin of error on either side. ≤ and ≥ already have a margin of error built-in: any overshoot to the low or high end respectively gets captured, so they're already much less susceptible to missing a desired case due to small deviations in the calculation. \$\endgroup\$ – DMGregory Feb 9 '18 at 0:13
0
\$\begingroup\$

I'd like to propose a different way of interpreting this question. A code pattern like this:

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

is basically trying to use the >= / <= operators as "has the left side reached or passed the right side?" tests.

Using >= / <= to mean "reached or passed" makes sense in a one-dimensional sense, if my position is just a float:

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

But in 3D space we don't have any one line to measure along, to decide which side is "high/far" and which side is "low/near." For example, we could be trying to patrol between the points

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

So now we expect patrolStart <= myPosition <= patrolEnd on the X axis, but patrolEnd <= myPosition <= patrolStart on the Z axis. Our "reached or passed" operator is different from one axis to another, so there's no longer a clear mapping between our concept of passing a threshold and a simple inequality check.

But, there's a way we can pick out just one line in 3D space, and make our >= / <= behave like the single float case along this line we chose:

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

As a bonus, if you normalize the axis vector before using it, then all the dot products represent distances, so you can measure exactly how far you are from either end, along the axis of travel.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.