//#define CINEMACHINE_RESET_PROJECTION_MATRIX // GML todo: decide on the correct solution using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Serialization; namespace Unity.Cinemachine { /// /// CinemachineBrain is the link between the Unity Camera and the CinemachineCameras /// in the Scene. It monitors the priority stack to choose the current Cinemachine /// Camera, and blend with another if necessary. Finally and most importantly, /// it applies the CinemachineCamera state to the attached Unity Camera. /// /// The CinemachineBrain is also the place where rules for blending between Cinemachine /// Cameras are defined. Camera blending is an interpolation over time of one Cinemachine /// Camera position and state to another. If you think of CinemachineCameras as cameramen, /// then blending is a little like one cameraman smoothly passing the camera to another /// cameraman. You can specify the time over which to blend, as well as the blend curve /// shape. Note that a camera cut is just a zero-time blend. /// [DisallowMultipleComponent] [ExecuteAlways] [AddComponentMenu("Cinemachine/Cinemachine Brain")] [HelpURL(Documentation.BaseURL + "manual/CinemachineBrain.html")] public class CinemachineBrain : MonoBehaviour, ICameraOverrideStack, ICinemachineMixer { /// /// When enabled, the current camera and blend are indicated in the game window, /// for debugging. /// [Tooltip("When enabled, the current camera and blend are indicated in " + "the game window, for debugging")] [FormerlySerializedAs("m_ShowDebugText")] public bool ShowDebugText = false; /// /// When enabled, shows the camera's frustum in the Scene view. /// [Tooltip("When enabled, shows the camera's frustum at all times " + "in the Scene view")] [FormerlySerializedAs("m_ShowCameraFrustum")] public bool ShowCameraFrustum = true; /// /// When enabled, the cameras always respond in real-time to user input and damping, /// even if the game is running in slow motion. /// [Tooltip("When enabled, the cameras always respond in real-time to user input " + "and damping, even if the game is running in slow motion")] [FormerlySerializedAs("m_IgnoreTimeScale")] public bool IgnoreTimeScale = false; /// /// If set, this GameObject's Y axis defines the world-space Up vector for all the /// CinemachineCameras. This is useful in top-down game environments. If not set, /// Up is world-space Y. /// [Tooltip("If set, this GameObject's Y axis defines the world-space Up vector for all the " + "CinemachineCameras. This is useful for instance in top-down game environments. " + "If not set, Up is world-space Y. Setting this appropriately is important, " + "because CinemachineCameras don't like looking straight up or straight down.")] [FormerlySerializedAs("m_WorldUpOverride")] public Transform WorldUpOverride; /// /// The CinemachineBrain finds the highest-priority CinemachineCamera that outputs /// to any of the channels selected. CinemachineCameras that do not output to one /// of these channels are ignored. Use this in situations where multiple /// CinemachineBrains are needed (for example, Split-screen). /// [Tooltip("The CinemachineBrain finds the highest-priority CinemachineCamera that outputs to " + "any of the channels selected. CinemachineCameras that do not output to one of these " + "channels are ignored. Use this in situations where multiple CinemachineBrains are " + "needed (for example, Split-screen).")] public OutputChannels ChannelMask = (OutputChannels)(-1); // default is Everything /// The options available for the update method. public enum UpdateMethods { /// CinemachineCameras are updated in sync with the Physics module, in FixedUpdate. FixedUpdate, /// CinemachineCameras are updated in MonoBehaviour LateUpdate. LateUpdate, /// CinemachineCameras are updated according to how the target is updated. SmartUpdate, /// CinemachineCameras are not automatically updated, client must explicitly call /// the CinemachineBrain's ManualUpdate() method. ManualUpdate }; /// /// Depending on how the target GameObjects are animated, adjust the update method to /// minimize the potential jitter. Use FixedUpdate if all your targets are animated /// with for RigidBody animation. SmartUpdate chooses the best method for each /// CinemachineCamera, depending on how the target is animated. /// [Tooltip("The update time for the CinemachineCameras. Use FixedUpdate if all your targets are animated " + "during FixedUpdate (e.g. RigidBodies), LateUpdate if all your targets are animated " + "during the normal Update loop, and SmartUpdate if you want Cinemachine to do the " + "appropriate thing on a per-target basis. SmartUpdate is the recommended setting")] [FormerlySerializedAs("m_UpdateMethod")] public UpdateMethods UpdateMethod = UpdateMethods.SmartUpdate; /// The options available for the update method. public enum BrainUpdateMethods { /// Camera is updated in sync with the Physics module, in FixedUpdate. FixedUpdate, /// Camera is updated in MonoBehaviour LateUpdate (or when ManualUpdate is called). LateUpdate }; /// /// The update time for the Brain, i.e. when the blends are evaluated and the /// brain's transform is updated. /// [Tooltip("The update time for the Brain, i.e. when the blends are evaluated and " + "the brain's transform is updated")] [FormerlySerializedAs("m_BlendUpdateMethod")] public BrainUpdateMethods BlendUpdateMethod = BrainUpdateMethods.LateUpdate; /// Defines the settings for Lens Mode overriding. [Serializable] public struct LensModeOverrideSettings { /// If set, enables CinemachineCameras to override the lens mode of the camera. [Tooltip("If set, enables CinemachineCameras to override the lens mode of the camera")] public bool Enabled; /// Lens mode to use when no mode override is active. [Tooltip("Lens mode to use when no mode override is active")] public LensSettings.OverrideModes DefaultMode; } /// Controls whether CinemachineCameras can change the lens mode. [FoldoutWithEnabledButton] public LensModeOverrideSettings LensModeOverride = new () { DefaultMode = LensSettings.OverrideModes.Perspective }; /// /// The blend that is used if you don't explicitly define a blend between two CinemachineCameras. /// [Tooltip("The blend that is used in cases where you haven't explicitly defined a " + "blend between two CinemachineCameras")] [FormerlySerializedAs("m_DefaultBlend")] public CinemachineBlendDefinition DefaultBlend = new (CinemachineBlendDefinition.Styles.EaseInOut, 2f); /// /// This is the asset that contains custom settings for blends between /// specific CinemachineCameras in your Scene. /// [Tooltip("This is the asset that contains custom settings for blends between " + "specific CinemachineCameras in your Scene")] [FormerlySerializedAs("m_CustomBlends")] [EmbeddedBlenderSettingsProperty] public CinemachineBlenderSettings CustomBlends = null; Camera m_OutputCamera = null; // never use directly - use accessor GameObject m_TargetOverride = null; // never use directly - use accessor int m_LastFrameUpdated; Coroutine m_PhysicsCoroutine; readonly WaitForFixedUpdate m_WaitForFixedUpdate = new (); readonly BlendManager m_BlendManager = new (); static readonly List s_ActiveBrains = new (); CameraState m_CameraState; // Cached camera state #if CINEMACHINE_UIELEMENTS && UNITY_EDITOR DebugText m_DebugText; #endif void OnValidate() { DefaultBlend.Time = Mathf.Max(0, DefaultBlend.Time); } void Reset() { DefaultBlend = new CinemachineBlendDefinition(CinemachineBlendDefinition.Styles.EaseInOut, 2f); CustomBlends = null; ShowDebugText = false; ShowCameraFrustum = true; IgnoreTimeScale = false; WorldUpOverride = null; ChannelMask = (OutputChannels)(-1); UpdateMethod = UpdateMethods.SmartUpdate; BlendUpdateMethod = BrainUpdateMethods.LateUpdate; LensModeOverride = new LensModeOverrideSettings { DefaultMode = LensSettings.OverrideModes.Perspective }; } void Awake() { ControlledObject.TryGetComponent(out m_OutputCamera); } void Start() { m_LastFrameUpdated = -1; UpdateVirtualCameras(CameraUpdateManager.UpdateFilter.Late, -1f); } void OnEnable() { m_BlendManager.OnEnable(); m_BlendManager.LookupBlendDelegate = LookupBlend; s_ActiveBrains.Add(this); #if UNITY_EDITOR && CINEMACHINE_UIELEMENTS CinemachineDebug.OnGUIHandlers -= OnGuiHandler; CinemachineDebug.OnGUIHandlers += OnGuiHandler; #endif // We check in after the physics system has had a chance to move things m_PhysicsCoroutine = StartCoroutine(AfterPhysics()); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; } void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; #if UNITY_EDITOR && CINEMACHINE_UIELEMENTS CinemachineDebug.OnGUIHandlers -= OnGuiHandler; m_DebugText?.Dispose(); m_DebugText = null; #endif s_ActiveBrains.Remove(this); m_BlendManager.OnDisable(); StopCoroutine(m_PhysicsCoroutine); UpdateTracker.ForgetContext(this); CameraUpdateManager.ForgetContext(this); } void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (Time.frameCount == m_LastFrameUpdated && m_BlendManager.IsInitialized) ManualUpdate(); } void OnSceneUnloaded(Scene scene) { if (Time.frameCount == m_LastFrameUpdated && m_BlendManager.IsInitialized) ManualUpdate(); } void LateUpdate() { if (UpdateMethod != UpdateMethods.ManualUpdate) ManualUpdate(); } // Instead of FixedUpdate() we have this, to ensure that it happens // after all physics updates have taken place IEnumerator AfterPhysics() { while (true) { // FixedUpdate can be called multiple times per frame yield return m_WaitForFixedUpdate; DoFixedUpdate(); } } #if UNITY_EDITOR /// This is only needed in editor mode to force timeline to call OnGUI while /// timeline is up and the game is not running, in order to allow dragging /// the composer guide in the game view. void OnPreCull() { if (!Application.isPlaying) { // Note: this call causes any screen canvas attached to the camera // to be painted one frame out of sync. It only happens in the editor when not playing. ProcessActiveCamera(GetEffectiveDeltaTime(false)); } } // We don't want this in runtime because it's only for debugging and it can generate garbage void OnGUI() { if (CinemachineDebug.OnGUIHandlers != null && Event.current.type != EventType.Layout) CinemachineDebug.OnGUIHandlers(this); } #if CINEMACHINE_UIELEMENTS void OnGuiHandler(CinemachineBrain brain) { if (!ShowDebugText && m_DebugText != null) { m_DebugText.Dispose(); m_DebugText = null; } if (!ShowDebugText || brain != this) return; m_DebugText ??= new DebugText(OutputCamera); // Show the active camera and blend var sb = CinemachineDebug.SBFromPool(); sb.Length = 0; sb.Append("CM "); sb.Append(gameObject.name); sb.Append(": "); if (CinemachineCore.SoloCamera != null) { sb.Append("SOLO "); m_DebugText.SetTextColor(CinemachineCore.SoloGUIColor()); } else m_DebugText.RestoreOriginalTextColor(); if (IsBlending) sb.Append(ActiveBlend.Description); else { var vcam = ActiveVirtualCamera; if (vcam == null) sb.Append("(none)"); else { sb.Append(vcam.Name); var desc = vcam.Description; if (!string.IsNullOrEmpty(desc)) { sb.Append(" "); sb.Append(desc); } } } m_DebugText.SetText(sb.ToString()); CinemachineDebug.ReturnToPool(sb); } #endif #endif // ============ ICameraOverrideStack implementation ================ /// public int SetCameraOverride( int overrideId, int priority, ICinemachineCamera camA, ICinemachineCamera camB, float weightB, float deltaTime) => m_BlendManager.SetCameraOverride(overrideId, priority, camA, camB, weightB, deltaTime); /// public void ReleaseCameraOverride(int overrideId) => m_BlendManager.ReleaseCameraOverride(overrideId); /// Get the default world up for the CinemachineCameras. public Vector3 DefaultWorldUp => (WorldUpOverride != null) ? WorldUpOverride.transform.up : Vector3.up; // ============ ICinemachineMixer implementation ================ /// public string Name => name; /// public string Description { get { if (ActiveVirtualCamera == null) return "(none)"; if (IsBlending) return ActiveBlend.Description; var sb = CinemachineDebug.SBFromPool(); sb.Append(ActiveVirtualCamera.Name); sb.Append(" "); sb.Append(ActiveVirtualCamera.Description); var text = sb.ToString(); CinemachineDebug.ReturnToPool(sb); return text; } } /// public CameraState State => m_CameraState; /// public bool IsValid => this != null; /// public ICinemachineMixer ParentCamera => null; // GML todo: think about this /// public void UpdateCameraState(Vector3 up, float deltaTime) {} // GML todo: think about this /// public void OnCameraActivated(ICinemachineCamera.ActivationEventParams evt) {} // GML todo: think about this /// public bool IsLiveChild(ICinemachineCamera cam, bool dominantChildOnly = false) { if (CinemachineCore.SoloCamera == cam || m_BlendManager.IsLive(cam)) return true; // Walk up the parents var parent = cam.ParentCamera; if (parent != null && parent.IsLiveChild(cam, dominantChildOnly)) return IsLiveChild(parent, dominantChildOnly); return false; } // ============ Global Brain cache ================ /// Access the array of active CinemachineBrains in the Scene public static int ActiveBrainCount => s_ActiveBrains.Count; /// /// Access the array of active CinemachineBrains in the Scene without generating garbage. /// /// Index of the brain to access, range 0-ActiveBrainCount. /// The brain at the specified index. public static CinemachineBrain GetActiveBrain(int index) => s_ActiveBrains[index]; // ============================ /// /// CinemachineBrain controls this GameObject. Normally, this is the GameObject to which /// the CinemachineBrain component is attached. However, it is possible to override this /// by setting this property to another GameObject. If a Camera component is attached to the /// Controlled GameObject, then that Camera component's lens settings is also driven /// by the CinemachineBrain. /// If this property is set to null, then CinemachineBrain is controlling the GameObject /// to which it is attached. The value of this property always reports as non-null. /// public GameObject ControlledObject { get => m_TargetOverride == null ? gameObject : m_TargetOverride; set { if (!ReferenceEquals(m_TargetOverride, value)) { m_TargetOverride = value; ControlledObject.TryGetComponent(out m_OutputCamera); // update output camera when target changes } } } /// /// Get the Unity Camera that is attached to this GameObject. This is the camera /// that is controlled by the CinemachineBrain. /// public Camera OutputCamera { get { if (m_OutputCamera == null && !Application.isPlaying) ControlledObject.TryGetComponent(out m_OutputCamera); return m_OutputCamera; } } /// /// Get the current active CinemachineCamera. /// public ICinemachineCamera ActiveVirtualCamera => CinemachineCore.SoloCamera ?? m_BlendManager.ActiveVirtualCamera; /// /// Call this to reset the current active camera, causing the brain to choose a new /// one without blending. It is useful, for example, when you want to restart a game level. /// public void ResetState() => m_BlendManager.ResetRootFrame(); /// /// Indicates if there is a blend in progress. /// public bool IsBlending => m_BlendManager.IsBlending; /// /// Get the current blend in progress. Returns null if none. /// It is also possible to set the current blend, but this is not a recommended usage /// unless it is to set the active blend to null, which forces the completion of the blend. /// public CinemachineBlend ActiveBlend { get => m_BlendManager.ActiveBlend; set => m_BlendManager.ActiveBlend = value; } /// /// Returns true if the CinemachineCamera is on a channel that is handled /// by this CinemachineBrain. /// /// The CinemachineCamera to check. /// True if the CinemachineCamera is on a channel that is handled by this Brain. public bool IsValidChannel(CinemachineVirtualCameraBase vcam) => vcam != null && ((uint)vcam.OutputChannel & (uint)ChannelMask) != 0; /// /// Checks if the CinemachineCamera is live as part of an outgoing blend. /// Does not check whether the CinemachineCamera is also the current active CinemachineCamera. /// /// The CinemachineCamera to check. /// True if the CinemachineCamera is part of a live outgoing blend, false otherwise. public bool IsLiveInBlend(ICinemachineCamera cam) { if (m_BlendManager.IsLiveInBlend(cam)) return true; // Walk up the parents var parent = cam.ParentCamera; if (parent != null && parent.IsLiveChild(cam, false)) return IsLiveInBlend(parent); return false; } /// /// Call this method explicitly from an external script to update the CinemachineCameras /// and position the main camera, if the UpdateMode is set to ManualUpdate. /// For other update modes, this method is called automatically, and should not be /// called from elsewhere. /// public void ManualUpdate() { m_LastFrameUpdated = Time.frameCount; float deltaTime = GetEffectiveDeltaTime(false); if (Application.isPlaying && (UpdateMethod == UpdateMethods.FixedUpdate || Time.inFixedTimeStep)) { CameraUpdateManager.s_CurrentUpdateFilter = CameraUpdateManager.UpdateFilter.Fixed; // Special handling for fixed update: cameras that have been enabled // since the last physics frame must be updated now if (BlendUpdateMethod != BrainUpdateMethods.FixedUpdate && CinemachineCore.SoloCamera == null) m_BlendManager.RefreshCurrentCameraState(DefaultWorldUp, GetEffectiveDeltaTime(true)); } else { var filter = CameraUpdateManager.UpdateFilter.Late; if (UpdateMethod == UpdateMethods.SmartUpdate) { // Track the targets UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Late, this); filter = CameraUpdateManager.UpdateFilter.SmartLate; } UpdateVirtualCameras(filter, deltaTime); } if (!Application.isPlaying || BlendUpdateMethod != BrainUpdateMethods.FixedUpdate) m_BlendManager.UpdateRootFrame(this, TopCameraFromPriorityQueue(), DefaultWorldUp, deltaTime); m_BlendManager.ComputeCurrentBlend(); // Choose the active CinemachineCamera and apply it to the Unity camera if (!Application.isPlaying || BlendUpdateMethod != BrainUpdateMethods.FixedUpdate) ProcessActiveCamera(deltaTime); } /// Called in the place of FixedUpdate void DoFixedUpdate() { if (UpdateMethod == UpdateMethods.FixedUpdate || UpdateMethod == UpdateMethods.SmartUpdate) { var filter = CameraUpdateManager.UpdateFilter.Fixed; if (UpdateMethod == UpdateMethods.SmartUpdate) { // Track the targets UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Fixed, this); filter = CameraUpdateManager.UpdateFilter.SmartFixed; } UpdateVirtualCameras(filter, GetEffectiveDeltaTime(true)); } // Choose the active CinemachineCamera and apply it to the Unity camera if (BlendUpdateMethod == BrainUpdateMethods.FixedUpdate) { m_BlendManager.UpdateRootFrame(this, TopCameraFromPriorityQueue(), DefaultWorldUp, Time.fixedDeltaTime); ProcessActiveCamera(Time.fixedDeltaTime); } } float GetEffectiveDeltaTime(bool fixedDelta) { if (CinemachineCore.UniformDeltaTimeOverride >= 0) return CinemachineCore.UniformDeltaTimeOverride; if (CinemachineCore.SoloCamera != null) return Time.unscaledDeltaTime; if (!Application.isPlaying) return m_BlendManager.GetDeltaTimeOverride(); if (IgnoreTimeScale) return fixedDelta ? Time.fixedDeltaTime : Time.unscaledDeltaTime; return fixedDelta ? Time.fixedDeltaTime : Time.deltaTime; } void UpdateVirtualCameras(CameraUpdateManager.UpdateFilter updateFilter, float deltaTime) { // We always update all active CinemachineCameras CameraUpdateManager.s_CurrentUpdateFilter = updateFilter; CameraUpdateManager.UpdateAllActiveVirtualCameras((uint)ChannelMask, DefaultWorldUp, deltaTime, this); // Make sure all live cameras get updated, in case some of them are deactivated if (CinemachineCore.SoloCamera != null) CinemachineCore.SoloCamera.UpdateCameraState(DefaultWorldUp, deltaTime); m_BlendManager.RefreshCurrentCameraState(DefaultWorldUp, deltaTime); // Restore the filter for general use updateFilter = CameraUpdateManager.UpdateFilter.Late; if (Application.isPlaying) { if (UpdateMethod == UpdateMethods.SmartUpdate) updateFilter |= CameraUpdateManager.UpdateFilter.Smart; else if (UpdateMethod == UpdateMethods.FixedUpdate) updateFilter = CameraUpdateManager.UpdateFilter.Fixed; } CameraUpdateManager.s_CurrentUpdateFilter = updateFilter; } /// /// Chooses the default active CinemachineCamera in the case there is no camera override. /// /// The highest-priority Enabled ICinemachineCamera that is in my Channel Mask. protected virtual ICinemachineCamera TopCameraFromPriorityQueue() { int numCameras = CameraUpdateManager.VirtualCameraCount; for (int i = 0; i < numCameras; ++i) { var cam = CameraUpdateManager.GetVirtualCamera(i); if (IsValidChannel(cam)) return cam; } return null; } CinemachineBlendDefinition LookupBlend(ICinemachineCamera fromKey, ICinemachineCamera toKey) => CinemachineBlenderSettings.LookupBlend(fromKey, toKey, DefaultBlend, CustomBlends, this); void ProcessActiveCamera(float deltaTime) { if (CinemachineCore.SoloCamera != null) { var state = CinemachineCore.SoloCamera.State; PushStateToUnityCamera(ref state); } else if (m_BlendManager.ProcessActiveCamera(this, DefaultWorldUp, deltaTime) != null) { // Apply the CinemachineCamera state to the Unity camera var state = m_BlendManager.CameraState; PushStateToUnityCamera(ref state); } else { // No active CinemachineCamera. We create a state representing its position // and call the callback, but we don't actively set the transform or lens var state = CameraState.Default; var target = ControlledObject.transform; state.RawPosition = target.position; state.RawOrientation = target.rotation; state.Lens = LensSettings.FromCamera(m_OutputCamera); state.BlendHint |= CameraState.BlendHints.NoTransform | CameraState.BlendHints.NoLens; PushStateToUnityCamera(ref state); } } /// Applies a to the GameOject. void PushStateToUnityCamera(ref CameraState state) { m_CameraState = state; var target = ControlledObject.transform; var pos = target.position; var rot = target.rotation; if ((state.BlendHint & CameraState.BlendHints.NoPosition) == 0) pos = state.GetFinalPosition(); if ((state.BlendHint & CameraState.BlendHints.NoOrientation) == 0) rot = state.GetFinalOrientation(); target.ConservativeSetPositionAndRotation(pos, rot); if ((state.BlendHint & CameraState.BlendHints.NoLens) == 0) { Camera cam = OutputCamera; if (cam != null) { bool isPhysical = cam.usePhysicalProperties; #if CINEMACHINE_RESET_PROJECTION_MATRIX cam.ResetProjectionMatrix(); #endif cam.nearClipPlane = state.Lens.NearClipPlane; cam.farClipPlane = state.Lens.FarClipPlane; cam.orthographicSize = state.Lens.OrthographicSize; cam.fieldOfView = state.Lens.FieldOfView; #if CINEMACHINE_RESET_PROJECTION_MATRIX if (!LensModeOverride.Enabled) cam.usePhysicalProperties = isPhysical; // because ResetProjectionMatrix resets it else #else if (LensModeOverride.Enabled) #endif { if (state.Lens.ModeOverride != LensSettings.OverrideModes.None) { isPhysical = state.Lens.IsPhysicalCamera; cam.orthographic = state.Lens.ModeOverride == LensSettings.OverrideModes.Orthographic; } else if (LensModeOverride.DefaultMode != LensSettings.OverrideModes.None) { isPhysical = LensModeOverride.DefaultMode == LensSettings.OverrideModes.Physical; cam.orthographic = LensModeOverride.DefaultMode == LensSettings.OverrideModes.Orthographic; } cam.usePhysicalProperties = isPhysical; } if (isPhysical) { cam.sensorSize = state.Lens.PhysicalProperties.SensorSize; cam.gateFit = state.Lens.PhysicalProperties.GateFit; cam.focalLength = Camera.FieldOfViewToFocalLength( state.Lens.FieldOfView, state.Lens.PhysicalProperties.SensorSize.y); cam.lensShift = state.Lens.PhysicalProperties.LensShift; cam.focusDistance = state.Lens.PhysicalProperties.FocusDistance; cam.iso = state.Lens.PhysicalProperties.Iso; cam.shutterSpeed = state.Lens.PhysicalProperties.ShutterSpeed; cam.aperture = state.Lens.PhysicalProperties.Aperture; cam.bladeCount = state.Lens.PhysicalProperties.BladeCount; cam.curvature = state.Lens.PhysicalProperties.Curvature; cam.barrelClipping = state.Lens.PhysicalProperties.BarrelClipping; cam.anamorphism = state.Lens.PhysicalProperties.Anamorphism; } } } // Send the camera updated event CinemachineCore.CameraUpdatedEvent.Invoke(this); } } }