Use MoveTowards with duration instead of speed

2019-02-25 05:42发布

问题:

I want to move GameObject from position A to B with Vector3.MoveTowards within x seconds and in a coroutine function. I know how to do this with Vector3.Lerp but this time would prefer to do it with Vector3.MoveTowards since both functions behave differently.

With Vector3.Lerp, this is done like this:

IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
    float counter = 0;

    //Get the current position of the object to be moved
    Vector3 startPos = fromPosition.position;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
        yield return null;
    }
}

I tried to do the the-same thing with Vector3.MoveTowards, but it's not working properly. The problem is that the move finishes before the x time or duration. Also, it doesn't move smoothly. It jumps to the middle of both positions than to the end of position B.

This is the function that uses Vector3.MoveTowards with the issue above:

IEnumerator MoveTowards(Transform objectToMove, Vector3 toPosition, float duration)
{
    float counter = 0;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        Vector3 currentPos = objectToMove.position;
        float time = Vector3.Distance(currentPos, toPosition) / duration;

        objectToMove.position = Vector3.MoveTowards(currentPos, toPosition,
         time);

        Debug.Log(counter + " / " + duration);
        yield return null;
    }
}

How do you move GameObject from position A to B with Vector3.MoveTowards within x seconds and in a coroutine function?

Please do not suggest Vector3.Lerp as that's not what I want to use.

EDIT:

Replacing

float time = Vector3.Distance(currentPos, toPosition) / duration;

with

float time = Vector3.Distance(startPos, toPosition) / (duration * 60f);

works but introduces a problem when focus is shifted from Unity to another application. Doing that causes the movement to not finish. Calculating it every frame instead of once before starting the timer seems more reasonable.

MatrixTai's answer fixed both problems.

回答1:

As I have long time not touching Unity... but I do believe you mess up in the calculation.

First of all, Vector3.MoveTowards(currentPos, toPosition, time) is talking about

Walking from currentPos to toPosition with each frame moving certain distance time.

Thus using time as name is confusing, but fine lets keep it.

However, you will notice in the statement, time is something move each frame. But Vector3.Distance(currentPos, toPosition) / duration is velocity (m/s), not (m/frame). To make it (m/frame), simply times Time.deltatime which is (s/frame).

Secondly,

When in coroutine, which means the function is iterated each frame. In the line float time = Vector3.Distance(currentPos, toPosition) / duration * Time.deltatime;, what you will notice is that the time keeps on decreasing when going next frame as distance become smaller and smaller.

To be more concrete, we can do some math. Typically this should be done with calculus, but lets simplify it by considering just 2 points. When object postion d = 0, and object postion d ~= 9.9, assume endpoint at 10.

At point 1, the object has time = (10-0)/duration, full speed. At point 2, the object has time = (10-9.9)/duration, 1/10 of full speed.

Unless you want it to move slower every frame, you cannot hold the value of duration unchanged. As after each frame, you want the velocity to be kept, duration should thus decreases with distance.

To make that physics work, minus the duration for the time passed.

So the final solution is

float time = Vector3.Distance(currentPos, toPosition) / (duration-counter) * Time.deltaTime;

Here is the complete function:

IEnumerator MoveTowards(Transform objectToMove, Vector3 toPosition, float duration)
{
    float counter = 0;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        Vector3 currentPos = objectToMove.position;

        float time = Vector3.Distance(currentPos, toPosition) / (duration - counter) * Time.deltaTime;

        objectToMove.position = Vector3.MoveTowards(currentPos, toPosition, time);

        Debug.Log(counter + " / " + duration);
        yield return null;
    }
}


标签: c# unity3d lerp