How to adjust FOV dynamically based on distance?

I have a game I’m working on where I’d like to adjust the FOV to get tighter as the enemy approaches, and it technically works. However, there is always a ‘popping’ in and out (even if I Lerp). I know I’m doing something wrong, but I can’t quite figure it out. Please let me know what I should be doing differently. I’m relatively new to Unity, so please bear with me. Thank you.

My script:

using Cinemachine;
using UnityEngine;

public class FOVChanger : MonoBehaviour
{
    [SerializeField] private NoiseSettings idleCameraNoiseSettings;
    [SerializeField] private NoiseSettings escapeCameraNoiseSettings;
    [SerializeField] private float desiredFOV;
    [SerializeField] private float initialFOV;
    private CinemachineVirtualCamera cine;
    private bool outOfRange = true;
    private CinemachineBasicMultiChannelPerlin cineNoise;
    private It enemy;

    // Start is called before the first frame update
    void Start()
    {
        cine = GetComponent<CinemachineVirtualCamera>();
        enemy = FindObjectOfType<It>();
        initialFOV = cine.m_Lens.FieldOfView;
        desiredFOV = initialFOV;
        cineNoise = cine.GetCinemachineComponent<CinemachineBasicMultiChannelPerlin>();
    }

    private void Update()
    {
        // Get distance between camera and player
        float currentDistance = Vector3.Distance(enemy.transform.position, transform.position);

        // Check if enemy is close
        if (currentDistance <= 15)
        {
            print("In range block running");
            outOfRange = false;
            // Animate FOV based on distance
            desiredFOV = (currentDistance + 15) / 15 * (initialFOV / 2);
            cineNoise.m_NoiseProfile = escapeCameraNoiseSettings;
            cineNoise.m_AmplitudeGain = (initialFOV / desiredFOV) / 3;
        }
        else if (!outOfRange)
        {
            print("out of range block running");
            outOfRange = true;
            // TODO - reset camera to initialFOV
            desiredFOV = initialFOV;
            cineNoise.m_NoiseProfile = idleCameraNoiseSettings;
            cineNoise.m_AmplitudeGain = 1f;
        }

        UpdateFOV();
    }

    void UpdateFOV()
    {
        if (desiredFOV == initialFOV && cine.m_Lens.FieldOfView == initialFOV) return;
        print("FOV mismatch");
        cine.m_Lens.FieldOfView = Mathf.Lerp(cine.m_Lens.FieldOfView, desiredFOV, Time.deltaTime * 5);
    }
}

So - I was able to ascertain that it actually wasn’t the FOV being set that was causing the issue, but the setting of the noise profiles that was causing it to shift. How would I switch profiles programmatically while animating the amplitude back down to 1? A coroutine?

You should not switch profiles dynamically unless you first lerp the amplitude gradually down to 0, and then lerp it back up afterwards. Otherwise, you will likely get a camera pop.

Normally, one doesn’t dynamically change profiles. Instead, set up 2 vcams, one with each profile, and activate one or the other allowing the CMBrain to blend smoothly between them.

1 Like

I’d also recommend writing a CinemachineExtension and overriding the PostPipelineStageCallback method, like this:

protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
{
    if (stage == CinemachineCore.Stage.Finalize)
    {
      // Your Update code goes here
    }
}

Would creating the 2nd virtual camera be the ‘best practice?’ Or would hooking into the script to modify the amplitude be the better (read: optimal) solution?

I just want to be able to switch between the standard handicam movement of the basic noise profile, versus the explosion movement (to convey shock). Can you edit a profile to have one start at one basic loop and switch to another? How are these different camera movement mechanics usually handled in Unity?

Forgive my ignorance - Is this where I would be putting the relevant update code for the cinemachine camera at?

I meant the code in your Update method in FOVChanger class.

Best practise would be to use one vcam for one concept. In your case, having two virtual cameras. One for standard handicam and one for explosive movement.

Thank you so much! One final question - is it bad practice to continuously set values that may be the same between updates? For instance, setting the amplitude based on distance. If they’re outside the range it would keep setting the amplitude to 1. Is that bad? Should I set a flag?

Not a problem, you can keep setting the value.

Is that broadly applicable? I find myself doing checks on values before setting them. Is it a relatively safe practice for any value to just keep setting it? Does Unity do some optimization under the hood to maximize performance?

You’ve been a tremendous help. Thank you so much!

In the case of Cinemachine, it’s just pure c#. You can look at the code. It’s possible that in other cases there is logic that gets triggered by setting - you have to check the code in each case.