Smooth look at - Possibly using a corountine? [SOLVED]

Hello!
I’m creating a 2D topdown shooter where I want to rotate the player character to where the player touches the screen. Later on I want the player to shoot after he’s rotated fully, so I thought that maybe a corountine would work here?

if (Input.GetTouch(0).phase == TouchPhase.Began)
{
      Vector3 upAxis = new Vector3(0, 0, -1);
      Vector3 mouseScreenPosition = Input.GetTouch(0).position;
      //set mouses z to your targets
      mouseScreenPosition.z = transform.position.z;
      Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
      transform.LookAt(mouseWorldSpace, upAxis);
}

While this works, it (obviously) snaps directly to the point. I want to make it smoothly rotate (slerp/lerp?).
Could anyone help me out or give me some tips?

I think you can do something like this in place of your transform.LookAt call:

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(mouseWorldSpace, upAxis), Time.deltaTime * rotationSpeed);

First parameter is your start rotation, second is your desired rotation, 3rd is the rotation speed (and rotationSpeed is just a float… experiment with that number to get the desired results, but it will be quite low).

Thanks for your answer, I tried it, but I’m getting a little wierd results! See gif below
https://cl.ly/181v2x012A3h

Another option would be to try Quaternion.RotateTowards for a more linear rotation speed.

Beyond that, how are you updating the rotation? Are you storing the destination rotation on click, then feeding this same rotation into the loop?

It probably because you are slerping a fixed amount from a smaller and smaller range.
Slerp is like a lerp but for rotations/quaternions.

You could just remember your start and end rotations. Then increment the 3rd parameter between 0 - 1 with the speed and time.deltatime.

The gif problem looks like because you are calculating your look at wrong. Cstooch example is just using the world position which is from world 0. You want the direction from your character to the mouse position.
Or go back to the transform.lookat you had at first.

 Vector3 upAxis = new Vector3(0, 0, -1);

        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mouseScreenPosition = Input.mousePosition;
            //set mouses z to your targets
            mouseScreenPosition.z = transform.position.z;
            Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
            LookTarget = mouseWorldSpace;

           // transform.LookAt(mouseWorldSpace, upAxis);

        }

        transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(LookTarget, upAxis), Time.deltaTime * 4.0f);

This was the code that produced the result from the gif. The LookTarget is a vector that’s stored.
@BlackPete Could you come with an example in this case using the RotateTowards, since I need to convert the mouse position to a quaternion it looks.

I think your Quaternion.Lookat is wrong. Look at the docs they show an example.

You already have the quaternion. That is what the lookat function is returning.

1 Like

So, lots of solutions that kinda work and don’t use coroutines - here’s one that uses a coroutine.

if (Input.GetTouch(0).phase == TouchPhase.Began)
{
      Vector3 upAxis = new Vector3(0, 0, -1);
      Vector3 mouseScreenPosition = Input.GetTouch(0).position;
      //set mouses z to your targets
      mouseScreenPosition.z = transform.position.z;
      Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
StartCoroutine(SmoothLookAt(mouseWorldSpace, upAxis, 0.5f)); // 0.5 second duration for the smoothing
}

....

IEnumerator SmoothLookAt(Vector3 worldPoint, Vector3 upAxis, float duration) {
Quaternion startRot = transform.rotation;
Quaternion endRot = Quaternion.LookRotation(worldPoint - transform.position, upAxis);
for (float t=0f; t < duration; t += Time.deltaTime) {
transform.rotation = Quaternion.Slerp(startRot, endRot, t / duration);
yield return null;
}
transform.rotation = endRot;
}

Yeah somewhere I need to minus my player’s transform, but I’m not quite sure where!

Sorry, as noted above, I think the problem is how I suggested the 2nd parameter of Slerp… I was taking a guess there, as I’m not sure how you convert the screen point to an actual rotation (there’s tuts out there like this 2D Mouse Aiming - Questions & Answers - Unity Discussions on how to do that properly though, of course). When I used slerp method, it worked quite well, but I was using joystick input, and I had already figured out my desired look rotation Quaternion, so I just simply dropped that in as 2nd param.

This works almost perfect, except for when I move around the calculations gets wierd. I thought that the endRot did check for this?
I modified it slightly to test with the mouse, setting Vector3 mouseScreenPosition = Input.mousePosition, instead of the touch position. Does that matter?

Indeed, think about where you’re looking from.

When you use transform.LookAt, you’re starting from the transform’s position.

When you use Quaternion.LookRotation, you need to do direction = mousePos - transform.position;

Alright thank you guys all so much! There is one little issue left, I think it has to do with normalization.
What’s coursing the rotation.y to behave like this?
GIF: Screen recording 2017-07-10...

  Vector3 upAxis = new Vector3(0, 0, -1);

        if (Input.GetMouseButtonDown(0))
        {
           
            Vector3 mouseScreenPosition = Input.mousePosition;
            //set mouses z to your targets
            mouseScreenPosition.z = transform.position.z;
            Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
            StartCoroutine(SmoothLookAt(mouseWorldSpace, upAxis, 0.5f)); //

        }

SmoothLookAt(StarManta)

  IEnumerator SmoothLookAt(Vector3 worldPoint, Vector3 upAxis, float duration)
    {
        Quaternion startRot = transform.rotation;
        Quaternion endRot = Quaternion.LookRotation(worldPoint - transform.position, upAxis);
        for (float t = 0f; t < duration; t += Time.deltaTime)
        {
            transform.rotation = Quaternion.Slerp(startRot, endRot, t / duration);
            yield return null;
        }
        transform.rotation = endRot;
    }

I have a suspicion that the axis are wrong, or that the world points aren’t coplanar to the player, hence the sprite flipping. you want the sprite to only spin on one axis so you’ll need to ensure that the world points that are fed into look rotation are coplanar (with a 0 on the z component for 2D, y component for 3D).

can you show another gif but with the sprite selected so that we can see the axes? or with 4 Debug.DrawRays (3 to show each sprite’s axis in update, 1 to show the target look direction while rotating)

EDIT:
Also you should be aware of the behavior behind your smooth rotation code. By using Slerp and duration the rotation speed will be linear, but not consistent. so if you have the duration set to 2 seconds, then it will always take him 2 seconds to fully rotate whether thats 2 degrees or 180 degrees. RotateTowards will always have a linear speed but also consistent.

plus, please set your StartCoroutine to a Coroutine variable and use StopCoroutine when starting a new one. the way you have it now if you click multiple times in opposite sides of the screen the rotations will “freak out” because you’ll have multiple coroutines fighting each other to rotate the character.

1 Like

It looks like it’s only happening on the first click. Try reversing your upAxis.

Yeah, it’s like it’s reversed?
Here is a new GIF: https://cl.ly/0N2Q3S1F2T2k

Edit: And also look how the y is getting modifed differently the further away from the player I press
Edit: I’ve tried that @BlackPete but that only reversed the whole thing, so he looks in the 180 opposite direction of where I’m clicking. The flip in the start still appears.

For what it’s worth, you could use transform.up (assuming up is along the z axis, otherwise use transform.forward or negative forward) instead of hardcoding a Vector3(0,0,-1).

Noted about the linear, thank you. I will stop the coroutine when I start it. Did you see I’ve posted the new gif? I don’t quite know why it does that.

Just to make it clear.
This is my result: Screen recording 2017-07-10... (GIF)

 void Update()
    {
        var move = new Vector2(CnControls.CnInputManager.GetAxis("Horizontal"), CnControls.CnInputManager.GetAxis("Vertical"));
        //transform.Translate(move * speed * Time.deltaTime);
        rb.MovePosition(rb.position + move * Speed * Time.fixedDeltaTime);

        Vector3 upAxis = new Vector3(0, 0, -1);

        if (Input.GetMouseButtonDown(0))
        {           
            Vector3 mouseScreenPosition = Input.mousePosition;
            //set mouses z to your targets
            mouseScreenPosition.z = transform.position.z;
            Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
            LookTarget = mouseWorldSpace;
        }
        if (LookTarget != Vector3.zero)
        {
            Quaternion startRot = transform.rotation;
            Quaternion endRot = Quaternion.LookRotation(LookTarget - transform.position, upAxis);
            transform.rotation = Quaternion.Slerp(startRot, endRot, 4 * Time.deltaTime);
        }
}

You’ll want to use Vector.forward as the up axis cause you want to spin around the world z axis i.e. the Vector.foward.

and cache the lookDirection vector and clear the z value from it before passing it to Quaternion.LookRotation. You’ll also need a rotation offset cause LookRotation tries to point the sprites z-axis at the look direction (which you want looking at the camera), while pointing the y axis to where you’ve defined the upaxis to be (LookRotation will make it point to the camera, when you’ll want it pointing in the opposite direction that you’ve clicked.

Below is the same coroutine reworked with a couple fixes

    IEnumerator LookToPoint2D(Vector3 worldPoint, Vector3 upAxis, float degreesPerSecond)
    {
        Quaternion 2DLookCorrection = Quaternion.LookRotation(Vector3.up,-Vector3.back);
        Vector3 targetDirection = worldPoint - transform.position;
        targetDirection.z = 0; // prevents the

        // this check prevents lookRotation is zero log if you try to feed Vector3.zero to look rotation
        if(targetDirection.sqrMagnitude >= float.Epsilon)
        {

            Quaternion targetRot = Quaternion.LookRotation(targetDirection, upAxis) * 2DLookCorrection;

            while(Quaternion.Dot(transform.rotation,targetRot) < 0.999)
            {
                transform.rotation = Quaternion.RotateTowards(
                    transform.rotation,
                    targetRot,
                    degreesPerSecond * Time.deltaTime);

                yield return null;

                // recalculate on the next frame in case the character is also moving
                targetDirection = worldPoint - transform.position;
                targetDirection.z = 0;

                if(targetDirection.sqrMagnitude < float.Epsilon) break;

                targetRot = Quaternion.LookRotation(targetDirection, upAxis) * 2DLookCorrection;
            }

            if(targetDirection.sqrMagnitude >= float.Epsilon)
                transform.rotation = targetRot;
        }

        //Done rotating, shoot now
    }

Edit: another way of writing the LookRotation without the need of LookCorrection is to swap the fields. make the 1st parameter point to where you want the z to point to (World positive Z) while the second parameter describes where you want the y axis to point (opposite from the target direction)

Quaternion targetRot = Quaternion.LookRotation(Vector3.forward, -targetDirection)