How to properly use lerp on an object that is already moving?

Let me preface this post by saying that I realize I am not using lerp properly in the following code example. That’s why I’m here. I would like to understand how to use lerp properly in a situation like this.

// manually rotate cam around player after race ended:
// 1: rotate around the player at a gradual rate
Camera.main.transform.RotateAround(transform.position, Vector3.up, Time.deltaTime * (RACE_ENDED_ROTATION +
    (RACE_ENDED_ROTATION * Mathf.Abs(Mathf.Sin(Time.time / 16f)))));

// 2: gradually zoom in and out (towards the player), slowly moving slightly more outwards with each Sine cycle
Camera.main.transform.position += Camera.main.transform.forward * Time.deltaTime * 0.2f * (Mathf.Sin(Time.time / 3f) - 0.05f);

// 3: look directly towards player, but slowly transition to the player if we just ended.
Camera.main.transform.LookAt(Time.time - raceEndedTime > 4f ? this.transform.position :
    Vector3.Lerp(
        Camera.main.transform.position + (Camera.main.transform.forward * 0.5f),
        this.transform.position,
        (Time.time - raceEndedTime) / 2f));

So basically, I am working on a little animation that smoothly rotates around the player after they are done a level. Here is what it currently looks like:

For the most part, the animation is fine, it’s just that for “step 3” the code doesn’t really make sense, it’s not using lerp properly, so it’s weird to properly configure or adjust. The start position is constantly changing. The animation happens near instantly, while the code at a glance suggests it should last 2 seconds. I can make it smoother by changing up the numbers, but I’d like to understand if there’s any “proper method” to using lerp in a situation like this. I have a similar solution using SmoothDamp that works better, but it has the same issue of the start position changing, and the code not really making sense.

From reading various posts and articles, I understand that the proper way to use lerp is to have a start position and destination position that don’t change. However, this does not apply to my situation. Every frame, the start position of this new animation SHOULD be changing, as the camera is already moving, but that means I am unable to interpolate at a linear rate. I don’t really know how to explain it properly, it just doesn’t work as expected if I save the camera’s position when the race ends and use that in the lerp - the camera ends up looking off to the right, since it is moving to the left but that movement is not accounted for in the fixed start position.

Does anyone know how to make lerp work in a situation like this, where the target object’s position being modified is already moving? Any advice would be greatly appreciated. Understanding this would also allow me to clean up many other similar behaviors in this project.

What’s the problem with SmoothDamp? It’s supposed to work with a changing value, so I don’t really see it as an issue.

You’re right about that, I’m not sure why I thought the changing start position was causing my confusion with SmoothDamp:

Maybe I misunderstood the SmoothDamp documentation, but it seemed to me like the smoothTime parameter is meant to dictate the animation length (approximately). So with a smoothTime param of 1, the movement should last ~1 second, and with a param of 0.1, it should last ~100ms. But, this doesn’t seem to be the case, so I thought I was using it improperly like with lerp. I don’t expect it to be perfect, but it’s way off. With a value of 1, it lasts > 4 seconds, around 10 or so seconds. With a value of 0.2, it takes about 2 seconds (~3 to fully complete) and looks good. Is the smoothTime not meant to directly represent animation duration like that?

Visually, my SmoothDamp implementation looks fine (better than the gif example, I wanted to keep the simpler Lerp example in the post):

// 3: look directly towards player, but slowly transition to the player if we just ended.
Vector3 dampVel = Vector3.zero;
Camera.main.transform.LookAt(Time.time - raceEndedTime > 4f ? this.transform.position :
    Vector3.SmoothDamp(
        Camera.main.transform.position + (Camera.main.transform.forward * 0.5f),
        this.transform.position, ref dampVel, 0.1f));

I guess I was looking for a more intuitive way to use Vector3.Lerp or Vector3.SmoothDamp in a situation like this, so that I don’t have to use trial and error to adjust animation timings. Though if this is at least normal/expected usage & behavior of SmoothDamp, that is good to know.

Omg I get it, my issue with SmoothDamp is that I’m supposed to actually keep the velocity variable alive during the whole animation. It works much better now that I’ve actually made the Vector3 a field rather than re-setting it to zero before each SmoothDamp call. Makes a lot of sense, actually. Why else would it be passed by ref, derp:face_with_spiral_eyes:

I wanna delete this so bad but I will leave it up in case any poor souls who go down the same path as I did come across this thread.

Thanks for leading me to dig in the right direction.

Update/edit: Actually, the duration/timing of SmoothDamp’s “smoothTime” parameter is still questionable, and seemingly random at times, so it still requires some trial & error to adjust properly. It does seem to very roughly last that amount of time, in terms of most of the movement, but for transitioning into a fixed position, as I was doing after 4 seconds in the initial lerp example (e.g, LookAt(Time.time - raceEndedTime > 4f ? this.transform.position : <Vector3.Lerp>), it seems to require a smaller value than expected - or you also need to interpolate the smoothTime parameter near the end of your animation so that it’s small and gets to the destination. Using SmoothDamp was still easily an improvement overall but still not as straightforward as I was hoping. Open to any advice/discussion regarding making the smoothTime param more intuitive to use.

2 Likes