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);
}
}
}
}