I'm newbie here, but will try to describe as clear as I can. (it's not that important, just to clarify) So I want to rotate an object for 405deg in 1 second only once, when space is pressed. I've thought it should be done using coroutines, so I've wrote something like this:
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
StopAllCoroutines(); //to prevent overlapping
StartCoroutine(Rotate());
}
}
IEnumerator Rotate()
{
//where final finalRotation is a rotation i want to get in a result
Quaternion finalRotation = Quaternion.Euler(0, 0, 405) * transform.rotation;
/*
using do..while to make sure that object will rotate even in case it's in same rotation as finalRotation
*/
do
{
//should smoothly rotate an object for 405deg in 1 sec but it doesnt
transform.Rotate(Vector3.forward, 405f * Time.deltaTime / 1f);
} while (transform.rotation != finalRotation);
yield return 1;
}
The code above works fine and everything is ok except one, it rotates an object instantly and not smoothly. I mean, if I use that line of code in Update, everything go smoothly
transform.Rotate(Vector3.forward, 405f * Time.deltaTime / 1f);
So my questions:
Why does it rotate smoothly in Update, but instantly in the coroutine;
and how can that be fixed?
Thanks in advance, once again, sorry if I made some mistakes when making a topic.
It updates instantly in the coroutine because you only yield when the rotation is complete. The frame wont be rendered until every pending/running coroutine yields or completes, so here your rotation completes and then finally the frame is rendered, making it an instant rotation.
Instead, you should yield in the loop.
You should also be aware that Quaternion finalRotation = Quaternion.Euler(0, 0, 405) * transform.rotation; and transform.Rotate(Vector3.forward, 405f); can have different results depending on parent object rotations, because they rotate in different spaces, the first rotating in global and the second rotating in local. This may result in the loop as written actually never ending.
Another problem is that there is no guarantee that summing up Time.deltaTime values will ever add up to 1f. Suppose on the very first frame, lag occurs, causing Time.deltaTime to be huge, 2f. You can see how it might never end as a result. As an alternative, you should remember the start rotation, then on each frame calcuate the rotation from that frame, then apply it that frame.
Altogether:
IEnumerator Rotate()
{
Quaternion startRotation = transform.rotation;
float endZRot = 405f;
float duration = 1f;
float t = 0;
while (t < 1f)
{
time = Mathf.Min(1f, t + Time.deltaTime/duration);
Vector3 newEulerOffset = Vector3.forward * (endZRot * t);
// global z rotation
transform.rotation = Quaternion.Euler(newEulerOffset) * startRotation;
// local z rotation
// transform.rotation = startRotation * Quaternion.Euler(newEulerOffset);
yield return null;
}
}
yield return null will wait one frame inside a coroutine. Right now you have placed the yield return 1; outside the while loop, so it wont do anything. In this situation I whould lerp between the two rotations in a while loop that goes from 0->1
or (if you dont want to do any of this stuff and dont care for a bit of bloat) use leantween, https://assetstore.unity.com/packages/tools/animation/leantween-3595
In the place where you rotate it one increment you have to wait for some time. Try
yield return new WaitForSeconds(0.1);
Also while I was typing this someone else answered.