using UnityEngine; using System; using System.Collections.Generic; namespace Unity.Cinemachine { /// /// This is an add-on for CinemachineCameras containing the OrbitalFollow component. /// It modifies the camera distance as a function of vertical angle. /// [SaveDuringPlay] [AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine FreeLook Modifier")] // Hide in menu [ExecuteAlways] [DisallowMultipleComponent] [HelpURL(Documentation.BaseURL + "manual/CinemachineFreeLookModifier.html")] public class CinemachineFreeLookModifier : CinemachineExtension { /// /// Interface for CinemachineComponentBase-derived to expose a normalized value that /// can be consumed by CinemachineFreeLookModifier to drive the rig selection. /// public interface IModifierValueSource { /// /// This value will be 0 for the middle rig, -1 for the bottom rid, and 1 for the top rig. /// Values in-between represent a blend between rigs. /// float NormalizedModifierValue { get; } } /// /// Interface for CinemachineComponentBase-derived to allow its position damping to be driven. /// public interface IModifiablePositionDamping { /// Get/Set the position damping value Vector3 PositionDamping { get; set; } } /// /// Interface for CinemachineComponentBase-derived to allow its screen composition to be driven /// public interface IModifiableComposition { /// Get/set the screen position ScreenComposerSettings Composition { get; set; } } /// /// Interface for CinemachineComponentBase-derived to allow the camera distance to be modified /// public interface IModifiableDistance { /// Get/set the camera distance float Distance { get; set; } } /// /// Interface for CinemachineComponentBase-derived to allow the noise amplitude and frequency to be modified /// public interface IModifiableNoise { /// Get/set the noise amplitude and frequency (float, float) NoiseAmplitudeFrequency { get; set; } } /// /// Interface for an object that will modify some aspect of a FreeLook camera /// based on the vertical axis value. /// [Serializable] public abstract class Modifier { /// Called from OnValidate in the editor. Validate and sanitize the fields. /// the virtual camera owner public virtual void Validate(CinemachineVirtualCameraBase vcam) {} /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public virtual void Reset(CinemachineVirtualCameraBase vcam) {} /// Type of the cached component (null if no cached component). If this modifier targets /// specific components, this value indicates the type of that component. /// The modifier should cache the component, for performance. /// for a base class for implementing this. public virtual Type CachedComponentType => null; /// Return true if cached vcam component is present or not required public virtual bool HasRequiredComponent => true; /// Called from OnEnable and from the inspector. Refresh any performance-sensitive stuff. /// the virtual camera owner public virtual void RefreshCache(CinemachineVirtualCameraBase vcam) {} /// /// Called from extension's PrePipelineMutateCameraState(). Perform any necessary actions to /// modify relevant camera settings. Original camera settings should be restored in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public virtual void BeforePipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) {} /// /// Called from extension's PostPipelineStageCallback(Finalize). Perform any necessary actions to state, /// and restore any camera parameters changed in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public virtual void AfterPipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) {} } /// /// Modifier for things inside a single CinemachineComponentBase. /// /// public abstract class ComponentModifier : Modifier { /// The CinemachineComponentBase that will be modified. Cached here for efficiency. protected T CachedComponent; /// True if the CinemachineCamera has the component we intend to modify. public override bool HasRequiredComponent => CachedComponent != null; /// The type of the component being modified public override Type CachedComponentType => typeof(T); /// Called from OnEnable and from the inspector. Refreshes CachedComponent. /// the virtual camera owner public override void RefreshCache(CinemachineVirtualCameraBase vcam) => TryGetVcamComponent(vcam, out CachedComponent); } /// /// Builtin FreeLook modifier for camera tilt. Applies a vertical rotation to the camera /// at the end of the camera pipeline. /// public class TiltModifier : Modifier { /// Values for the top and bottom rigs [HideFoldout] public TopBottomRigs Tilt; /// Called from OnValidate to validate this component /// the virtual camera owner public override void Validate(CinemachineVirtualCameraBase vcam) { Tilt.Top = Mathf.Clamp(Tilt.Top, -30, 30); Tilt.Bottom = Mathf.Clamp(Tilt.Bottom, -30, 30); } /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public override void Reset(CinemachineVirtualCameraBase vcam) => Tilt = new TopBottomRigs() { Top = -5, Bottom = 5 }; /// /// Called from extension's PostPipelineStageCallback(Finalize). Perform any necessary actions to state, /// and restore any camera parameters changed in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void AfterPipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { float tilt = modifierValue > 0 ? Mathf.Lerp(0, Tilt.Top, modifierValue) : Mathf.Lerp(Tilt.Bottom, 0, modifierValue + 1); // Tilt in local X var qTilted = state.RawOrientation * Quaternion.AngleAxis(tilt, Vector3.right); state.OrientationCorrection = Quaternion.Inverse(state.GetCorrectedOrientation()) * qTilted; } } /// /// Builtin modifier for camera lens. Applies the lens at the start of the camera pipeline. /// public class LensModifier : Modifier { /// Values for the top and bottom rigs /// Settings for top orbit [Tooltip("Value to take at the top of the axis range")] [LensSettingsHideModeOverrideProperty] public LensSettings Top; /// Settings for bottom orbit [Tooltip("Value to take at the bottom of the axis range")] [LensSettingsHideModeOverrideProperty] public LensSettings Bottom; /// Called from OnValidate to validate this component /// the virtual camera owner public override void Validate(CinemachineVirtualCameraBase vcam) { Top.Validate(); Bottom.Validate(); } /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public override void Reset(CinemachineVirtualCameraBase vcam) { if (vcam == null) Top = Bottom = LensSettings.Default; else { var state = vcam.State; Top = Bottom = state.Lens; Top.CopyCameraMode(ref state.Lens); Bottom.CopyCameraMode(ref state.Lens); } } /// /// Called from extension's PrePipelineMutateCameraState(). Perform any necessary actions to /// modify relevant camera settings. Original camera settings should be restored in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void BeforePipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { Top.CopyCameraMode(ref state.Lens); Bottom.CopyCameraMode(ref state.Lens); if (modifierValue >= 0) state.Lens.Lerp(Top, modifierValue); else state.Lens.Lerp(Bottom, -modifierValue); } } /// /// Builtin FreeLook modifier for positional damping. Modifies positional damping at the start of the camera pipeline. /// public class PositionDampingModifier : ComponentModifier { /// Values for the top and bottom rigs [HideFoldout] public TopBottomRigs Damping; /// Called from OnValidate to validate this component /// the virtual camera owner public override void Validate(CinemachineVirtualCameraBase vcam) { Damping.Top = new Vector3(Mathf.Max(0, Damping.Top.x), Mathf.Max(0, Damping.Top.y), Mathf.Max(0, Damping.Top.z)); Damping.Bottom = new Vector3(Mathf.Max(0, Damping.Bottom.x), Mathf.Max(0, Damping.Bottom.y), Mathf.Max(0, Damping.Bottom.z)); } /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public override void Reset(CinemachineVirtualCameraBase vcam) { if (CachedComponent != null) Damping.Top = Damping.Bottom = CachedComponent.PositionDamping; } Vector3 m_CenterDamping; /// /// Called from extension's PrePipelineMutateCameraState(). Perform any necessary actions to /// modify relevant camera settings. Original camera settings should be restored in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void BeforePipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { if (CachedComponent != null) { m_CenterDamping = CachedComponent.PositionDamping; CachedComponent.PositionDamping = modifierValue >= 0 ? Vector3.Lerp(m_CenterDamping, Damping.Top, modifierValue) : Vector3.Lerp(Damping.Bottom, m_CenterDamping, modifierValue + 1); } } /// /// Called from extension's PostPipelineStageCallback(Finalize). Perform any necessary actions to state, /// and restore any camera parameters changed in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void AfterPipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { // Restore the settings if (CachedComponent != null) CachedComponent.PositionDamping = m_CenterDamping; } } /// /// Builtin Freelook modifier for screen composition. Modifies composition at the start of the camera pipeline. /// public class CompositionModifier : ComponentModifier { /// Values for the top and bottom rigs [HideFoldout] public TopBottomRigs Composition; /// Called from OnValidate to validate this component /// the virtual camera owner public override void Validate(CinemachineVirtualCameraBase vcam) { Composition.Top.Validate(); Composition.Bottom.Validate(); } /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public override void Reset(CinemachineVirtualCameraBase vcam) { if (CachedComponent != null) Composition.Top = Composition.Bottom = CachedComponent.Composition; } ScreenComposerSettings m_SavedComposition; /// /// Called from extension's PrePipelineMutateCameraState(). Perform any necessary actions to /// modify relevant camera settings. Original camera settings should be restored in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void BeforePipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { if (CachedComponent != null) { m_SavedComposition = CachedComponent.Composition; CachedComponent.Composition = modifierValue >= 0 ? ScreenComposerSettings.Lerp(m_SavedComposition, Composition.Top, modifierValue) : ScreenComposerSettings.Lerp(Composition.Bottom, m_SavedComposition, modifierValue + 1); } } /// /// Called from extension's PostPipelineStageCallback(Finalize). Perform any necessary actions to state, /// and restore any camera parameters changed in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void AfterPipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { // Restore the settings if (CachedComponent != null) { CachedComponent.Composition = m_SavedComposition; } } } /// /// Builtin FreeLook modifier for camera distance. Applies distance to the camera at the start of the camera pipeline. /// public class DistanceModifier : ComponentModifier { /// Values for the top and bottom rigs [HideFoldout] public TopBottomRigs Distance; /// Called from OnValidate to validate this component /// the virtual camera owner public override void Validate(CinemachineVirtualCameraBase vcam) { Distance.Top = Mathf.Max(0, Distance.Top); Distance.Bottom = Mathf.Max(0, Distance.Bottom); } /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public override void Reset(CinemachineVirtualCameraBase vcam) { if (CachedComponent != null) Distance.Top = Distance.Bottom = CachedComponent.Distance; } float m_CenterDistance; /// /// Called from extension's PrePipelineMutateCameraState(). Perform any necessary actions to /// modify relevant camera settings. Original camera settings should be restored in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void BeforePipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { if (CachedComponent != null) { m_CenterDistance = CachedComponent.Distance; CachedComponent.Distance = modifierValue >= 0 ? Mathf.Lerp(m_CenterDistance, Distance.Top, modifierValue) : Mathf.Lerp(Distance.Bottom, m_CenterDistance, modifierValue + 1); } } /// /// Called from extension's PostPipelineStageCallback(Finalize). Perform any necessary actions to state, /// and restore any camera parameters changed in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void AfterPipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { // Restore the settings if (CachedComponent != null) CachedComponent.Distance = m_CenterDistance; } } /// /// Builtin modifier for noise components such as . /// Applies scaling to amplitude and frequency. /// public class NoiseModifier : ComponentModifier { /// /// Settings to apply to the IModifiableNoise component /// [Serializable] public struct NoiseSettings { /// Multiplier for the noise amplitude [Tooltip("Multiplier for the noise amplitude")] public float Amplitude; /// Multiplier for the noise frequency [Tooltip("Multiplier for the noise frequency")] public float Frequency; } /// Values for the top and bottom rigs [HideFoldout] public TopBottomRigs Noise; (float, float) m_CenterNoise; /// Called when the modifier is created. Initialize fields with appropriate values. /// the virtual camera owner public override void Reset(CinemachineVirtualCameraBase vcam) { if (CachedComponent != null) { var value = CachedComponent.NoiseAmplitudeFrequency; Noise.Top = Noise.Bottom = new NoiseSettings { Amplitude = value.Item1, Frequency = value.Item2 }; } } /// /// Called from extension's PrePipelineMutateCameraState(). Perform any necessary actions to /// modify relevant camera settings. Original camera settings should be restored in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void BeforePipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { if (CachedComponent != null) { m_CenterNoise = CachedComponent.NoiseAmplitudeFrequency; if (modifierValue >= 0) CachedComponent.NoiseAmplitudeFrequency = ( Mathf.Lerp(m_CenterNoise.Item1, Noise.Top.Amplitude, modifierValue), Mathf.Lerp(m_CenterNoise.Item2, Noise.Top.Frequency, modifierValue)); else CachedComponent.NoiseAmplitudeFrequency = ( Mathf.Lerp(Noise.Bottom.Amplitude, m_CenterNoise.Item1, modifierValue + 1), Mathf.Lerp(Noise.Bottom.Frequency, m_CenterNoise.Item2, modifierValue + 1)); } } /// /// Called from extension's PostPipelineStageCallback(Finalize). Perform any necessary actions to state, /// and restore any camera parameters changed in . /// /// vcam owner /// current vcam state. May be modified in this function /// current applicable deltaTime /// The normalized value of the modifier variable. /// This is the FreeLook's vertical axis. /// Ranges from -1 to 1, where 0 is center rig. public override void AfterPipeline( CinemachineVirtualCameraBase vcam, ref CameraState state, float deltaTime, float modifierValue) { // Restore the settings if (CachedComponent != null) CachedComponent.NoiseAmplitudeFrequency = m_CenterNoise; } } /// /// Helper struct to hold settings for Top, Middle, and Bottom orbits. /// /// [Serializable] public struct TopBottomRigs { /// Settings for top orbit [Tooltip("Value to take at the top of the axis range")] public T Top; /// Settings for bottom orbit [Tooltip("Value to take at the bottom of the axis range")] public T Bottom; } /// /// Collection of modifiers that will be applied to the camera every frame. /// These will modify settings as a function of the FreeLook's Vertical axis value. /// [Tooltip("These will modify settings as a function of the FreeLook's Vertical axis value")] [SerializeReference] [NoSaveDuringPlay] public List Modifiers = new (); IModifierValueSource m_ValueSource; float m_CurrentValue; static AnimationCurve s_EasingCurve; void OnValidate() { var vcam = ComponentOwner; for (int i = 0; i < Modifiers.Count; ++i) Modifiers[i]?.Validate(vcam); } /// Called when component is enabled protected override void OnEnable() { base.OnEnable(); RefreshComponentCache(); } // GML todo: clean this up static void TryGetVcamComponent(CinemachineVirtualCameraBase vcam, out T component) { if (vcam == null || !vcam.TryGetComponent(out component)) component = default; } void RefreshComponentCache() { var vcam = ComponentOwner; TryGetVcamComponent(vcam, out m_ValueSource); for (int i = 0; i < Modifiers.Count; ++i) Modifiers[i]?.RefreshCache(vcam); } // Needed by inspector internal bool HasValueSource() { RefreshComponentCache(); return m_ValueSource != null; } /// Override this to do such things as offset the ReferenceLookAt. /// Base class implementation does nothing. /// The virtual camera being processed /// Input state that must be mutated /// The current applicable deltaTime public override void PrePipelineMutateCameraStateCallback( CinemachineVirtualCameraBase vcam, ref CameraState curState, float deltaTime) { if (m_ValueSource != null && vcam == ComponentOwner) { // Apply easing if (s_EasingCurve == null) { s_EasingCurve = AnimationCurve.Linear(0f, 0f, 1, 1f); #if false // nah, it doesn't look so great // GML todo: find a nice way to amke a smooth curve. Maybe a bezier? // Ease out, hard in var keys = s_EasingCurve.keys; keys[0].outTangent = 0; keys[1].inTangent = 1.4f; s_EasingCurve.keys = keys; #endif } var v = m_ValueSource.NormalizedModifierValue; var sign = Mathf.Sign(v); m_CurrentValue = sign * s_EasingCurve.Evaluate(sign * v); for (int i = 0; i < Modifiers.Count; ++i) Modifiers[i]?.BeforePipeline(vcam, ref curState, deltaTime, m_CurrentValue); } } /// /// Callback to perform the requested rig modifications. /// /// The virtual camera being processed /// The current pipeline stage /// The current virtual camera state /// The current applicable deltaTime protected override void PostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime) { if (m_ValueSource != null && stage == CinemachineCore.Stage.Finalize && vcam == ComponentOwner) { for (int i = 0; i < Modifiers.Count; ++i) Modifiers[i]?.AfterPipeline(vcam, ref state, deltaTime, m_CurrentValue); } } } }