让玩家自由旋转一个GameObject

Let the players freely rotate a GameObject

我正在尝试找到一种方法,让玩家可以使用鼠标自由拖动和旋转对象 (click to view scene)。 如您所见,移动对象的脚本工作得很好,但当您想要旋转它时,问题就来了。事实上,它在您第一次尝试旋转对象时起作用。如果您松开鼠标并重试,该对象会在再次被鼠标旋转之前回到其初始旋转。我不想要那个。我希望它从原来的位置继续旋转。到目前为止,这是我的代码:

private void OnMouseDown()
    {
        worldPositionA = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    }

void Update()
    {
        worldPositionB = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    }

private void OnMouseDrag()
    {
        Vector3 relativePositionA = worldPositionA - parent.gameObject.transform.position;
        Vector3 relativePositionB = worldPositionB - parent.gameObject.transform.position;

        float angleBetween = Vector2.SignedAngle(relativePositionB, relativePositionA);

        angleBetween += parent.transform.rotation.z;

        parent.transform.rotation = Quaternion.AngleAxis(angleBetween, Vector3.back);
    }

worldPositionA是鼠标开始拖动时的世界位置,worldPositionB是更新后的鼠标位置。 relativePosition(A 和 B)相同,但相对于对象的中心。 我知道它们是正确的,因为我有一个指向它们的光线投射,并且 angleBetween 也是正确的,因为我正在控制台中打印它。 怎么了?有什么我没有考虑到的吗?

不确定是不是这个原因,但是角度计算取决于旋转,每帧也会改变。我建议将初始旋转保存在 OnMouseDown 中,这样角度计算仅取决于初始条件(即旋转和鼠标位置)和当前鼠标位置,而不取决于任何中间结果。这也可能有助于避免数值问题。

我不是特别熟悉 unity,但是有没有理由将鼠标位置保存在 worldPositionB 中而不是直接在 OnMouseDrag 中计算它?

您的问题很可能与线路有关

angleBetween += parent.transform.rotation.z;

rotation 是一个 Quaternion。您应该永远不要 直接设置和读取各个组件,除非您非常清楚自己在做什么。 Quaternion 不是 3 个而是 4 个分量 xyzw,它们在 -11 之间移动,这很可能不是您期望的值。

因为一开始 worldPositionA = worldPositionB 第一个角度也总是在 -11 之间所以看起来对象被重置为原始旋转。

稍后您不会注意到这个错误,因为它最多会偏离 +/-1°。


您可以尝试使用 eulerAngles,它实际上 returns 围绕 Z 轴的预期旋转:

angleBetween += parent.transform.rotation.eulerAngles.z;

或者您可以使用 * operator

将角度旋转添加到现有旋转
var angleBetween = Vector2.SignedAngle(relativePositionB, relativePositionA);

parent.transform.rotation *= Quaternion.AngleAxis(angleBetween, Vector3.back);

顺便说一句,我会简单地移动

的部分
worldPositionB = Camera.main.ScreenToWorldPoint(Input.mousePosition);

进入 OnMouseDrag() 因为只有那里需要它。

并且通常您应该存储 Camera.main 引用,因为此调用非常昂贵:

// If possible even already reference this via the Inspector!
[SerializeField] private Camera _mainCamera;

// As fallback get it ONCE on runtime
private void Awake()
{
    if(!_mainCamera) _mainCamera = Camera.main;
}

private void OnMouseDown()
{
    worldPositionA = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
}

private void OnMouseDrag()
{
    worldPositionB = _mainCamera.ScreenToWorldPoint(Input.mousePosition);

    var relativePositionA = worldPositionA - parent.transform.position;
    var relativePositionB = worldPositionB - parent.transform.position;

我仍然不知道我以前的脚本有什么问题,但我找到了另一种方法来完成我想要的。 我没有直接使用 Transform,而是添加了一个 Rigidbody 并修改了 Rotation 值。 这是脚本,以防有人需要它:

[SerializeField] private Camera _mainCamera; //Assigned from Inspector

[SerializeField] private GameObject parent = null; //Assigned from Inspector
private Rigidbody2D parentRb2d;

float startRotation;
float angleBetween;

private void Awake()
{
    parentRb2d = parent.GetComponent<Rigidbody2D>();
}

private void Update()
{
    worldPositionB = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
}

private void OnMouseDown()
{
    worldPositionA = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
    startRotation = parentRb2d.rotation;
}

private void OnMouseDrag()
{
    Vector3 relativePositionA = worldPositionA - parent.gameObject.transform.position;
    Vector3 relativePositionB = worldPositionB - parent.gameObject.transform.position;

    angleBetween = Vector2.SignedAngle(relativePositionB, relativePositionA);

    parentRb2d.MoveRotation(startRotation - angleBetween);
}

如果您的对象在相反的方向,请尝试将最后一行中的“-”运算符切换为“+”。