Search code examples
mathunity-game-enginegame-physicsunity3d-2dtools

When rotating 2D sprite towards cursor via angularVelocity, it spins at one point


Intro

I've created a spaceship sprite in my Unity project, I wanted it to rotate towards the cursor via angular velocity, because I'd like make my game to be heavily physics based.

Problem

Now my problem with rotating the sprite via by angular velocity is the following:

At -180° / 180° rotation my ship spins around, because while my mouse's angle is already 180°, while my ship's rotation is still -180°, or the other way around.

I tried

I tried to solve it mathematically, wasn't too successful, I could make it spin the right way just much slower/faster, I could fix the 180/-180 point, but made two different ones instead.

Looked for different solutions, but couldn't find a more fitting one.

Code

So I have this code for the rotation:

// Use this for initialization
void Start () {
    rb = gameObject.GetComponent<Rigidbody2D>();
}

// Update is called once per frame
void Update () {
    //getting mouse position in world units
    mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);

    //getting the angle of the ship -> cursor vector
    angle = Mathf.Atan2(mousePos.y - transform.position.y, mousePos.x - transform.position.x) * Mathf.Rad2Deg;

    //getting the angle between the ship -> cursor and the rigidbody.rotation vector
    diffAngle = angle - (rb.rotation + 90);

    //Increasing angular velocity scaling with the diffAngle
    rb.angularVelocity = diffAngle * Time.deltaTime * PlayerShipStats.Instance.speed * 100f;

Thank you for your contribution in advance

Solution for Problem 1

Inserting this code made it work, not for long :

if(diffAngle > 180) {
        diffAngle -= 360;
    } else if (diffAngle < -180) {
        diffAngle += 360;
    }

Problem 2 and Solution for Problem 2

The new problem is: rigidbody.rotation can exceed it's boundaries, it can be rotated for more than 360 degrees.

this code patched this bug:

if(rb.rotation + 90 >= 180) {
            rb.rotation = -270;
        } else if (rb.rotation + 90 <= -180) {
            rb.rotation =  90;
        }

The perfect code

void AimAtTarget(Vector2 target, float aimSpeed) {

    //getting the angle of the this -> target vector
    float targetAngle = Mathf.Atan2(target.y - transform.position.y, target.x - transform.position.x) * Mathf.Rad2Deg;

    if (rb.rotation + 90 >= 180) {
        rb.rotation = -270;
    } else if (rb.rotation + 90 <= -180) {
        rb.rotation = 90;
    }

    //getting the angle between the this -> target and the rigidbody.rotation vector
    float diffAngle = targetAngle - (rb.rotation - 90);

    if (diffAngle > 180) {
        diffAngle -= 360;
    } else if (diffAngle < -180) {
        diffAngle += 360;
    }

    //Increasing angular velocity scaling with the diffAngle
    rb.angularVelocity = diffAngle * Time.deltaTime * aimSpeed * 100;
}

Solution

  • There are two problems I see here:

    Problem 1

    angle is always going to be between -180 and 180, while rb.rotation is between 0 and 360. So you are comparing angles using two different notations. The first step is to get both angles returning -180 to 180 or 0 to 360. I chose to do the following which puts both angles between -180 and 180:

    //getting the angle of the ship -> cursor vector
    float targetAngle = Mathf.Atan2(
        mousePos.y - transform.position.y, 
        mousePos.x - transform.position.x) * Mathf.Rad2Deg;
    
    //get the current angle of the ship
    float sourceAngle = Mathf.Atan2(
        this.transform.up.y, 
        this.transform.up.x) * Mathf.Rad2Deg;
    

    Problem 2

    If you fix problem 1 and tried your app you would notice that the ship sometimes rotates the wrong way, although it will eventually get to its target. The problem is that diffAngle can sometimes give a result that is greater than +180 degrees (or less than -180). When this happens we actually want the ship to rotate the other direction. That code looks like this:

    //getting the angle between the ship -> cursor and the rigidbody.rotation vector
    float diffAngle = targetAngle - sourceAngle;
    
    //use the smaller of the two angles to ensure we always turn the correct way
    if (Mathf.Abs(diffAngle) > 180f)
    {
        diffAngle = sourceAngle - targetAngle;
    }
    

    I made a simple Unity to verify this works. I was able to rotate my ship in either direction smoothly.

    One thing you may have to handle, if you don't already, is appropriately stopping the rotation of the ship when the it is facing the cursor. In my test I noticed that the ship would jitter slightly when it reached its target because it would (very) slightly overshoot the cursor's angle in one direction and then the other. The larger the value of PlayerShipStats.Instance.speed the more pronounced this effect will likely be.