为什么在Vector3s中不能使用运算符'> ='?


9

我正在尝试获得一个矩形,以便在我称为_positionA和的两个位置之间移动_positionB。两者都是类型Vector3。矩形移动得很好。但是,到达时_positionB它不会像应该的那样朝相反的方向移动。

我回到代码中看一看。我得出的结论是,随着对象的移动,if代码中的语句错过了rects位置等于的框架_positionB。如果rects的位置大于或等于, 我决定将代码修改为反向_positionB。我的代码不太长,因此我将在下面显示它:

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

但是,当我更改它时,它警告我以下错误消息:

运算符> =不能应用于Vector3和Vector3类型的操作数。

这使我感到困惑,原因有两个:首先,两个值的数据类型相同。其次,==对两个值使用比较运算符()可以正常工作。为什么不能将运算符>=Vector3s一起使用?


旁注:您应避免使用2 Bools_atPosA_atPosB。不可避免的是,您将使两者保持同步会出错,并且会导致错误。最好设置一个enum包含所有职位(A,B,将来可能还会有其他职位)的职位,并使用该职位
亚历山大-恢复莫妮卡

5
>=意味着什么Vector3?比较组件?那将不是全部订购。考虑使用Vector3.MoveTowards
rwols

4
考虑一下:var vec1 = new Vector3(1, 0, 0)var vec2 = new Vector3(0, 1 ,0)。是vec1 >= vec2真的还是假的?
gronostaj'5

Answers:


16

为了简化答案,Vector3是名称空间struct提供的自定义UnityEngine。创建自定义classstruct类型时,必须定义其操作符。因此,>=操作员没有默认逻辑。正如指出的叶夫根尼·瓦西里耶夫_rect_tfm.position == _positionB是有道理的,因为我们可以直接检查Vector3.xVector3.yVector3.z价值观。_rect_tfm.position >= _positionB由于a Vector3由三个单独的值表示,因此没有太大的意义。

我们可以在理论上重载Vector3该类以包含合适的运算符,但这似乎相当复杂。相反,它会更容易简单地延长了用合适的类方法。话虽如此,看来您打算使用此逻辑进行移动。因此,您可能会发现使用该方法要容易得多。如果是这样,请在下面进一步阅读。Vector3Vector3.Lerp

将扩展方法添加到 Vector3

如前所述,将<=>=应用于a Vector3通常是不合逻辑的。对于移动,您可能想进一步阅读该Vector3.Lerp方法。就是说,您可能<= =>出于其他原因要应用该算法,因此我将为您提供一个简单的替代方法。

我建议不扩展应用Vector3 <= Vector3or 的逻辑Vector3 >= Vector3,而建议扩展Vector3该类以包含isGreaterOrEqual(Vector3 other)and的方法isLesserOrEqual(Vector3)。我们可以向或通过在不继承的类中声明扩展方法添加扩展方法。我们还使用关键字将目标或作为第一个参数包括在内。请注意,在我的例子中,我假设你的意思是,以确保所有三个主要的值(,和)是所有大于或等于,或更少或相等,分别。您可以根据需要在此处提供自己的逻辑。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;
        }
    }
}

当我们尝试从Vector3类中调用这些方法时,local将代表Vector3我们从中调用该方法的实例。您会注意到这些方法是static;扩展方法必须static,但是您仍然必须从实例中调用它们。给定以上扩展方法,您现在可以将它们直接应用于您的Vector3类型。

Vector3 left;
Vector3 right;

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

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

移动Vector3Vector3.Lerp

调用Vector3.Lerp方法可使我们确定Vector3给定时间两个值之间的确切位置。这种方法的好处是,Vector3不会过冲其目标Vector3.Lerp具有三个参数;起始位置,结束位置和当前位置,以0到1之间的值表示。它将输出的结果输出为a Vector3,我们可以将其直接设置为当前位置。

解决您的问题后,我建议使用Vector3.Lerp移至targetPositionMove在每个方法中调用方法之后Update,我们可以检查是否达到了上述目标;Lerp.Vector3不会过冲,所以transform.position == targetPosition变得可靠。现在,我们可以检查位置,并相应地将其更改targetPositionleftPositionrightPosition反转。

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

您可以在以下动画中看到这一点。我使用来翻译蓝色立方体Vector3.LerpUnclamped,这与简单的未经检查的翻译结果相似。我使用翻译红色立方体Vector3.Lerp。如果不加检查,蓝色立方体将消失。而红色立方体恰好停在我想要的位置。您可以在Stack Overflow文档中阅读有关此类运动的更多信息。

如果不加检查,蓝色立方体将消失。 而红色立方体恰好停在我想要的位置。


哇,您真的付出了更多,非常感谢!
哈维尔·马丁内斯

27

定义>=Vector3类型是没有意义的。是什么决定一个向量是否大于另一个向量?它们的大小或它们各自的x,y,z分量?

向量是大小和方向。那么,什么决定更大的方向呢?

如果需要比较幅度,可以使用sqrMagnitude

在这种情况下,Vector3重写==仅用于比较不同的组件以查看它们是否相同。(在阈值内)

这是*不可能将两个向量相乘的相同原因。根本没有数学方法可以做到这一点。有些人使用*点产品,但这是一个不清楚的API设计。


Unity Vector3是的struct,因此有关引用比较的段落不太正确。
31eee384年

可能意味着一个向量在每个轴上的位置都大于另一个在轴上的位置,类似于将两个整数进行比较,仅作为一个组。与单独比较每个属性相比,它在应用程序上有更多限制,但至少仍可以使用。
Pysis

这不是Java。在定义了等于运算符的结构或类中,引用比较是不正确的
Gustavo Maciel

我修改了答案以删除该部分。但是C#曾经是Java。据我所知,类的核心仍然可以正常工作,并且如果==没有被覆盖,它的行为与Java完全相同。
Evgeny Vasilyev'5

2

这是一个古老的问题,但用较少的技术术语来说,Vector3是3个浮点值-x,y,z的“容器”。

您可以比较各个值,例如比较两个Vector3的x值,因为它们只是数字。

但是,不能将整个Vector3与另一个Vector3进行比较,因为没有可用于比较两者的单个值。


0

只需添加有关Gnemlock发布的内容,有关向Vector3类添加扩展方法。当使用某些比较运算(有没有在统一的问题(我敢肯定,其他游戏引擎)==<=>=)两个浮点值之间,由于浮点运算的处理方式。Mathf.Approximately应该改用,因此可以添加以下扩展方法来检查两个向量是否彼此> =或<=:

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

如果要让≤&≥测试在值略低于一点时都返回true,则可以肯定使用此方法。通常,尽管如此,我们仅在测试是否等于单个特定值时才应用近似相等的检查。它将检查从单个点(容易遗漏)“扩大”到两侧的一小部分误差。≤和≥已内置误差范围:分别捕获到低端或高端的任何过冲,因此由于计算中的微小偏差,它们已经不太容易错过所需的情况。
DMGregory

0

我想提出一种不同的方式来解释这个问题。这样的代码模式:

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

基本上是试图使用>=/ <=运算符,因为“左侧到达还是经过右侧?” 测试。

如果我的位置只是浮点数,则使用>=/ <=表示“已到达或已通过”在一个维度上是有意义的:

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

但是在3D空间中,我们没有一条线可以测量,从而确定哪一侧是“高/远”,哪一侧是“低/近”。例如,我们可能试图在两点之间巡逻

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

所以现在我们期望patrolStart <= myPosition <= patrolEnd在X轴上,但patrolEnd <= myPosition <= patrolStart在Z轴上。我们的“到达或通过”运算符从一个轴到另一个轴是不同的,因此在通过阈值的概念和简单的不平等检查之间不再有明确的映射。

但是,有一种方法我们可以只选择3D空间中的一条线,并使我们的>=/ <=行为就像我们选择的这条线的单个float情况:

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

另外,如果在使用轴矢量之前对其进行了归一化,那么所有点积都代表距离,因此您可以精确地沿行进轴测量距两端的距离。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.