When researching how to make the camera movement better I came across many references to Lerp smoothing but I found few, if any, detailed explanations as to how this works in practice and (particularly) ways to get around its significant limitations. Cue this post, by the end of which we’ll have a class that’ll move an object smoothly towards a target point and ‘brake’ so it reaches it exactly no matter how often and by how much the target is changed.

I’m just concentrating on one axis of movement here for simplicity but this approach can easily be applied to 2D or 3D vectors. For those of you with short attention spans you can just download the final class here.

So… Lerp is a quasi-acronym for ‘linear interpolation’ and used to create smooth movement between two points. It’s often used to smooth out camera movement but can equally be applied to sprites or anything else in 2D or 3D space. The basic equation for Lerp is very simple, it takes a position and target, works out the difference between them and then returns a value a specified amount along that path. Only values >0 and <1 are generally meaningful.

public static float Lerp(float position, float target, float amount) { float d = (value2 - value1) * amount; return value1 + d; }

Now this looks pretty good already but, as you’ve probably figured out, there are issues. The first one is that using this approach the object will never actually reach its destination – this can result in a slightly jerky looking motion as it reaches the end of its trajectory as well as presenting potential issues if you want to do something when the destination is reached.

One way of resolving this is to implement a minimum amount of movement per frame. This can be anything you want but for many applications it makes sense for this to be one pixel.

Here’s a simple ‘Lerper’ class that implements this – each frame the amount of movement (LerpVelocity) is calculated and, if the absolute value of this is less the minimum velocity we clamp it at the minimum velocity. Of course this means that the object might now be in danger of moving beyond its target point so we check for this at well and make sure it doesn’t.

public static float Lerp(float position, float target, float amount) public class Lerper { public Lerper() { Amount = 0.025f; } // Returns the amount of movement at this stage of the lerp private float LerpVelocity(float position, float target) { return (target - position) * Amount; } public float Lerp(float position, float target) { float v = LerpVelocity(position, target); // If this is less than the minimum velocity then // clamp at minimum velocity if ( Math.Abs(v) 0) ? MinVelocity : 0 - MinVelocity; position += v; // Now account for potential overshoot and clamp to target if necessary if ( (v= target)) { position = target; } return position; } public float Amount { get; set; } public float MinVelocity { get; set; } }

Here’s an example of this in action. As you can see it looks much better…

Still we have limitations though if we’re looking for really smooth movement. The next glaringly obvious one is that Lerp is ‘ease out’ only, the object will be moving fastest at the start of the sequence which makes it look like it’s being fired from a slingshot rather than smoothly accelerating from rest.

To resolve this I’m going to apply an ‘Acceleration’ property to my Lerper class. This ensures that, if the object is increasing in speed, the increase doesn’t exceed a specified amount. This requires a variable to store the amount of movement each frame.

public class Lerper { private float previous_velocity; public Lerper() { Amount = 0.025f; Acceleration = float.MaxValue; } // Returns the amount of movement at this staget of the lerp private float LerpVelocity(float position, float target) { return (target - position) * Amount; } // Returns the next position with lerp smoothing public float Lerp(float position, float target) { // get the amount to move float v = LerpVelocity(position, target); // don't allow increases in velocity beyond the specifed acceleration if ( v>0 && previous_velocity>=0 && v-previous_velocity>Acceleration ) { v = previous_velocity + Acceleration; } else if (v < 0 && previous_velocity Acceleration) { v = previous_velocity - Acceleration; } // If this is less than the minimum velocity then // clamp at minimum velocity if ( Math.Abs(v) 0) ? MinVelocity : 0 - MinVelocity; } // Remember the previous velocity previous_velocity = v; // Adjust the position based on the new velocity position += v; // Now account for potential overshoot if ( (v= target)) { position = target; } return position; } public float Amount { get; set; } public float MinVelocity { get; set; } public float Acceleration { get; set; } }

Here’s an example of this in action – now we have a nice ‘ease in’ to our Lerp motion. Here I’m using an acceleration value of 0.25f.

There’s one other problem I’m going to address though. This ‘ease in’ effect works nicely if the object is initially at rest or moving in the same direction but if the object had previously been moving in the opposite direction we’re still going to get a nasty jolt. Here’s an example where I’m changing the destination to the opposite side before the previous Lerp has reached the end of its trajectory.

So we’re going to check for this in the code and again apply a maximum amount by which the velocity can change each frame. Remember that because we’re changing direction the absolute value of the current velocity at the point of change is the sum of the absolute value of both the current and previous velocities, ie a change in direction from 10 to -10 would result in a new velocity of -20.

A side-effect of this is that the object will probably end up moving away from the target until it’s gathered enough momentum to change direction. As the code that clamps to the target position assumes we are moving towards the target we have to max out the target value if this happens.

We also have a special case check for our minimum velocity here as we need to make sure that if the minimum velocity is applied its applied in the direction that moves towards the target (otherwise the object could get stuck moving in the wrong direction).

public class Lerper { private float previous_velocity; public Lerper() { Amount = 0.025f; Acceleration = float.MaxValue; MinVelocity = 0; } // Returns the amount of movement at this staget of the lerp private float LerpVelocity(float position, float target) { return (target - position) * Amount; } // Returns the next position with lerp smoothing public float Lerp(float position, float target) { // get the amount to move float v = LerpVelocity(position, target); // don't allow increases in velocity beyond the specifed acceleration (ease in) if ( v>0 && previous_velocity>=0 && v-previous_velocity>Acceleration ) { v = previous_velocity + Acceleration; } else if (v Acceleration ) { v = previous_velocity - Acceleration; // we might actually end up moving away from the target // here in which case we adjust the target so we don't get // clamped to it later if (v 0 && previous_velocity Acceleration ) { v = previous_velocity + Acceleration; // we might actually end up moving away from the target // here in which case we adjust the target so we don't get // clamped to it later if (v > 0-MinVelocity) v = MinVelocity; else target = float.MinValue; } // If this is less than the minimum velocity then // clamp at minimum velocity if ( Math.Abs(v) 0) ? MinVelocity : 0 - MinVelocity; } // Remember the previous velocity previous_velocity = v; // Adjust the position based on the new velocity position += v; // Now account for potential overshoot and clamp to target if necessary if ( (v= target)) { position = target; } return position; } public virtual void OnTarget(){} public float Amount { get; set; } public float MinVelocity { get; set; } public float Acceleration { get; set; } }

Here’s what happens when you suddenly change the Lerp target with this code applied – as you can see we now have a nice smooth movement no matter how often we change the target value.

And that’s pretty much it. You can download the final class file here – in the final version I’ve also added a MaxVelocity property, a delegate to be called when the object reaches its destination, and tidied up the code so its not quite so verbose.

If you are in the process of coding a camera for a 2D game I highly recommend you check out this excellent article.

If you are looking for something to move smoothly between two points *over a specified number of frames* then you’re probably better off using some kind of tweening rather than Lerp. Check out Robert Penner’s excellent tweening/easing functions or the ‘smoothstep’ algorithm approaches described here.

These articles take a lot of time and effort to put together. If you found this useful and would like to help me create more content like this like you can support me below – I really appreciate it!

These articles take a lot of time and effort to put together. If you found this useful and would like to help me create more content like this like you can support me below – I really appreciate it!