Let the players freely rotate a GameObject

走远了吗. 提交于 2020-06-16 17:29:48

问题


I am trying to find a way so that the player can drag and rotate an object freely using the mouse (click to view scene). As you can see the script to move the object works just fine, the problems come when you want to rotate it. In fact it works the firts time that you try to rotate the object. If you release the mouse and try again, the object snaps back to its initial rotation before being rotated by the mouse again. I don't want that. I want it to continue the rotation from where it is. Here's my code so far:

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 is the world position of the mouse when it starts dragging, worldPositionB is the updated mouse position. relativePosition (A and B) are the same but relative to the center of the object. I know they are correct because I have a raycast that points to those, and the angleBetween is correct too because I'm printing it in che console. What is wrong? Is there something I'm not thinking about?


回答1:


Your issue is most probably related to the line

angleBetween += parent.transform.rotation.z;

rotation is a Quaternion. You should never directly set and read the individual components, unless you know really really well what you are doing. A Quaternion has not 3 but 4 components x, y, z and w which move between -1 and 1, this is most probably not the value you expected.

Since at the beginning worldPositionA = worldPositionB the first angle also will always be between -1 and 1 so it seems that the object is reset to the original rotation.

Later you don't note the mistake to much since it will be off by maximum +/-1°.


You could try and rather use eulerAngles which actually returns the expected rotation around the Z-Axis:

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

Or alternatively you can add the angle rotation to the existing rotation using the * operator

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

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

I would btw simply move the part for

worldPositionB = Camera.main.ScreenToWorldPoint(Input.mousePosition);

into the OnMouseDrag() since it is needed only there.

and in general you should store the Camera.main reference as this call is pretty expensive:

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



回答2:


Not sure this is the reason, but the angle calculation depend on the rotation, that is also changed each frame. I would recommend saving the initial rotation in OnMouseDown so the angle calculation only depend on the initial conditions (i.e. rotation and mouse position) and the current mouse position, not on any intermediate results. This may also help avoid numerical problems.

I'm not particularly familiar with unity, but is there a reason for saving the mouse position in worldPositionB instead of computing it directly in OnMouseDrag?




回答3:


I still don't know what was wrong with my previous script, but I've found another way to accomplish what I wanted. Instead of working with the Transform directly, I added a Rigidbody and modified the Rotation value. Here's the script in case someone wants it:

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

If your object in the opposite direction, try switching the '-' operator in the last line with a '+'.



来源:https://stackoverflow.com/questions/61745756/let-the-players-freely-rotate-a-gameobject

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!