Search Unity

Custom extension with 2D confiner

Discussion in 'Cinemachine' started by samanabo, Feb 24, 2022.

  1. samanabo

    samanabo

    Joined:
    Mar 10, 2015
    Posts:
    51
    Hi,
    I'm looking to create a custom extension (or any other solution) that provides a 2D zoom behavior by changing the Z position of the camera. So far I haven't found a way to get this to work with the 2D confiner (without overriding the confinement) or the editor behavior breaking a bit. The camera/extension should have the following behavior:

    1. Set the Virtual Camera's Z position or the transposer follow distance to display a specified number of world units, based on aspectRatio and FOV
    2. Run in the editor, and set these values so the view is correct when working in the editor
    3. Work with the 2D confiner, so that the camera is still confined correctly, after setting the Z/follow distance

    I have a solution that somewhat works, using a normal Monobehaviour but it doesn't work very well in editor. I suspect an extension would be a better integrated solution but I have not been able to get that to work without breaking the 2D confiner.

    Any help is much appreciated!
    here is the existing monobehaviour I'm using.

    Code (CSharp):
    1. using Cinemachine;
    2. using UnityEngine;
    3.  
    4.  
    5.     [DisallowMultipleComponent]
    6.     public class CinemachineZoom2D : MonoBehaviour {
    7.         [Tooltip("The shot width to maintain, in world units.")]
    8.         [SerializeField]
    9.         private float width = 80f;
    10.  
    11.         [Space]
    12.  
    13.         [SerializeField] private Vector2 aspectRatio = new Vector2(16f, 9f);
    14.         [Range(1f, 200f)] [SerializeField] private float minDistance = 3f;
    15.         [Range(1f, 1000f)] [SerializeField] private float maxDistance = 60f;
    16.  
    17.         private CinemachineVirtualCamera vcam;
    18.         private CinemachineFramingTransposer transposer;
    19.  
    20.         private void OnValidate() {
    21.             width = Mathf.Max(0, width);
    22.             maxDistance = Mathf.Clamp(maxDistance, 1, 1000);
    23.             minDistance = Mathf.Clamp(minDistance, 1, maxDistance);
    24.             UpdatePosition(GetComponent<CinemachineVirtualCamera>().m_Lens.FieldOfView);
    25.         }
    26.  
    27.         private void Awake() {
    28.             vcam = GetComponent<CinemachineVirtualCamera>();
    29.             transposer = vcam.GetCinemachineComponent<CinemachineFramingTransposer>();
    30.         }
    31.  
    32.         private void Start() {
    33.             UpdatePosition(vcam.m_Lens.FieldOfView);
    34.         }
    35.  
    36.         private void UpdatePosition(float fieldOfView) {
    37.             float targetHeight = width * aspectRatio.y / aspectRatio.x;
    38.             float distance = GetZDistanceAtScreenHeight(targetHeight, fieldOfView);
    39.             distance = Mathf.Clamp(distance, minDistance, maxDistance);
    40.  
    41.             if(transposer == null) {
    42.                 Vector3 pos = transform.position;
    43.                 pos.z = -distance;
    44.                 transform.position = pos;
    45.             } else {
    46.                 transposer.m_CameraDistance = distance;
    47.             }
    48.         }
    49.  
    50.         private float GetZDistanceAtScreenHeight(float height, float fieldOfView) {
    51.             return height * 0.5f / Mathf.Tan(fieldOfView * 0.5f * Mathf.Deg2Rad);
    52.         }
    53.     }
    54.  
     
  2. samanabo

    samanabo

    Joined:
    Mar 10, 2015
    Posts:
    51
    @Gregoryl you seem to be the resident expert, any insight? Any other info I can provide on what I'm trying to achieve?
     
  3. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,728
    Can you show an image of your vcam inspector?
     
  4. samanabo

    samanabo

    Joined:
    Mar 10, 2015
    Posts:
    51
    Sure, also as a note, the follow transform is set at runtime to a TargetGroup.
    2022-02-25 14-47-46.png
     
  5. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,728
    Thanks. If your follow target is a group, then the Framing Transposer might provide your requested functionality out of the box. The Group Framing settings at the bottom will make the camera move towards or away from the group so that the members of the group fill the screen, moving no closer than 3 and no farther than 60. Confiner will be respected. Just set the follow target of your vcam to a group (you can change it at runtime, no problem).

    upload_2022-2-25_15-56-54.png

    Note: "Group Framing Size = 1" means Fill the screen. "Group Framing Size = 0.5" means Fill half the screen.
     
  6. samanabo

    samanabo

    Joined:
    Mar 10, 2015
    Posts:
    51
    Thanks, I believe the behavior I'm trying to achieve is a little different from just the Group Framing Mode settings but please let me know if I'm misunderstanding:

    The Group Framing Mode you described will automatically zoom in and out depending on the position of the Target Group, clamped to the Max Dolly in/out settings and the Group Framing Size. It will attempt to keep all targets in the frame by automatically zooming.

    However, I'm attempting to create a behavior where the camera does not automatically change zoom, but rather a level designer can set a specific "world units width" for a particular vcam to zoom to, when that vcam is activated. Additionally, the camera should follow the Target Group, respect the 2D confiner, and be editable/viewable while working in the editor.

    Hopefully that clarifies a bit, but let me know if I'm missing something.
     
  7. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,728
    Isn't that just a matter of setting the camera distance in the framing transposer?

    EDIT: Looking back at your script above, I see that that's what you're doing. What happens if you add the [ExecuteAlways] attribute to the class? That should get it working in edit mode.
     
    samanabo likes this.
  8. samanabo

    samanabo

    Joined:
    Mar 10, 2015
    Posts:
    51
    That does indeed get it running in the editor consistently, and appears to support undo! The only issue I can see is the camera and editor become a bit sluggish when soloing/editing a camera with this script on it.

    This should work for now, but if you have any thoughts on the performance of this vs an extension (or some other solution) I'd be grateful to hear your insight.
    Thanks!
     
  9. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,728
    I don't see why it would become sluggish. Is this only when Soloing?

    One optimization you can make: in OnValidate() use the vcam variable instead of calling the expensive GetComponent().
     
    samanabo likes this.
  10. samanabo

    samanabo

    Joined:
    Mar 10, 2015
    Posts:
    51
    Yes, in testing this a bit more it does seem to be a little sluggish only when soloing. If I enable the camera by setting it to a higher priority, it seems to behave alright. Incidentally it seems I don't need the [ExecuteAlways] attribute so long as the OnValidate method calls UpdatePosition.

    I went ahead and serialized the vcam property to apply the optimization you mentioned. Below is the updated script which does appear to work just fine in the editor and hit all the requirements I had listed. For my purposes this works fine. Thanks again!

    Code (CSharp):
    1. using Cinemachine;
    2. using UnityEngine;
    3.  
    4.  
    5.     [DisallowMultipleComponent]
    6.     public class CinemachineZoom2D : MonoBehaviour {
    7.         [Tooltip("The shot width to maintain, in world units.")]
    8.         [SerializeField]
    9.         private float width = 80f;
    10.  
    11.         [Space]
    12.  
    13.         [SerializeField] private CinemachineVirtualCamera vcam;
    14.         [SerializeField] private Vector2 aspectRatio = new Vector2(16f, 9f);
    15.         [Range(1f, 200f)] [SerializeField] private float minDistance = 3f;
    16.         [Range(1f, 1000f)] [SerializeField] private float maxDistance = 60f;
    17.  
    18.         private CinemachineFramingTransposer transposer;
    19.  
    20.         private void OnValidate() {
    21.             width = Mathf.Max(0, width);
    22.             maxDistance = Mathf.Clamp(maxDistance, 1, 1000);
    23.             minDistance = Mathf.Clamp(minDistance, 1, maxDistance);
    24.             if(vcam) {
    25.                 UpdatePosition(vcam.m_Lens.FieldOfView);
    26.             }
    27.         }
    28.  
    29.         private void Awake() {
    30.             transposer = vcam.GetCinemachineComponent<CinemachineFramingTransposer>();
    31.         }
    32.  
    33.         private void Start() {
    34.             UpdatePosition(vcam.m_Lens.FieldOfView);
    35.         }
    36.  
    37.         private void UpdatePosition(float fieldOfView) {
    38.             float targetHeight = width * aspectRatio.y / aspectRatio.x;
    39.             float distance = GetZDistanceAtScreenHeight(targetHeight, fieldOfView);
    40.             distance = Mathf.Clamp(distance, minDistance, maxDistance);
    41.  
    42.             if(transposer == null) {
    43.                 Vector3 pos = transform.position;
    44.                 pos.z = -distance;
    45.                 transform.position = pos;
    46.             } else {
    47.                 transposer.m_CameraDistance = distance;
    48.             }
    49.         }
    50.  
    51.         private float GetZDistanceAtScreenHeight(float height, float fieldOfView) {
    52.             return height * 0.5f / Mathf.Tan(fieldOfView * 0.5f * Mathf.Deg2Rad);
    53.         }
    54.     }
    55.  
     
  11. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,728
    Glad to hear it's working. Solo in edit mode will always be sluggish - it's meant as a helpful hint only, and triggers serial editor redraws, so runs at a lower framerate. Nothing to do with your script. In playmode it's zippier.
     
    samanabo likes this.