using UnityEngine; using UnityEngine.Serialization; using UnityEngine.Splines; namespace Unity.Cinemachine { /// /// This is a very simple behaviour that constrains its transform to a Spline. /// It can be used to animate any objects along a path, or as a tracking target for /// Cinemachine Cameras. /// [ExecuteAlways] [DisallowMultipleComponent] [AddComponentMenu("Cinemachine/Helpers/Cinemachine Spline Cart")] [HelpURL(Documentation.BaseURL + "manual/CinemachineSplineCart.html")] public class CinemachineSplineCart : MonoBehaviour { /// /// Holds the Spline container, the spline position, and the position unit type /// public SplineSettings SplineSettings = new () { Units = PathIndexUnit.Normalized }; /// This enum defines the options available for the update method. public enum UpdateMethods { /// Updated in normal MonoBehaviour Update. Update, /// Updated in sync with the Physics module, in FixedUpdate FixedUpdate, /// Updated in normal MonoBehaviour LateUpdate LateUpdate }; /// When to move the cart, if Speed is non-zero [Tooltip("When to move the cart, if Speed is non-zero")] public UpdateMethods UpdateMethod = UpdateMethods.Update; /// Controls how automatic dollying occurs [FoldoutWithEnabledButton] [Tooltip("Controls how automatic dollying occurs. A tracking target may be necessary to use this feature.")] public SplineAutoDolly AutomaticDolly; /// Used only by Automatic Dolly settings that require it [Tooltip("Used only by Automatic Dolly settings that require it")] public Transform TrackingTarget; /// The Spline container to which the cart will be constrained. public SplineContainer Spline { get => SplineSettings.Spline; set => SplineSettings.Spline = value; } /// The cart's current position on the spline, in spline position units public float SplinePosition { get => SplineSettings.Position; set => SplineSettings.Position = value; } /// How to interpret PositionOnSpline: /// - Distance: Values range from 0 (start of Spline) to Length of the Spline (end of Spline). /// - Normalized: Values range from 0 (start of Spline) to 1 (end of Spline). /// - Knot: Values are defined by knot indices and a fractional value representing the normalized /// interpolation between the specific knot index and the next knot." public PathIndexUnit PositionUnits { get => SplineSettings.Units; set => SplineSettings.ChangeUnitPreservePosition(value); } CinemachineSplineRoll m_RollCache; // don't use this directly - use SplineRoll // In-editor only: CM 3.0.x Legacy support ================================= [SerializeField, HideInInspector, FormerlySerializedAs("SplinePosition")] private float m_LegacyPosition = -1; [SerializeField, HideInInspector, FormerlySerializedAs("PositionUnits")] private PathIndexUnit m_LegacyUnits; [SerializeField, HideInInspector, FormerlySerializedAs("Spline")] private SplineContainer m_LegacySpline; void PerformLegacyUpgrade() { if (m_LegacyPosition != -1) { SplineSettings.Position = m_LegacyPosition; SplineSettings.Units = m_LegacyUnits; m_LegacyPosition = -1; m_LegacyUnits = 0; } if (m_LegacySpline != null) { SplineSettings.Spline = m_LegacySpline; m_LegacySpline = null; } } // ================================= private void OnValidate() { PerformLegacyUpgrade(); // only called in-editor AutomaticDolly.Method?.Validate(); } void Reset() { SplineSettings = new SplineSettings { Units = PathIndexUnit.Normalized }; UpdateMethod = UpdateMethods.Update; AutomaticDolly.Method = null; TrackingTarget = null; } void OnEnable() { RefreshRollCache(); AutomaticDolly.Method?.Reset(); } void FixedUpdate() { if (UpdateMethod == UpdateMethods.FixedUpdate) UpdateCartPosition(); } void Update() { if (!Application.isPlaying) SetCartPosition(SplinePosition); else if (UpdateMethod == UpdateMethods.Update) UpdateCartPosition(); } void LateUpdate() { if (!Application.isPlaying) SetCartPosition(SplinePosition); else if (UpdateMethod == UpdateMethods.LateUpdate) UpdateCartPosition(); } void UpdateCartPosition() { if (AutomaticDolly.Enabled && AutomaticDolly.Method != null) SplinePosition = AutomaticDolly.Method.GetSplinePosition( this, TrackingTarget, Spline, SplinePosition, PositionUnits, Time.deltaTime); SetCartPosition(SplinePosition); } void SetCartPosition(float distanceAlongPath) { if (Spline.IsValid()) { SplinePosition = Spline.Spline.StandardizePosition(distanceAlongPath, PositionUnits, Spline.Spline.GetLength()); var t = Spline.Spline.ConvertIndexUnit(SplinePosition, PositionUnits, PathIndexUnit.Normalized); Spline.EvaluateSplineWithRoll(SplineRoll, transform.rotation, t, out var pos, out var rot); transform.ConservativeSetPositionAndRotation(pos, rot); } } CinemachineSplineRoll SplineRoll { get { #if UNITY_EDITOR if (!Application.isPlaying) RefreshRollCache(); #endif return m_RollCache; } } void RefreshRollCache() { // check if we have CinemachineSplineRoll TryGetComponent(out m_RollCache); #if UNITY_EDITOR // need to tell CinemachineSplineRoll about its spline for gizmo drawing purposes if (m_RollCache != null) m_RollCache.Container = Spline; #endif // check if our spline has CinemachineSplineRoll if (Spline != null && m_RollCache == null) Spline.TryGetComponent(out m_RollCache); } } }