4
\$\begingroup\$

I'm trying to move my camera with the player but I'm trying to move/lerp over time, so that the camera allows the player to get a small radius from the center as it gets up to speed, and the camera will "catch up" when the player slows or stops.

I've written code that increases the movement speed of the camera the further the player gets - ideally you should only be able to go a set radius before the camera just "follows" you.

This works, except it's too jittery when the player is moving. The larger the distance, the more noticeable the jitter. I've tried different speeds but nothing seems to satisfactorily solve the issue.

Player.cs Update():

var moveHorizontal = player.GetAxis("Move Horizontal") * rigidBody.mass;
var moveVertical = player.GetAxis("Move Vertical") * rigidBody.mass;

var forceDirection = new Vector3(moveHorizontal, 0, moveVertical);

rigidBody.AddForce(forceDirection);

Camera's Follow.cs:

void Awake() {
    offset = transform.position - player.transform.position;
}

void LateUpdate() {
    var dist = Vector3.Distance(transform.position - offset, player.transform.position);

    if (dist != 0) {
        transform.position = Vector3.Lerp(
            transform.position,
            player.transform.position + offset,
            Time.deltaTime * dist * speed);
    }
}

Per the first answer, I've also tried setting my player's rigibody to "Interpolation" mode, and I update my follow script with the suggested smoothing code:

// Compute our exponential smoothing factor.
float blend = 1f - Mathf.Pow(1f - followSharpness, Time.deltaTime * 30f);

transform.position = Vector3.Lerp(
    transform.position,
    player.transform.position + offset,
    blend);

This works initially, but I get awful jitter once my player collides with an object. Plus, interpolation mode appears more susceptible to framerate drops but that's an aspect of my game I haven't worked on yet.

\$\endgroup\$
4
  • \$\begingroup\$ I'm not going to go into enough detail to post an "answer" for this, but since you are using Unity, I would highly recommend you look into Cinemachine. It's a free asset in the asset store (I believe it was purchased by Unity), and there are a number of really great videos / tutorials on how to use it. (Start with this intro from Unite earlier this year: youtube.com/watch?v=jH4LY-SS6oM) I'm pretty sure it will get you exactly the effect you are looking for (without any code, even). \$\endgroup\$
    – livingtech
    Dec 29, 2017 at 16:52
  • \$\begingroup\$ Even during gameplay? I had looked at that a while back but it appeared to be more of a cinematic system meant for cut-scenes, etc. \$\endgroup\$
    – BotskoNet
    Dec 29, 2017 at 23:05
  • \$\begingroup\$ Oh yes, you can definitely use cinemachine during gameplay. I did the tutorial day at Unite Austin, and that project used cinemachine to create a camera much as you describe in a very fast paced (F-Zero style) game. And it only took minutes to set up. \$\endgroup\$
    – livingtech
    Jan 2, 2018 at 17:43
  • \$\begingroup\$ I think the main problem with it (if there is one) is just how many different options and how customizable it is. Can be really overwhelming to start looking at using it without some idea of how to achieve the effect you are going for. Check out this tutorial around minute 8 or so: youtube.com/watch?v=8aYvX7JyYGI \$\endgroup\$
    – livingtech
    Jan 2, 2018 at 17:54

2 Answers 2

6
\$\begingroup\$

This style of using Lerp:

current = lerp(current, target, sharpness)

is an exponential moving average. So if we want to correct it for deltaTime, a linear adjustment won't do. We need an exponential adjustment.

public float followSharpness = 0.1f;

void LateUpdate() {
     // No need for the "if" - we'll practically never reach exactly 0 distance anyway.

     // Compute our exponential smoothing factor.
     float blend = 1f - Mathf.Pow(1f - followSharpness, Time.deltaTime * 30f);

     transform.position = Vector3.Lerp(
            transform.position,
            player.transform.position + offset,
            blend);
}

You can verify that as deltaTime approaches zero, so does our blend factor. When deltaTime is exactly 1/30, the blend factor is exactly equal to sharpness. When deltaTime is 2/30 (eg. we missed a VBlank when trying to hit 30 FPS), the blending result is the same as if we'd performed a blend with sharpness on two consecutive frames.

You might not find you need to scale the follow sharpness by distance, since an exponential moving average already adjusts its speed according to its distance from the new target - moving slowly when close, and faster when further away.

\$\endgroup\$
4
  • \$\begingroup\$ Since physics stepped at its own rate, different from the rendering framerate, you can get a beat frequency between the two that manifests as judder in things like camera follow. Enabling Interpolation on the Rigidbody usually solves this, by smoothing out the motion on frames between physics updates, or falling earlier/later in the cycle. Just ensure you're never moving or rotating the object using its Transform directly, or this will disable interpolation for the frame. \$\endgroup\$
    – DMGregory
    Dec 23, 2017 at 22:27
  • \$\begingroup\$ That does help, until a collision. Before the collision it seems to run smoothly, but begins shuddering a lot when a collision suddenly impacts the velocity. \$\endgroup\$
    – BotskoNet
    Dec 23, 2017 at 22:40
  • \$\begingroup\$ Hmm.. I haven't seen that. You are using Interpolation mode, not Extrapolation, right? \$\endgroup\$
    – DMGregory
    Dec 24, 2017 at 1:49
  • \$\begingroup\$ Yes ----------- \$\endgroup\$
    – BotskoNet
    Dec 24, 2017 at 1:50
0
\$\begingroup\$

This is done very easily and the Youtube Channel "Brackeys" has a nice tutorial on how to achieve such an effect. You can find a link to that video here.

To sum up what was in the video:

  • Create a script and put it on the camera you want to follow your player.
  • Setup a couple public variable which will allow the camera to know which GameObject to follow as well as float variable for desired smooth speed, and a Vector3 for the offset.

Should like something like this:

public Transform target;

public float smoothSpeed = 15f;

public Vector3 offset;

We then set use Unity's built in LateUpdate() function, this allows for all of the other object's to update first, then calls this function. (We want to move the camera after everything else already has.)

  • Then in our LateUpdate() function we write a line of code to contain the Vector3 of the desired position we have, as well as smoothing that position. Finally we set the camera position equal to the smoothed position variable.

The way we smooth our position is using a function Unity provides us with called Lerp. It allows us to interpolate linearly between values based on a time value from 0 to 1.

void LateUpdate()
{
    Vector3 desiredPosition = transform.position + offset;
    Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed * Time.deltatime);
    transform.position = smoothedPosition;
}

There you go, that's all there is to it.

Final notes:

  • Make sure to set the target gameobject in the editor, this should be your player or object you want to follow.
  • To achieve the radius effect you want, play around with the smoothspeed.
  • If you are still getting jittey movement or updates, change LateUpdate() to FixedUpdate(). Although this is not the best practice, it usually will achieve a very smooth effect and will not have jitter in the movements because we are moving the time of updating the camera to what other people call the 'Physics Loop'.
\$\endgroup\$
1
  • 1
    \$\begingroup\$ This is very similar to the code that OP is using but has reported is not meeting their needs. Note too that the deltaTime correction applied to the Lerp here is incorrect for this use, as described in the earlier answer. \$\endgroup\$
    – DMGregory
    Dec 27, 2017 at 19:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .