Smooth camera zoom over set amount of time

Hi,

I have been trying to get a camera to zoom out/change FOV smoothly over a set amount of time/seconds when a collion occurs in one of my colliders. I have mainly been trying to use Mathf.Lerp with Time.deltaTime but when I collide with the object the camera does nothing.

Hope someone can help :slight_smile:

In the future this type of thing “ought” to be in the Scripting forum.

Show us your code where you do this. Have you tried putting a breakpoint or a comment in the code where the zoom is, to test whether or not it’s actually triggered?

Sorry im very new to this and didn’t know where to post. I have just started learning programming and unity.

Im using this code block to sense when the player hits a certain collider and then run some other scripts but also change the main cams FOV to 120 with a lerp. I know how lerp works and i know im using it right but i just dont know how to make it smoothly increase the lerp amount.

public class PlayerCollision : MonoBehaviour
{

    public GameObject fracturedCube;
    public Camera cam;

    void OnCollisionEnter(Collision collisionInfo)
    {
        //Disables player movement and frees rotate axis once obstacle has been hit
        if (collisionInfo.collider.CompareTag("Obstacle"))
        {
            FindObjectOfType<GameManager>().GameOver();

            cam.fieldOfView = Mathf.Lerp(cam.fieldOfView, 120, 1);

            fracturedCube.gameObject.SetActive(true);
            fracturedCube.transform.position = transform.position;
            fracturedCube.transform.rotation = transform.rotation;
            gameObject.SetActive(false);
        }
    }
}

The issue is that you’re jumping straight to the end of the lerp, because you’re only calling it once. You need to call it multiple times over multiple frames, with interpolation values changing from 0 to 1 over those frames.

Typically you would do this by putting it in the Update() method. You’ll need a variable to use as the interpolation factor. It starts at 0, and each frame you add Time.deltaTime * speed before you pass it into the Lerp function.

2 Likes

angrypenguin already hit it.

The way Lerp works is that you give it the starting point, the ending point, and some proportional value for how-in-between you want to be.

That means the first value in your lerp function should stay the same (you may need to set that in a variable). And the last value should increase from 0-1 in a repeating function.

I understand the logic behind it but i dont know the code required to make a number incrementally increase every update. Also this code is running in a collision detection routine and I need the lerp function to trigger when that starts so I cant take it out and put it in an update().

If you could help with the code that would be great.

Well, let’s think about what you want to happen.

You want something to start when the collision occurs. There are two easy ways to do this: start a repeating function, or put a clause in an already-existing repeating function that says “if the collision occurs, execute this code.” Another word for that second way is a “conditional.”

You want that something to go from a beginning to ending in the repeating function. You’ve already figured out what can do that - a Lerp function. The documentation mentions that the function interpolates between 0 and 1, so we need to go from 0 to 1. The obvious way to handle this is to use a value that continues to exist after using it in the function, and change that value after using it.

So. In the OnCollisionEnter code, you can 1) set a “lerp” conditional to allow the Lerp to activate in Update (the repeating function, and 2) set a variable for the beginning of the Lerp function (you’ve already decided the end is fixed at 120) so it doesn’t change while updating (which would change the size of subsequent Lerp increments).

Then, in the Update function, you can 1) check that the “lerp” conditional you set is true, and then within that, 2) call the lerp function using the variable for the beginning FOV value, your static “end” 120, and the position - a variable set to 0. After using it you want to make sure the next call moves forward, so you want to 3) increase the value of that position variable by Time.deltaTime (this how long a frame takes, and Update is called once every frame). angrypenguin mentions a speed value: you can multiply the Time.deltaTime value by that to move faster or slower through the lerp (something like lerpPosition = Time.deltaTime * fovSpeed).

There are a couple of pitfalls here. You want to make sure it stops after it gets to the end. The easy way is to check if the position variable is over 1, and if so set the “lerp” conditional to false so that code doesn’t get called. Additionally, you want to ensure this is repeatable, so you want to be sure to set the position variable to 0 either when you start (so in the OnCollisionEnter function) or when you end (when checking if it’s over 1). Finally, you want to make sure things don’t go wrong if there’s another collision while the camera is already changing. You can do this by only triggering the OnCollisionEnter code if the “lerp” conditional is set to false.

Is any of that unclear?

1 Like

Im trying to understand it the best i can but am still a bit confused. Right now im trying to atleast get the cam.fieldofview code into an update block.

This is my new code:

public class PlayerCollision : MonoBehaviour
{

    public GameObject fracturedCube;
    public Camera cam;
    public bool hasCollided = false;

    void OnCollisionEnter(Collision collisionInfo)
    {
        if (collisionInfo.collider.CompareTag("Obstacle"))
        {
            FindObjectOfType<GameManager>().GameOver();

            hasCollided = true;      

            fracturedCube.gameObject.SetActive(true);
            fracturedCube.transform.position = transform.position;
            fracturedCube.transform.rotation = transform.rotation;
            gameObject.SetActive(false);
        }
    }

    void Update()
    {
        if(hasCollided == true)
        {
            cam.fieldOfView = Mathf.Lerp(cam.fieldOfView, 120, 1);
            Debug.Log("FOV Changed");
        }
    }
}

Unfortunately when i run it and collide nothing happens and the Debug.Log statement doesn’t run.

This version of a tag comparison uses the GameObject, not the collider. Have you tried that? Additionally, have you double-checked that the object does indeed have that tag (I think it’s case-sensitive too) in the Editor?

Edit: Actually:

You’re disabling the gameobject, and thus preventing Update from running. You might circumvent this by having a script on your camera or some other non-deleted GameObject, that does the camera lerp, and set the hasCollided variable from that script in this OnCollisionEnter function.

I have this new code on the main camera:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraFOV : MonoBehaviour
{
    void Update()
    {
        if (GetComponent<PlayerCollision>().hasCollided == true)
        {
            Camera.main.fieldOfView = Mathf.Lerp(Camera.main.fieldOfView, 120, 1);
            Debug.Log("FOV Changed");
        }
    }
}

The only problem is when i run it the debug log says the line with get component is a NullReferenceException.
Im probably being dumb but I have no idea whats wrong.

Okay, so:

the GetComponent method only looks on the same gameobject as the script (here, CameraFOV) for whatever is in the arrows (PlayerCollision). This means that for this method to work, PlayerCollision would have to be on the same GameObject as CameraFOV. Just to be clear, you don’t want that: In your previous code you deactivated the gameobject in OnCollisionEnter, right?

The way I would handle this is to keep everything here in the CameraFOV script: keep the hasCollided boolean, the lerpPosition variable, and the initialFOV variable, here in the CameraFOV script.

In your code where you have OnCollisionEnter, in that function you can call this script. Something like:

Camera.main.gameobject.GetComponent<CameraFOV>().StartFOVChange(initialFOV);

There are slightly better ways to do it but that seems simplest.

This implies a StartFOVChange function here in the CameraFOV script. You can do the stuff I mentioned before in this script.

public void StartFOVChange(double fov)
{
    hasCollided = true;
    this.initialFOV = fov;
    this.lerpPosition = 0;
}

(forgive any formatting issues, I’m just typing this out directly)
Then your code in Update here in your new script will continue.

Ok thanks it now runs right. Now im back to asking how to slowly increase the float value for lerp.
Also i left out a few things because i didn’t understand what you were doing. I left out the this.initialFOV and this.lerpPosition along with the double fov. If they are needed please explain what they do. Otherwise im just trying to figure out how to increase the lerp value slowly in the update.

Thanks for your help

Re-read post #7 in the thread here. lerpPosition is what lets the lerp position move from 0 to 1, and initialFOV is what will ensure the transition happens linearly–that you always start at 60 or whatever, and always end at 120.

Very basic example of how to do that because I didn’t feel like trying to figure out which posts had which code to combine.

private float durationInSeconds;
private float currentTime;

public void Update
{
    currentTime += Time.deltaTime / durationInSeconds;

    cam.fieldOfView = Mathf.Lerp(cam.fieldOfView, 120, currentTime);
}

Unfortunately you cannot do smooth-camera motion or zoom due to a major bug in Unity with Time.deltaTime. This is a bug Unity are not willing to fix. In all versions of Unity Time.deltaTime is not constant but it varies, so it makes it impossible to get true smooth motion.

You can read more in this thread Time.deltaTime Not Constant: VSync CameraFollow and Jitter

Just a warning that this is not going to give a smooth transition. As the distance between the two FOV values gets smaller, the relative size of (Time.deltaTime / durationInSeconds) will change.

6132452--668726--Increment2.PNG

The initial FOV value needs to stay the same (hence, there needs to be a variable set when starting the FOV shift).

1 Like

Ok thanks everyone, it now zooms slowly. I understand since Time.deltaTime is a frame by frame basis it wont always look smooth but for what im using it for it doesn’t matter and im willing to try something new if I find it’a a big problem.

One more question for Ryiah, what does current time ajust if you were to alter its content. I understand the rest of the code just not that.

Rather than “currentTime” I would call it “lerpFactor” or something like that. It does not contain a time value. It contains a number between 0 and 1 which tells the Lerp function what the output value should be compared to the two input values.

0 just returns the first value. 1 just returns the second value. 0.5 returns a value half way between the two.

That number is usually calculated by using time, but it isn’t a time itself.

Ok got it. Thanks everyone:)

1 Like