using System;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Cinemachine
{
///
/// Describes a blend between 2 CinemachineCameras, and holds the current state of the blend.
///
public class CinemachineBlend
{
///
/// Interface for implementing custom CameraState blending algorithm
///
public interface IBlender
{
///
/// Interpolate a camera state between the two cameras being blended.
///
/// The first camera
/// The second camera
/// Range 0...1 where 0 is CamA state and 1 is CamB state
/// The interpolated state.
CameraState GetIntermediateState(ICinemachineCamera CamA, ICinemachineCamera CamB, float t);
}
/// First camera in the blend
public ICinemachineCamera CamA;
/// Second camera in the blend
public ICinemachineCamera CamB;
/// The curve that describes the way the blend transitions over time
/// from the first camera to the second. X-axis is normalized time (0...1) over which
/// the blend takes place and Y axis is blend weight (0..1)
public AnimationCurve BlendCurve;
/// The current time relative to the start of the blend
public float TimeInBlend;
/// The current weight of the blend. This is an evaluation of the
/// BlendCurve at the current time relative to the start of the blend.
/// 0 means camA, 1 means camB.
public float BlendWeight
{
get
{
if (BlendCurve == null || BlendCurve.length < 2 || IsComplete)
return 1;
return Mathf.Clamp01(BlendCurve.Evaluate(TimeInBlend / Duration));
}
}
///
/// If non-null, the custom blender will be used to blend camera state.
/// If null, then CameraState.Lerp will be used.
///
public IBlender CustomBlender { get; set; }
/// Validity test for the blend. True if either camera is defined.
public bool IsValid => (CamA != null && CamA.IsValid) || (CamB != null && CamB.IsValid);
/// Duration in seconds of the blend.
public float Duration;
/// True if the time relative to the start of the blend is greater
/// than or equal to the blend duration
public bool IsComplete => TimeInBlend >= Duration || !IsValid;
/// Text description of the blend, for debugging
public string Description
{
get
{
if (CamB == null || !CamB.IsValid)
return "(none)";
var sb = CinemachineDebug.SBFromPool();
sb.Append(CamB.Name);
sb.Append(" ");
sb.Append((int)(BlendWeight * 100f));
sb.Append("% from ");
if (CamA == null || !CamA.IsValid)
sb.Append("(none)");
else
sb.Append(CamA.Name);
string text = sb.ToString();
CinemachineDebug.ReturnToPool(sb);
return text;
}
}
/// Does the blend use a specific CinemachineCamera?
/// The camera to test
/// True if the camera is involved in the blend
public bool Uses(ICinemachineCamera cam)
{
if (cam == null)
return false;
if (cam == CamA || cam == CamB)
return true;
if (CamA is NestedBlendSource b && b.Blend.Uses(cam))
return true;
b = CamB as NestedBlendSource;
return b != null && b.Blend.Uses(cam);
}
/// Copy contents of a blend
/// Copy fields from this blend
public void CopyFrom(CinemachineBlend src)
{
CamA = src.CamA;
CamB = src.CamB;
BlendCurve = src.BlendCurve;
TimeInBlend = src.TimeInBlend;
Duration = src.Duration;
CustomBlender = src.CustomBlender;
}
///
/// Clears all fields except CamB. This effectively cuts to the end of the blend
///
public void ClearBlend()
{
CamA = null;
BlendCurve = null;
TimeInBlend = Duration = 0;
CustomBlender = null;
}
/// Make sure the source cameras get updated.
/// Default world up. Individual vcams may modify this
/// Time increment used for calculating time-based behaviours (e.g. damping)
public void UpdateCameraState(Vector3 worldUp, float deltaTime)
{
// Make sure both cameras have been updated (they are not necessarily
// enabled, and only enabled cameras get updated automatically
// every frame)
if (CamA != null && CamA.IsValid)
CamA.UpdateCameraState(worldUp, deltaTime);
if (CamB != null && CamB.IsValid)
CamB.UpdateCameraState(worldUp, deltaTime);
}
/// Compute the blended CameraState for the current time in the blend.
public CameraState State
{
get
{
if (CamA == null || !CamA.IsValid)
{
if (CamB == null || !CamB.IsValid)
return CameraState.Default;
return CamB.State;
}
if (CamB == null || !CamB.IsValid)
return CamA.State;
if (CustomBlender != null)
return CustomBlender.GetIntermediateState(CamA, CamB, BlendWeight);
return CameraState.Lerp(CamA.State, CamB.State, BlendWeight);
}
}
}
/// Definition of a Camera blend. This struct holds the information
/// necessary to generate a suitable AnimationCurve for a Cinemachine Blend.
[Serializable]
public struct CinemachineBlendDefinition
{
///
/// Delegate for finding a blend definition to use when blending between 2 cameras.
///
/// The outgoing camera
/// The incoming camera
/// An appropriate blend definition,. Must not be null.
public delegate CinemachineBlendDefinition LookupBlendDelegate(
ICinemachineCamera outgoing, ICinemachineCamera incoming);
/// Supported predefined shapes for the blend curve.
public enum Styles
{
/// Zero-length blend
Cut,
/// S-shaped curve, giving a gentle and smooth transition
EaseInOut,
/// Linear out of the outgoing shot, and easy into the incoming
EaseIn,
/// Easy out of the outgoing shot, and linear into the incoming
EaseOut,
/// Easy out of the outgoing, and hard into the incoming
HardIn,
/// Hard out of the outgoing, and easy into the incoming
HardOut,
/// Linear blend. Mechanical-looking.
Linear,
/// Custom blend curve.
Custom
};
/// The shape of the blend curve.
[Tooltip("Shape of the blend curve")]
[FormerlySerializedAs("m_Style")]
public Styles Style;
/// The duration (in seconds) of the blend, if not a cut.
/// If style is a cut, then this value is ignored.
[Tooltip("Duration of the blend, in seconds")]
[FormerlySerializedAs("m_Time")]
public float Time;
///
/// Get the duration of the blend, in seconds. Will return 0 if blend style is a cut.
///
public float BlendTime => Style == Styles.Cut ? 0 : Time;
/// Constructor
/// The shape of the blend curve.
/// The duration (in seconds) of the blend
public CinemachineBlendDefinition(Styles style, float time)
{
Style = style;
Time = time;
CustomCurve = null;
}
///
/// A user-defined AnimationCurve, used only if style is Custom.
/// Curve MUST be normalized, i.e. time range [0...1], value range [0...1].
///
[FormerlySerializedAs("m_CustomCurve")]
public AnimationCurve CustomCurve;
static AnimationCurve[] s_StandardCurves;
void CreateStandardCurves()
{
s_StandardCurves = new AnimationCurve[(int)Styles.Custom];
s_StandardCurves[(int)Styles.Cut] = null;
s_StandardCurves[(int)Styles.EaseInOut] = AnimationCurve.EaseInOut(0f, 0f, 1, 1f);
s_StandardCurves[(int)Styles.EaseIn] = AnimationCurve.Linear(0f, 0f, 1, 1f);
Keyframe[] keys = s_StandardCurves[(int)Styles.EaseIn].keys;
keys[0].outTangent = 1.4f;
keys[1].inTangent = 0;
s_StandardCurves[(int)Styles.EaseIn].keys = keys;
s_StandardCurves[(int)Styles.EaseOut] = AnimationCurve.Linear(0f, 0f, 1, 1f);
keys = s_StandardCurves[(int)Styles.EaseOut].keys;
keys[0].outTangent = 0;
keys[1].inTangent = 1.4f;
s_StandardCurves[(int)Styles.EaseOut].keys = keys;
s_StandardCurves[(int)Styles.HardIn] = AnimationCurve.Linear(0f, 0f, 1, 1f);
keys = s_StandardCurves[(int)Styles.HardIn].keys;
keys[0].outTangent = 0;
keys[1].inTangent = 3f;
s_StandardCurves[(int)Styles.HardIn].keys = keys;
s_StandardCurves[(int)Styles.HardOut] = AnimationCurve.Linear(0f, 0f, 1, 1f);
keys = s_StandardCurves[(int)Styles.HardOut].keys;
keys[0].outTangent = 3f;
keys[1].inTangent = 0;
s_StandardCurves[(int)Styles.HardOut].keys = keys;
s_StandardCurves[(int)Styles.Linear] = AnimationCurve.Linear(0f, 0f, 1, 1f);
}
///
/// A normalized AnimationCurve specifying the interpolation curve
/// for this camera blend. Y-axis values must be in range [0,1] (internally clamped
/// within Blender) and time must be in range of [0, 1].
///
public AnimationCurve BlendCurve
{
get
{
if (Style == Styles.Custom)
{
CustomCurve ??= AnimationCurve.EaseInOut(0f, 0f, 1, 1f);
return CustomCurve;
}
if (s_StandardCurves == null)
CreateStandardCurves();
return s_StandardCurves[(int)Style];
}
}
}
///
/// Blend result source for blending. This exposes a CinemachineBlend object
/// as an ersatz virtual camera for the purposes of blending. This achieves the purpose
/// of blending the result oif a blend.
///
public class NestedBlendSource : ICinemachineCamera
{
string m_Name;
/// Contructor to wrap a CinemachineBlend object
/// The blend to wrap.
public NestedBlendSource(CinemachineBlend blend) { Blend = blend; }
/// The CinemachineBlend object being wrapped.
public CinemachineBlend Blend { get; internal set; }
///
public string Name
{
get
{
// Cache the name only if name is requested
m_Name ??= (Blend == null || Blend.CamB == null)? "(null)" : "mid-blend to " + Blend.CamB.Name;
return m_Name;
}
}
///
public string Description => Blend == null ? "(null)" : Blend.Description;
///
public CameraState State { get; private set; }
///
public bool IsValid => Blend != null && Blend.IsValid;
///
public ICinemachineMixer ParentCamera => null;
///
public void UpdateCameraState(Vector3 worldUp, float deltaTime)
{
if (Blend != null)
{
Blend.UpdateCameraState(worldUp, deltaTime);
State = Blend.State;
}
}
///
public void OnCameraActivated(ICinemachineCamera.ActivationEventParams evt) {}
}
}