Files

180 lines
7.8 KiB
C#

using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine
{
/// <summary>
/// Extension that can be added to a SplineContainer or a CinemachineCamera that uses a SplineContainer, for example a CinemachineCamera
/// that has SplineDolly as Body component.
/// - When CinemachineSplineRoll is added to a gameObject that has SplineContainer,
/// then the roll affects any CinemachineCamera that reads that SplineContainer globally.
/// - When CinemachineSplineRoll is added to a CinemachineCamera, then roll only affects that CinemachineCamera locally.
/// </summary>
[ExecuteInEditMode]
[DisallowMultipleComponent]
[AddComponentMenu("Cinemachine/Helpers/Cinemachine Spline Roll")]
[HelpURL(Documentation.BaseURL + "manual/CinemachineSplineRoll.html")]
[SaveDuringPlay]
public class CinemachineSplineRoll : MonoBehaviour, ISerializationCallbackReceiver
{
/// <summary>Structure to hold roll value for a specific location on the track.</summary>
[Serializable]
public struct RollData
{
/// <summary>
/// Roll (in degrees) around the forward direction for specific location on the track.
/// When placed on a SplineContainer, this is going to be a global override that affects all vcams using the Spline.
/// When placed on a CinemachineCamera, this is going to be a local override that only affects that CinemachineCamera.
/// </summary>
[Tooltip("Roll (in degrees) around the forward direction for specific location on the track.\n" +
"- When placed on a SplineContainer, this is going to be a global override that affects all vcams using the Spline.\n" +
"- When placed on a CinemachineCamera, this is going to be a local override that only affects that CinemachineCamera.")]
public float Value;
/// <summary> Implicit conversion to float </summary>
/// <param name="roll"> The RollData setting to convert. </param>
/// <returns> The value of the RollData setting. </returns>
public static implicit operator float(RollData roll) => roll.Value;
/// <summary> Implicit conversion from float </summary>
/// <param name="roll"> The value with which to initialize the RollData setting. </param>
/// <returns>A new RollData setting with the given value. </returns>
public static implicit operator RollData(float roll) => new () { Value = roll };
}
/// <summary>
/// When enabled, roll eases into and out of the data point values. Otherwise, interpolation is linear.
/// </summary>
[Tooltip("When enabled, roll eases into and out of the data point values. Otherwise, interpolation is linear.")]
public bool Easing = true;
/// <summary>
/// Get the appropriate interpolator for the RollData, depending on the Easing setting
/// </summary>
/// <returns>The appropriate interpolator for the RollData, depending on the Easing setting.</returns>
public IInterpolator<RollData> GetInterpolator() => Easing ? new LerpRollDataWithEasing() : new LerpRollData();
/// <summary>Interpolator for the RollData, with no easing between data points.</summary>
public struct LerpRollData : IInterpolator<RollData>
{
/// <inheritdoc/>
public RollData Interpolate(RollData a, RollData b, float t) => new() { Value = Mathf.Lerp(a.Value, b.Value, t) };
}
/// <summary>Interpolator for the RollData, with easing between data points</summary>
public struct LerpRollDataWithEasing : IInterpolator<RollData>
{
/// <inheritdoc/>
public RollData Interpolate(RollData a, RollData b, float t)
{
var t2 = t * t;
var d = 1f - t;
t = 3f * d * t2 + t * t2;
return new() { Value = Mathf.Lerp(a.Value, b.Value, t) };
}
}
/// <summary>
/// Roll (in degrees) around the forward direction for specific location on the track.
/// When placed on a SplineContainer, this is going to be a global override that affects all vcams using the Spline.
/// When placed on a CinemachineCamera, this is going to be a local override that only affects that CinemachineCamera.
/// </summary>
/// <remarks>
/// It is not recommended to modify the data array at runtime, because the infrastructure
/// expects the array to be in strclty increasing order of distance along the spline. If you do change
/// the array at runtime, you must take care to keep it in this order, or the results will be unpredictable.
/// </remarks>
[HideFoldout]
public SplineData<RollData> Roll;
#if UNITY_EDITOR
// Only needed for drawing the gizmo
internal ISplineContainer SplineContainer
{
get
{
// In case behaviour was re-parented in the editor, we check every time
if (TryGetComponent(out ISplineReferencer referencer))
return referencer.SplineSettings.Spline == null || referencer.SplineSettings.Spline.Splines.Count == 0
? null : referencer.SplineSettings.Spline;
if (TryGetComponent(out ISplineContainer container))
return container.Splines.Count > 0 ? container : null;
return null;
}
}
#endif
//============================================================================
// Legacy streaming support
[HideInInspector, SerializeField, NoSaveDuringPlay]
int m_StreamingVersion;
void PerformLegacyUpgrade(int streamedVersion)
{
if (streamedVersion < 20240101)
{
// roll values were inverted
for (int i = 0; i < Roll.Count; ++i)
{
var item = Roll[i];
item.Value = -item.Value;
Roll[i] = item;
}
}
}
//============================================================================
void Reset()
{
Roll?.Clear();
Easing = true;
}
void OnEnable() {} // Needed so we can disable it in the editor
/// <inheritdoc/>
public void OnBeforeSerialize() {}
/// <inheritdoc/>
public void OnAfterDeserialize()
{
// Perform legacy upgrade if necessary
if (m_StreamingVersion < CinemachineCore.kStreamingVersion)
PerformLegacyUpgrade(m_StreamingVersion);
m_StreamingVersion = CinemachineCore.kStreamingVersion;
}
/// <summary>Cache for clients that use CinemachineSplineRoll</summary>
internal struct RollCache
{
CinemachineSplineRoll m_RollCache;
public void Refresh(MonoBehaviour owner)
{
m_RollCache = null;
// Check if owner has CinemachineSplineRoll
if (!owner.TryGetComponent(out m_RollCache) && owner is ISplineReferencer referencer)
{
// Check if the spline has CinemachineSplineRoll
var spline = referencer.SplineSettings.Spline;
if (spline != null && spline is Component component)
component.TryGetComponent(out m_RollCache);
}
}
public CinemachineSplineRoll GetSplineRoll(MonoBehaviour owner)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Refresh(owner);
#endif
return m_RollCache;
}
}
}
}