Rotation with Quaternion.Slerp isn't smooth

I’m trying to rotate the player character towards the direction they are moving smoothly. I’m rotating the player once to align to the ground and another time to face the movement direction. This works correctly, but when I use Quaternion.Slerp, the rotation is “choppy” and does not work as intented.

Here is my code:

void Update()
    {
        alignToGround();
        if (onGround)
            groundMovement();
        else airMovement();

       
    }



    void groundMovement()
    {
        float InputX = Input.GetAxisRaw("Horizontal");
        float InputY = Input.GetAxisRaw("Vertical");
        Vector3 moveDir = (cam.transform.forward * InputY) + (cam.transform.right * InputX);
        moveDir = Vector3.ProjectOnPlane(moveDir, transform.up).normalized;

        if (moveDir != Vector3.zero)
        {
            Quaternion toRotation = Quaternion.LookRotation(moveDir, transform.up);
            transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
        }
           
       
        Vector3 velocity = moveDir * speed;

        if (velocity.magnitude > maxSpeed)
            velocity = velocity.normalized * maxSpeed;


        if (Input.GetKeyDown(KeyCode.R))
            rb.AddForce(transform.up * jumpForce * 1000, ForceMode.Force);


        float moveDirSpeed = rb.velocity.magnitude * (Vector3.Dot(rb.velocity.normalized, moveDir));
       
        if (moveDirSpeed < maxSpeed)
        {
            rb.velocity = rb.velocity + (velocity - ( moveDir * moveDirSpeed));
        }

        applyFriction(moveDir);

        Debug.DrawRay(transform.position, moveDir * 5, Color.green);
        Debug.DrawRay(transform.position, transform.up * 5, Color.red);
    }
    

    void airMovement()
    {
        rb.AddForce(Vector3.down * gravityForce, ForceMode.Force);
    }


    void applyFriction(Vector3 moveDir)
    {
        if (moveDir == Vector3.zero)
        {
            rb.velocity = Vector3.MoveTowards(rb.velocity, Vector3.zero, 20 * Time.deltaTime);
        }
        else
        {
            rb.velocity = Vector3.MoveTowards(rb.velocity, moveDir * speed, 15);
        }
    }


    void alignToGround()
    {
        Ray ray = new Ray(transform.position, -transform.up);
        RaycastHit info = new RaycastHit();
        Quaternion rf = Quaternion.Euler(0, 0, 0);

        if (Physics.Raycast(ray, out info, 0.51f, groundMask))
        {
            transform.rotation = Quaternion.FromToRotation(transform.up, info.normal) * transform.rotation;
            onGround = true;
        }
        else onGround = false;
    }

Smoothing movement between any two particular values:

You have currentQuantity and desiredQuantity.

  • only set desiredQuantity
  • the code always moves currentQuantity towards desiredQuantity
  • read currentQuantity for the smoothed value

Works for floats, Vectors, Colors, Quaternions, anything continuous or lerp-able.

The code: SmoothMovement.cs · GitHub

Another approach would be to use a tweening package such as LeanTween, DOTween or iTween.

OR… just make a simple canned animation… No code needed!

So, I’m not sure if I’m understanding correctly, but am I not already doing this? I set the desired quantity

Quaternion toRotation = Quaternion.LookRotation(moveDir, transform.up);

and then I Slerp the current quantity to it

transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);

I’m not doing this with the ground alignment because I want that part of the rotation to be immediate.

Try enabling rotation constraints (Freeze Rotation) on all axis so then your script isn’t fighting with the physics engine.

All Lerps should have the following form : initial value, end value, a timer going from 0 to 1.

No multiplying the timer with delta time and changing the initial value every frame and other bullshit like that, that unfortunately Unity also likes to teach in its tutorials.

You can answer it yourself.

The third term of any Slerp/Lerp needs to go from 0 to 1 smoothly (or from 1 to 0).

That’s how Slerp/Lerp works. If you don’t “get” this, focus on it until you do.

NOW… armed with that knowledge, reason about what term you’re passing in:

and you can see it doesn’t make sense because it does not go from 0 to 1.

Slerp/Lerp is completely stateless. Nothing you have done before matters. Every computation is 3 values in, one value out. ALWAYS.

So, going back to my first post: make something that smoothly goes from 0 to 1, using the technique above.

Take the output of that thing (eg, the “current quantity”) and use it in your Slerp/Lerp.

EDIT: looking at your problem space, Slerp/Lerp isn’t going to be useful. You already know the direction TO face (that’s the “desired”), now track the direction you ARE facing (that’s the “current”) and go implement the code contained in the gist above. Be sure you use Mathf.MoveTowardsAngle(), as already noted in the example code provided above.

I see that the code you linked is for angles / floats, while I am trying to rotate a quaternion. Is there a quaternion equivalent (RotateTowards maybe?), or do I need to use angles instead?

EDIT: Also while I don’t doubt that using Slerps in the way you described is not ideal, I think the issue lies elsewhere. I just copy pasted the code in another project and it appears to work.

EDIT 2: just kidding it still doesn’t work, only in a different way.