using Cinemachine.Utility; using System; using UnityEngine; namespace Cinemachine { /// /// Property applied to CinemachineImpulseManager Channels. /// Used for custom drawing in the inspector. /// public sealed class CinemachineImpulseDefinitionPropertyAttribute : PropertyAttribute {} /// /// Definition of an impulse signal that gets propagated to listeners. /// /// Here you provide a Raw Signal source, and define an envelope for time-scaling /// it to craft the complete Impulse signal shape. Also, you provide here parameters /// that define how the signal dissipates with spatial distance from the source location. /// Finally, you specify the Impulse Channel on which the signal will be sent. /// /// An API method is provided here to take these parameters, create an Impulse Event, /// and broadcast it on the channel. /// /// When creating a custom Impulse Source class, you will have an instance of this class /// as a field in your custom class. Be sure also to include the /// [CinemachineImpulseDefinition] attribute on the field, to get the right /// property drawer for it. /// [DocumentationSorting(DocumentationSortingAttribute.Level.API)] [Serializable] public class CinemachineImpulseDefinition { /// /// Impulse events generated here will appear on the channels included in the mask. /// [CinemachineImpulseChannelProperty] [Tooltip("Impulse events generated here will appear on the channels included in the mask.")] public int m_ImpulseChannel = 1; /// /// Defines the signal that will be generated. /// [Header("Signal Shape")] [Tooltip("Defines the signal that will be generated.")] [CinemachineEmbeddedAssetProperty(true)] public SignalSourceAsset m_RawSignal = null; /// /// Gain to apply to the amplitudes defined in the signal source asset. /// [Tooltip("Gain to apply to the amplitudes defined in the signal source. 1 is normal. Setting this to 0 completely mutes the signal.")] public float m_AmplitudeGain = 1f; /// /// Scale factor to apply to the time axis. /// [Tooltip("Scale factor to apply to the time axis. 1 is normal. Larger magnitudes will make the signal progress more rapidly.")] public float m_FrequencyGain = 1f; /// How to fit the signal into the envelope time public enum RepeatMode { /// Time-stretch the signal to fit the envelope Stretch, /// Loop the signal in time to fill the envelope Loop } /// How to fit the signal into the envelope time [Tooltip("How to fit the signal into the envelope time")] public RepeatMode m_RepeatMode = RepeatMode.Stretch; /// Randomize the signal start time [Tooltip("Randomize the signal start time")] public bool m_Randomize = true; /// /// This defines the time-envelope of the signal. /// The raw signal will be time-scaled to fit in the envelope. /// [Tooltip("This defines the time-envelope of the signal. The raw signal will be time-scaled to fit in the envelope.")] [CinemachineImpulseEnvelopeProperty] public CinemachineImpulseManager.EnvelopeDefinition m_TimeEnvelope = CinemachineImpulseManager.EnvelopeDefinition.Default(); /// /// The signal will have full amplitude in this radius surrounding the impact point. /// Beyond that it will dissipate with distance. /// [Header("Spatial Range")] [Tooltip("The signal will have full amplitude in this radius surrounding the impact point. Beyond that it will dissipate with distance.")] public float m_ImpactRadius = 100; /// How the signal direction behaves as the listener moves away from the origin. [Tooltip("How the signal direction behaves as the listener moves away from the origin.")] public CinemachineImpulseManager.ImpulseEvent.DirectionMode m_DirectionMode = CinemachineImpulseManager.ImpulseEvent.DirectionMode.Fixed; /// /// This defines how the signal will dissipate with distance beyond the impact radius. /// [Tooltip("This defines how the signal will dissipate with distance beyond the impact radius.")] public CinemachineImpulseManager.ImpulseEvent.DissipationMode m_DissipationMode = CinemachineImpulseManager.ImpulseEvent.DissipationMode.ExponentialDecay; /// /// At this distance beyond the impact radius, the signal will have dissipated to zero. /// [Tooltip("At this distance beyond the impact radius, the signal will have dissipated to zero.")] public float m_DissipationDistance = 1000; /// /// The speed (m/s) at which the impulse propagates through space. High speeds /// allow listeners to react instantaneously, while slower speeds allow listeners in the /// scene to react as if to a wave spreading from the source. /// [Tooltip("The speed (m/s) at which the impulse propagates through space. High speeds " + "allow listeners to react instantaneously, while slower speeds allow listeners in the " + "scene to react as if to a wave spreading from the source.")] public float m_PropagationSpeed = 343; // speed of sound /// Call this from your behaviour's OnValidate to validate the fields here public void OnValidate() { m_ImpactRadius = Mathf.Max(0, m_ImpactRadius); m_DissipationDistance = Mathf.Max(0, m_DissipationDistance); m_TimeEnvelope.Validate(); m_PropagationSpeed = Mathf.Max(1, m_PropagationSpeed); } /// Generate an impulse event at a location in space, /// and broadcast it on the appropriate impulse channel /// Event originates at this position in world space /// This direction is considered to be "down" for the purposes of the /// event signal, and the magnitude of the signal will be scaled according to the /// length of this vector public void CreateEvent(Vector3 position, Vector3 velocity) { CreateAndReturnEvent(position, velocity); } /// Generate an impulse event at a location in space, /// and broadcast it on the appropriate impulse channel /// Event originates at this position in world space /// This direction is considered to be "down" for the purposes of the /// event signal, and the magnitude of the signal will be scaled according to the /// length of this vector /// Returns the created event, so that the caller can modify it dynamically public CinemachineImpulseManager.ImpulseEvent CreateAndReturnEvent(Vector3 position, Vector3 velocity) { if (m_RawSignal == null || Mathf.Abs(m_TimeEnvelope.Duration) < UnityVectorExtensions.Epsilon) return null; CinemachineImpulseManager.ImpulseEvent e = CinemachineImpulseManager.Instance.NewImpulseEvent(); e.m_Envelope = m_TimeEnvelope; // Scale the time-envelope decay as the root of the amplitude scale e.m_Envelope = m_TimeEnvelope; if (m_TimeEnvelope.m_ScaleWithImpact) e.m_Envelope.m_DecayTime *= Mathf.Sqrt(velocity.magnitude); e.m_SignalSource = new SignalSource(this, velocity); e.m_Position = position; e.m_Radius = m_ImpactRadius; e.m_Channel = m_ImpulseChannel; e.m_DirectionMode = m_DirectionMode; e.m_DissipationMode = m_DissipationMode; e.m_DissipationDistance = m_DissipationDistance; e.m_PropagationSpeed = m_PropagationSpeed; CinemachineImpulseManager.Instance.AddImpulseEvent(e); return e; } // Wrap the raw signal to handle gain, RepeatMode, randomization, and velocity class SignalSource : ISignalSource6D { CinemachineImpulseDefinition m_Def; Vector3 m_Velocity; float m_StartTimeOffset = 0; public SignalSource(CinemachineImpulseDefinition def, Vector3 velocity) { m_Def = def; m_Velocity = velocity; if (m_Def.m_Randomize && m_Def.m_RawSignal.SignalDuration <= 0) m_StartTimeOffset = UnityEngine.Random.Range(-1000f, 1000f); } public float SignalDuration { get { return m_Def.m_RawSignal.SignalDuration; } } public void GetSignal(float timeSinceSignalStart, out Vector3 pos, out Quaternion rot) { float time = m_StartTimeOffset + timeSinceSignalStart * m_Def.m_FrequencyGain; // Do we have to fit the signal into the envelope? float signalDuration = SignalDuration; if (signalDuration > 0) { if (m_Def.m_RepeatMode == RepeatMode.Loop) time %= signalDuration; else if (m_Def.m_TimeEnvelope.Duration > UnityVectorExtensions.Epsilon) time *= m_Def.m_TimeEnvelope.Duration / signalDuration; // stretch } m_Def.m_RawSignal.GetSignal(time, out pos, out rot); float gain = m_Velocity.magnitude; Vector3 dir = m_Velocity.normalized; gain *= m_Def.m_AmplitudeGain; pos *= gain; pos = Quaternion.FromToRotation(Vector3.down, m_Velocity) * pos; rot = Quaternion.SlerpUnclamped(Quaternion.identity, rot, gain); } } } }