Unity 3D 如何将 Vector3 投影到平面上或获取特定平面内两个 Vector3 之间的角度?

4

我正在尝试编写第三人称角色的程序,使得当按下方向键(例如D键)时,如果角色当前面对的方向与相机相同,则播放右侧四分之一转身动画,如果它面对相机,则播放左侧四分之一转身动画,类似于GTA V。但是我在获取摄像机和玩家在Y轴平面上的夹角方面遇到了问题。我在玩家控制脚本中尝试了以下内容:

void Right()
    {
        Vector3 pVec = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
        Vector3 cVec = Vector3.ProjectOnPlane(mainCam.transform.forward, Vector3.up);
        print(cVec);
        float angle = Vector3.Angle(pVec, cVec);
        print(angle);
        if(angle >= 345 && angle <= 15)
        {
            animator.Play("StandQuarterTurnRight");
        }
        else if(angle >= 255 && angle <= 285)
        {
            animator.Play("StandHalfTurnRight");
        }
        else if(angle >= 165 && angle <= 195)
        {
            animator.Play("StandQuarterTurnLeft");
        }
        else if(angle >=75 && angle <= 105)
        {
            float forw = Input.GetAxis("Horizontal");
            if (forw > 0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = 0.5f;
            else if (forw < -0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = -0.5f;
            animator.SetFloat("Speed", forw);
        }
    }

但是它并没有起作用,我得到了错误的角度。当角色面对相机前方向左或向右时,我得到了90的角度,而其他情况下则得到了错误的角度,并且根本没有给出180+,我做错了什么?是否有更好的方法来实现我想要的效果?


尝试使用三角函数或四元数 https://answers.unity.com/questions/54495/how-do-i-convert-angle-to-vector3.html - jiveturkey
下投票是怎么回事?我的问题有什么问题吗? - Shantanu Shinde
3个回答

1
我会将相机的视角转换为您角色的坐标系。这样,您就可以很容易地看到他应该朝哪个方向转动或移动。
假设您的相机旋转角度为mainCam.transform.rotation,您可以使用以下代码:
float target_angle = 90.0f; // assuming want to turn 'right'.

// direction of camera, in worldspace.
Vector3 cam_dir = mainCam.transform.forward;
// now transform to a direction vector in character's local space.
cam_dir = transform.InverseTransformDirection(cam_dir);
// ignore y part, take X/Z to get the angle.
// 0 degrees is forward, 90 deg is toward positive X, so normally right.
float cam_angle = Mathf.Atan2(cam_dir.x,cam_dir.z)*Mathf.Rad2Deg;

// angle we need to turn
float turn_angle = target_angle - cam_angle;
// [.....] do it now.

如果想从某个角色的本地视角考虑问题,使用InverseTransformXxx()函数通常非常有帮助。您还可以通过使用char.transform.InverseTransformPoint(mainCam.transform.position)将相机位置转换为角色空间,并将其用作参考。


问题是我的相机前方不在Y玩家上,相机略高于玩家并向下倾斜,因此测量的角度是三维的。我希望角度仅在Y平面上。 - Shantanu Shinde
这就是它的作用。它将相机视图转换为玩家的本地空间,其中仍然包括Y轴上的一些负分量,以及X和Z轴。请参见注释“忽略y部分...”。在那里删除Y会将投影相机方向向量压平到Y平面上。 - Blindleistung
我喜欢这种方法。@ShantanuShinde 请参见 https://meta.stackexchange.com/a/5235/405359 - Ruzihm

0

这是一个小修复,可以调整您的代码,使其正常工作,而不需要更改其他任何内容:

void Right()
{
    Vector3 pVec = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
    Vector3 cVec = Vector3.ProjectOnPlane(mainCam.transform.forward, Vector3.up);
    print(cVec);

    float angleA = Vector3.Angle(pVec, cVec); //Get the angle between the 2 vectors, projected on Y-plane
    float perspectiveAngle = Vector3.Angle(transform.right, cVec); //Get the angle between camera and player's right vector
    float angle = angleA; //In case angle is below 180, angle is AngleA

    if (perspectiveAngle > 90f) //If angle between player's right vector and camera is > 90, then we need to adjust the angle, as it is equal to or greater than 180
        angle = 360f - angleA;

    print(angle);
    if (angle >= 345 && angle <= 15)
    {
        animator.Play("StandQuarterTurnRight");
    }
    else if (angle >= 255 && angle <= 285)
    {
        animator.Play("StandHalfTurnRight");
    }
    else if (angle >= 165 && angle <= 195)
    {
        animator.Play("StandQuarterTurnLeft");
    }
    else if (angle >= 75 && angle <= 105)
    {
        float forw = Input.GetAxis("Horizontal");
        if (forw > 0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = 0.5f;
        else if (forw < -0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = -0.5f;
        animator.SetFloat("Speed", forw);
    }
}

请看上面的注释,它们描述了它是如何工作的。希望这有所帮助。

0
我建议尽量避免在此处使用角度。相反,您可以使用摄像机的右方向和玩家本地方向之间的点积来确定哪个本地方向与摄像机的右方向最吻合。评论中有解释。
void Right()
{
    float dotThreshold = Mathf.Sin(Mathf.PI * 0.25f); 

    // Take the dot products between the camera's right and  
    // each direction from the player. 
    // Either exactly one dot product will exceed this threshold 
    // (sin 45 degrees) or two will equal it.
    // Either way, when we see one dot product >= the threshold, 
    // we know what direction we should face.

    Vector3 camRight = mainCam.transform.right;

    if(Vector3.Dot(camRight, transform.right) >= dotThreshold) 
    {
        // camera's right ~ player's right
        animator.Play("StandQuarterTurnRight");
    }
    else if(Vector3.Dot(camRight, -transform.forward) >= dotThreshold) 
    {
        // camera's right ~ player's back
        animator.Play("StandHalfTurnRight");
    }
    else if(Vector3.Dot(camRight, -transform.right) >= dotThreshold)
    {
        // camera's right ~ player's left
        animator.Play("StandQuarterTurnLeft");
    }
    else
    {
        // camera's right ~ player's forward
        float forw = Input.GetAxis("Horizontal");
        if (forw > 0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = 0.5f;
        else if (forw < -0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = -0.5f;
        animator.SetFloat("Speed", forw);
    }
}

如果您不能假设玩家和相机具有相同的y轴方向,则必须像问题中所做的那样投影到相同的平面中:
void Right()
{
    float dotThreshold = Mathf.Sin(Mathf.PI * 0.25f); 

    // Take the dot products between the camera's right and  
    // each direction from the player. 
    // Either exactly one dot product will exceed this threshold 
    // (sin 45 degrees) or two will equal it.
    // Either way, when we see one dot product >= the threshold, 
    // we know what direction we should face.

    Vector3 camRight = Vector3.ProjectOnPlane(mainCam.transform.right, Vector3.up);
    Vector3 playerRight = Vector3.ProjectOnPlane(transform.right, Vector3.up);
    Vector3 playerForward = Vector3.ProjectOnPlane(transform.forward, Vector3.up);

    if(Vector3.Dot(camRight, playerRight) >= dotThreshold) 
    {
        // camera's right ~ player's right
        animator.Play("StandQuarterTurnRight");
    }
    else if(Vector3.Dot(camRight, -playerForward) >= dotThreshold) 
    {
        // camera's right ~ player's back
        animator.Play("StandHalfTurnRight");
    }
    else if(Vector3.Dot(camRight, -playerRight) >= dotThreshold)
    {
        // camera's right ~ player's left
        animator.Play("StandQuarterTurnLeft");
    }
    else
    {
        // camera's right ~ player's forward
        float forw = Input.GetAxis("Horizontal");
        if (forw > 0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = 0.5f;
        else if (forw < -0.5f && !Input.GetKey(KeyCode.LeftShift)) forw = -0.5f;
        animator.SetFloat("Speed", forw);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接