using System; using UnityEngine; using UnityEngine.Splines; namespace Unity.Cinemachine { /// /// This structure holds the object that implements AutoDolly on a spline. /// [Serializable] public struct SplineAutoDolly { /// If set, will enable AutoDolly on a spline [Tooltip("If set, will enable the selected automatic dolly along the spline")] public bool Enabled; /// This is the object that actually implements the AutoDolly [SerializeReference] public ISplineAutoDolly Method; /// /// Interface for procedural spline dolly. /// Implement this to provide a custom algorithm for choosing a point on the path. /// public interface ISplineAutoDolly { /// Called from OnValidate() to validate the settings. void Validate(); /// Call this to reset any state information contained in the implementation. void Reset(); /// Returns true if this implementation requires a tracking target. bool RequiresTrackingTarget { get; } /// /// Compute the desired position on the spline. /// /// The MonoBehaviour that is asking. /// The target object (may be null for algorithms that don't require it). /// The spline on which the location must be found. /// The current position on the spline. /// The units in which spline positions are expressed. /// Current deltaTime. If smaller than 0, then previous frame data should be ignored. /// The desired position on the spline, expressed in positionUnits. float GetSplinePosition( MonoBehaviour sender, Transform target, SplineContainer spline, float currentPosition, PathIndexUnit positionUnits, float deltaTime); } /// /// ISplineAutoDolly implementation that moves the object at a constant speed align the spline. /// [Serializable] public class FixedSpeed : ISplineAutoDolly { /// Speed of travel, in current position units per second. [Tooltip("Speed of travel, in current position units per second.")] public float Speed; /// Called from OnValidate() to validate the settings. void ISplineAutoDolly.Validate() {} /// This implementation does nothing. void ISplineAutoDolly.Reset() {} /// Returns true if this implementation requires a tracking target. bool ISplineAutoDolly.RequiresTrackingTarget => false; /// /// Compute the desired position on the spline. /// /// The MonoBehaviour that is asking. /// The target object (may be null for algorithms that don't require it). /// The spline on which the location must be found. /// The current position on the spline. /// The units in which spline positions are expressed. /// Current deltaTime. If smaller than 0, then previous frame data should be ignored. /// The desired position on the spline, expressed in positionUnits. float ISplineAutoDolly.GetSplinePosition( MonoBehaviour sender, Transform target, SplineContainer spline, float currentPosition, PathIndexUnit positionUnits, float deltaTime) { // Only works if playing if (Application.isPlaying && spline.IsValid() && deltaTime > 0) return currentPosition + Speed * deltaTime; return currentPosition; } } /// /// ISplineAutoDolly implementation that finds the point on th spline closest to the target. /// Note that this is a simple stateless algorithm, and is not appropriate for all spline shapes. /// For example, if the spline is forming an arc and the target is inside the arc, then the closest /// point can be noisy or undefined. Consider for example a spline that is perfectly circular /// with the target at the center. Where is the closest point? /// [Serializable] public class NearestPointToTarget : ISplineAutoDolly { /// /// Offset, in current position units, from the closest point on the spline to the follow target. /// [Tooltip("Offset, in current position units, from the closest point on the spline to the follow target")] public float PositionOffset = 0; /// /// Affects how many segments to split a spline into when calculating the nearest point. /// Higher values mean smaller and more segments, which increases accuracy at the cost of /// processing time. In most cases, the default resolution is appropriate. Use /// with to fine-tune point accuracy. /// For more information, see SplineUtility.GetNearestPoint. /// [Tooltip("Affects how many segments to split a spline into when calculating the nearest point. " + "Higher values mean smaller and more segments, which increases accuracy at the cost of " + "processing time. In most cases, the default value (4) is appropriate. Use with SearchIteration " + "to fine-tune point accuracy.")] public int SearchResolution = 4; /// /// The nearest point is calculated by finding the nearest point on the entire length /// of the spline using to divide into equally spaced line segments. /// Successive iterations will then subdivide further the nearest segment, producing more /// accurate results. In most cases, the default value is sufficient. /// For more information, see SplineUtility.GetNearestPoint. /// [Tooltip("The nearest point is calculated by finding the nearest point on the entire " + "length of the spline using SearchResolution to divide into equally spaced line segments. " + "Successive iterations will then subdivide further the nearest segment, producing more " + "accurate results. In most cases, the default value (2) is sufficient.")] public int SearchIteration = 2; /// Called from OnValidate() to validate the settings. void ISplineAutoDolly.Validate() { SearchResolution = Mathf.Max(SearchResolution, 1); SearchIteration = Mathf.Max(SearchIteration, 1); } /// This implementation does nothing. void ISplineAutoDolly.Reset() {} /// Returns true if this implementation requires a tracking target. bool ISplineAutoDolly.RequiresTrackingTarget => true; /// /// Compute the desired position on the spline. /// /// The MonoBehaviour that is asking. /// The target object (may be null for algorithms that don't require it). /// The spline on which the location must be found. /// The current position on the spline. /// The units in which spline positions are expressed. /// Current deltaTime. If smaller than 0, then previous frame data should be ignored. /// The desired position on the spline, expressed in positionUnits. float ISplineAutoDolly.GetSplinePosition( MonoBehaviour sender, Transform target, SplineContainer spline, float currentPosition, PathIndexUnit positionUnits, float deltaTime) { if (target == null || !spline.IsValid()) return currentPosition; // Convert target into spline local space, because SplineUtility works in spline local space SplineUtility.GetNearestPoint(spline.Spline, spline.transform.InverseTransformPoint(target.position), out _, out var normalizedPos, SearchResolution, SearchIteration); // GML hack because SplineUtility.GetNearestPoint is buggy normalizedPos = Mathf.Clamp01(normalizedPos); var pos = spline.Spline.ConvertIndexUnit(normalizedPos, PathIndexUnit.Normalized, positionUnits); return pos + PositionOffset; } } } }