- Home /
Making Quaternion.RotateTowards finish at exact time Vector3.MoveTowards finishes in a coroutine?
I have absolutely no idea if this makes sense, but I have a coroutine in which the transform.position, transform.rotation, and transform.localScale are all being moved towards and rotated towards their own specific value until the position of the object reaches it's value. What I am asking is how to make everything finish at the exact time the transform.position reaches it's value/position. Here is my current code to make it slightly clearer:
private IEnumerator IMoveTransform(Vector3 position, Quaternion rotation, Vector3 scale, float speed)
{
while (transform.localPosition != position)
{
transform.localPosition = Vector3.MoveTowards(transform.localPosition, position, speed * Time.deltaTime);
transform.localScale = Vector3.MoveTowards(transform.localScale, scale, speed * Time.deltaTime);
transform.localRotation = Quaternion.RotateTowards(transform.localRotation, rotation, speed * Time.deltaTime);
yield return new WaitForEndOfFrame();
}
transform.localPosition = position;
transform.localRotation = rotation;
transform.localScale = scale;
}
Answer by TreyH · Sep 24, 2018 at 08:34 PM
Have you tried interpolating them? Untested code below:
private IEnumerator IMoveTransform(Vector3 position, Quaternion rotation, Vector3 scale, float speed)
{
// Keep track of how far we've travelled along this path.
float progress = 0;
float distance = (transform.localPosition - position).magnitude;
// Keep our original position
Vector3 originalPosition = transform.localPosition;
Vector3 originalScale = transform.localScale;
Quaternion originalRotation = transform.localRotation;
// Default transition time is 1
while (progress < distance)
{
float t = progress / distance;
transform.localPosition = Vector3.Lerp(originalPosition, position, t);
transform.localScale = Vector3.Lerp(originalScale, scale, t);
transform.localRotation = Quaternion.Lerp(originalRotation, rotation, t);
progress += speed * Time.deltaTime;
yield return new WaitForEndOfFrame();
}
transform.localPosition = position;
transform.localRotation = rotation;
transform.localScale = scale;
}
This is a naive way of doing it just to demonstrate Lerp. This will not allow any other movement while the routine is running, so you'll want to modify it accordingly if you want this to be gently pushing your object into a desired form alongside other influences.
Right, that's actually the correct way to use lerp (or Slerp which should be used for rotations or direction vectors).
Abusing lerp for "percentage movement" has several issues. First of all it actually "never" ends. It just gets closer and closer. At some point the changes are too small to notice but they are still there. After some more time due to floating point precision you may actually reach the target but theoretically you will never reach it.
Using such percentage movement represents a non linear (exponential) movement. As such it will always be framerate dependent and can't be made framerate independent.
ps: You shouldn't use "WaitForEndOfFrame" unless you have a very specific reason to do so. Using WaitForEndOfFrame makes no sense in this case. First of all applying the changes after the current frame has rendered is pretty pointless since the effect will be seen the next frame. Further more creating a "WaitForEndOfFrame" object each frame just creates garbage that need to be cleaned up. It makes more sense to just use
yield return null;
If for some reason you really need to use "WaitForEndOfFrame" you can create a single static instance and reuse it since it's just an empty class that doesn't contain any state on its own.
// inside the class
public static WaitForEndOfFrame waitEndOfFrame = new WaitForEndOfFrame();
// inside a coroutine
yield return waitEndOfFrame;
I also had no clue that you could use null ins$$anonymous$$d of WaitForEndOfFrame. $$anonymous$$ost likely doesn't help that I never quite understood all of what a coroutine was in the first place. Only that they could pause execution. I'm still learning.
I try to leave peoples' answers as close to their original setup as possible.
For the GC, I do cache my yield instructions but didn't know that null
cleared GC concerns with framewise yielding -- I assumed it'd just create a new instruction for EndOfFrame if null was the instruction. Good to know, ty.