Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to adjust FOV dynamically based on distance?

Discussion in 'Cinemachine' started by jrhager84, Feb 27, 2023.

  1. jrhager84

    jrhager84

    Joined:
    Oct 8, 2018
    Posts:
    18
    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:
    Code (CSharp):
    1. using Cinemachine;
    2. using UnityEngine;
    3.  
    4. public class FOVChanger : MonoBehaviour
    5. {
    6.     [SerializeField] private NoiseSettings idleCameraNoiseSettings;
    7.     [SerializeField] private NoiseSettings escapeCameraNoiseSettings;
    8.     [SerializeField] private float desiredFOV;
    9.     [SerializeField] private float initialFOV;
    10.     private CinemachineVirtualCamera cine;
    11.     private bool outOfRange = true;
    12.     private CinemachineBasicMultiChannelPerlin cineNoise;
    13.     private It enemy;
    14.  
    15.     // Start is called before the first frame update
    16.     void Start()
    17.     {
    18.         cine = GetComponent<CinemachineVirtualCamera>();
    19.         enemy = FindObjectOfType<It>();
    20.         initialFOV = cine.m_Lens.FieldOfView;
    21.         desiredFOV = initialFOV;
    22.         cineNoise = cine.GetCinemachineComponent<CinemachineBasicMultiChannelPerlin>();
    23.     }
    24.  
    25.     private void Update()
    26.     {
    27.         // Get distance between camera and player
    28.         float currentDistance = Vector3.Distance(enemy.transform.position, transform.position);
    29.  
    30.         // Check if enemy is close
    31.         if (currentDistance <= 15)
    32.         {
    33.             print("In range block running");
    34.             outOfRange = false;
    35.             // Animate FOV based on distance
    36.             desiredFOV = (currentDistance + 15) / 15 * (initialFOV / 2);
    37.             cineNoise.m_NoiseProfile = escapeCameraNoiseSettings;
    38.             cineNoise.m_AmplitudeGain = (initialFOV / desiredFOV) / 3;
    39.         }
    40.         else if (!outOfRange)
    41.         {
    42.             print("out of range block running");
    43.             outOfRange = true;
    44.             // TODO - reset camera to initialFOV
    45.             desiredFOV = initialFOV;
    46.             cineNoise.m_NoiseProfile = idleCameraNoiseSettings;
    47.             cineNoise.m_AmplitudeGain = 1f;
    48.         }
    49.  
    50.         UpdateFOV();
    51.     }
    52.  
    53.     void UpdateFOV()
    54.     {
    55.         if (desiredFOV == initialFOV && cine.m_Lens.FieldOfView == initialFOV) return;
    56.         print("FOV mismatch");
    57.         cine.m_Lens.FieldOfView = Mathf.Lerp(cine.m_Lens.FieldOfView, desiredFOV, Time.deltaTime * 5);
    58.     }
    59. }
     
  2. jrhager84

    jrhager84

    Joined:
    Oct 8, 2018
    Posts:
    18
    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?
     
  3. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,553
    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.
     
    Last edited: Feb 28, 2023
    gaborkb likes this.
  4. gaborkb

    gaborkb

    Unity Technologies

    Joined:
    Nov 7, 2019
    Posts:
    856
    I'd also recommend writing a CinemachineExtension and overriding the PostPipelineStageCallback method, like this:
    Code (CSharp):
    1. protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
    2. {
    3.     if (stage == CinemachineCore.Stage.Finalize)
    4.     {
    5.       // Your Update code goes here
    6.     }
    7. }
     
  5. jrhager84

    jrhager84

    Joined:
    Oct 8, 2018
    Posts:
    18
    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?
     
  6. jrhager84

    jrhager84

    Joined:
    Oct 8, 2018
    Posts:
    18
    Forgive my ignorance - Is this where I would be putting the relevant update code for the cinemachine camera at?
     
  7. gaborkb

    gaborkb

    Unity Technologies

    Joined:
    Nov 7, 2019
    Posts:
    856
    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.
     
  8. jrhager84

    jrhager84

    Joined:
    Oct 8, 2018
    Posts:
    18
    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?
     
  9. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,553
    Not a problem, you can keep setting the value.
     
  10. jrhager84

    jrhager84

    Joined:
    Oct 8, 2018
    Posts:
    18
    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!
     
  11. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,553
    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.
     
    Last edited: Mar 2, 2023