CinemachineImpulseDefinition.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. using Cinemachine.Utility;
  2. using System;
  3. using UnityEngine;
  4. namespace Cinemachine
  5. {
  6. /// <summary>
  7. /// Property applied to CinemachineImpulseManager Channels.
  8. /// Used for custom drawing in the inspector. This is obsolete and no longer used.
  9. /// </summary>
  10. public sealed class CinemachineImpulseDefinitionPropertyAttribute : PropertyAttribute {}
  11. /// <summary>
  12. /// Definition of an impulse signal that gets propagated to listeners.
  13. ///
  14. /// Here you provide a Raw Signal source, and define an envelope for time-scaling
  15. /// it to craft the complete Impulse signal shape. Also, you provide here parameters
  16. /// that define how the signal dissipates with spatial distance from the source location.
  17. /// Finally, you specify the Impulse Channel on which the signal will be sent.
  18. ///
  19. /// An API method is provided here to take these parameters, create an Impulse Event,
  20. /// and broadcast it on the channel.
  21. ///
  22. /// When creating a custom Impulse Source class, you will have an instance of this class
  23. /// as a field in your custom class. Be sure also to include the
  24. /// [CinemachineImpulseDefinition] attribute on the field, to get the right
  25. /// property drawer for it.
  26. /// </summary>
  27. [DocumentationSorting(DocumentationSortingAttribute.Level.API)]
  28. [Serializable]
  29. public class CinemachineImpulseDefinition
  30. {
  31. /// <summary>
  32. /// Impulse events generated here will appear on the channels included in the mask.
  33. /// </summary>
  34. [CinemachineImpulseChannelProperty]
  35. [Tooltip("Impulse events generated here will appear on the channels included in the mask.")]
  36. public int m_ImpulseChannel = 1;
  37. /// <summary>Supported predefined shapes for the impulses.</summary>
  38. public enum ImpulseShapes
  39. {
  40. /// <summary>Custom shape</summary>
  41. Custom,
  42. /// <summary></summary>
  43. Recoil,
  44. /// <summary></summary>
  45. Bump,
  46. /// <summary></summary>
  47. Explosion,
  48. /// <summary></summary>
  49. Rumble
  50. };
  51. /// <summary>The shape of the impact signal.</summary>
  52. [Tooltip("Shape of the impact signal")]
  53. public ImpulseShapes m_ImpulseShape;
  54. /// <summary>
  55. /// A user-defined impulse shape, used only if m_ImpulseShape is Custom.
  56. /// Defines the signal that will be generated. X axis must go from 0...1,
  57. /// and Y axis is the scale that will be applied to the impact velocity.
  58. /// </summary>
  59. [Tooltip("Defines the custom shape of the impact signal that will be generated.")]
  60. public AnimationCurve m_CustomImpulseShape = new AnimationCurve();
  61. /// <summary>
  62. /// The time during which the impact signal will occur.
  63. /// The signal shape will be stretched to fill that time.
  64. /// </summary>
  65. [Tooltip("The time during which the impact signal will occur. "
  66. + "The signal shape will be stretched to fill that time.")]
  67. public float m_ImpulseDuration = 0.2f;
  68. /// <summary>
  69. /// This enum represents the various ways an impulse can travel through space
  70. /// </summary>
  71. public enum ImpulseTypes
  72. {
  73. /// <summary>The impulse is felt equally everywhere in space, at the same time</summary>
  74. Uniform,
  75. /// <summary>The impulse is felt only within a specified radius, and its strength
  76. /// weakens for listeners that are farther away</summary>
  77. Dissipating,
  78. /// <summary>
  79. /// The impulse is felt only within a specified radius, and its strength
  80. /// weakens for listeners that are farther away. Also, the impulse travels outwardly
  81. /// from the impact point, at a specified velocity, similar to a sound wave.
  82. /// </summary>
  83. Propagating,
  84. /// <summary>
  85. /// Back-compatibility mode for older projects. It's recommended to use
  86. /// one of the other impulse types, if possible.
  87. /// </summary>
  88. Legacy
  89. }
  90. /// <summary>
  91. /// How the impulse travels through space and time.
  92. /// </summary>
  93. [Tooltip("How the impulse travels through space and time.")]
  94. public ImpulseTypes m_ImpulseType = ImpulseTypes.Legacy; // Back-compatibility mode by default
  95. /// <summary>
  96. /// This defines how the widely signal will spread within the effect radius before
  97. /// dissipating with distance from the impact point
  98. /// </summary>
  99. [Tooltip("This defines how the widely signal will spread within the effect radius before "
  100. + "dissipating with distance from the impact point")]
  101. [Range(0,1)]
  102. public float m_DissipationRate;
  103. /// <summary>
  104. /// Legacy mode only: Defines the signal that will be generated.
  105. /// </summary>
  106. [Header("Signal Shape")]
  107. [Tooltip("Legacy mode only: Defines the signal that will be generated.")]
  108. [CinemachineEmbeddedAssetProperty(true)]
  109. public SignalSourceAsset m_RawSignal = null;
  110. /// <summary>
  111. /// Legacy mode only: Gain to apply to the amplitudes defined in the signal source asset.
  112. /// </summary>
  113. [Tooltip("Legacy mode only: Gain to apply to the amplitudes defined in the signal source. "
  114. + "1 is normal. Setting this to 0 completely mutes the signal.")]
  115. public float m_AmplitudeGain = 1f;
  116. /// <summary>
  117. /// Legacy mode only: Scale factor to apply to the time axis.
  118. /// </summary>
  119. [Tooltip("Legacy mode only: Scale factor to apply to the time axis. 1 is normal. "
  120. + "Larger magnitudes will make the signal progress more rapidly.")]
  121. public float m_FrequencyGain = 1f;
  122. /// <summary>Legacy mode only: How to fit the signal into the envelope time</summary>
  123. public enum RepeatMode
  124. {
  125. /// <summary>Time-stretch the signal to fit the envelope</summary>
  126. Stretch,
  127. /// <summary>Loop the signal in time to fill the envelope</summary>
  128. Loop
  129. }
  130. /// <summary>Legacy mode only: How to fit the signal into the envelope time</summary>
  131. [Tooltip("Legacy mode only: How to fit the signal into the envelope time")]
  132. public RepeatMode m_RepeatMode = RepeatMode.Stretch;
  133. /// <summary>Legacy mode only: Randomize the signal start time</summary>
  134. [Tooltip("Legacy mode only: Randomize the signal start time")]
  135. public bool m_Randomize = true;
  136. /// <summary>
  137. /// Legacy mode only: This defines the time-envelope of the signal.
  138. /// The raw signal will be time-scaled to fit in the envelope.
  139. /// </summary>
  140. [Tooltip("Legacy mode only: This defines the time-envelope of the signal. "
  141. + "The raw signal will be time-scaled to fit in the envelope.")]
  142. public CinemachineImpulseManager.EnvelopeDefinition m_TimeEnvelope
  143. = CinemachineImpulseManager.EnvelopeDefinition.Default();
  144. /// <summary>
  145. /// Legacy mode only: The signal will have full amplitude in this radius surrounding the impact point.
  146. /// Beyond that it will dissipate with distance.
  147. /// </summary>
  148. [Header("Spatial Range")]
  149. [Tooltip("Legacy mode only: The signal will have full amplitude in this radius surrounding "
  150. + "the impact point. Beyond that it will dissipate with distance.")]
  151. public float m_ImpactRadius = 100;
  152. /// <summary>Legacy mode only: How the signal direction behaves as the listener moves
  153. /// away from the origin.</summary>
  154. [Tooltip("Legacy mode only: How the signal direction behaves as the listener moves away from the origin.")]
  155. public CinemachineImpulseManager.ImpulseEvent.DirectionMode m_DirectionMode
  156. = CinemachineImpulseManager.ImpulseEvent.DirectionMode.Fixed;
  157. /// <summary>
  158. /// Legacy mode only: This defines how the signal will dissipate with distance beyond the impact radius.
  159. /// </summary>
  160. [Tooltip("Legacy mode only: This defines how the signal will dissipate with distance beyond the impact radius.")]
  161. public CinemachineImpulseManager.ImpulseEvent.DissipationMode m_DissipationMode
  162. = CinemachineImpulseManager.ImpulseEvent.DissipationMode.ExponentialDecay;
  163. /// <summary>
  164. /// The signal will have no effect outside this radius surrounding the impact point.
  165. /// </summary>
  166. [Tooltip("The signal will have no effect outside this radius surrounding the impact point.")]
  167. public float m_DissipationDistance = 100;
  168. /// <summary>
  169. /// The speed (m/s) at which the impulse propagates through space. High speeds
  170. /// allow listeners to react instantaneously, while slower speeds allow listeners in the
  171. /// scene to react as if to a wave spreading from the source.
  172. /// </summary>
  173. [Tooltip("The speed (m/s) at which the impulse propagates through space. High speeds "
  174. + "allow listeners to react instantaneously, while slower speeds allow listeners in the "
  175. + "scene to react as if to a wave spreading from the source.")]
  176. public float m_PropagationSpeed = 343; // speed of sound
  177. /// <summary>Call this from your behaviour's OnValidate to validate the fields here</summary>
  178. public void OnValidate()
  179. {
  180. RuntimeUtility.NormalizeCurve(m_CustomImpulseShape, true, false);
  181. m_ImpulseDuration = Mathf.Max(UnityVectorExtensions.Epsilon, m_ImpulseDuration);
  182. m_DissipationDistance = Mathf.Max(UnityVectorExtensions.Epsilon, m_DissipationDistance);
  183. m_DissipationRate = Mathf.Clamp01(m_DissipationRate);
  184. m_PropagationSpeed = Mathf.Max(1, m_PropagationSpeed);
  185. // legacy
  186. m_ImpactRadius = Mathf.Max(0, m_ImpactRadius);
  187. m_TimeEnvelope.Validate();
  188. m_PropagationSpeed = Mathf.Max(1, m_PropagationSpeed);
  189. }
  190. static AnimationCurve[] sStandardShapes;
  191. static void CreateStandardShapes()
  192. {
  193. int max = 0;
  194. foreach (var value in Enum.GetValues(typeof(ImpulseShapes)))
  195. max = Mathf.Max(max, (int)value);
  196. sStandardShapes = new AnimationCurve[max + 1];
  197. sStandardShapes[(int)ImpulseShapes.Recoil] = new AnimationCurve(new Keyframe[]
  198. {
  199. new Keyframe(0, 1, -3.2f, -3.2f),
  200. new Keyframe(1, 0, 0, 0)
  201. });
  202. sStandardShapes[(int)ImpulseShapes.Bump] = new AnimationCurve(new Keyframe[]
  203. {
  204. new Keyframe(0, 0, -4.9f, -4.9f),
  205. new Keyframe(0.2f, 0, 8.25f, 8.25f),
  206. new Keyframe(1, 0, -0.25f, -0.25f)
  207. });
  208. sStandardShapes[(int)ImpulseShapes.Explosion] = new AnimationCurve(new Keyframe[]
  209. {
  210. new Keyframe(0, -1.4f, -7.9f, -7.9f),
  211. new Keyframe(0.27f, 0.78f, 23.4f, 23.4f),
  212. new Keyframe(0.54f, -0.12f, 22.6f, 22.6f),
  213. new Keyframe(0.75f, 0.042f, 9.23f, 9.23f),
  214. new Keyframe(0.9f, -0.02f, 5.8f, 5.8f),
  215. new Keyframe(0.95f, -0.006f, -3.0f, -3.0f),
  216. new Keyframe(1, 0, 0, 0)
  217. });
  218. sStandardShapes[(int)ImpulseShapes.Rumble] = new AnimationCurve(new Keyframe[]
  219. {
  220. new Keyframe(0, 0, 0, 0),
  221. new Keyframe(0.1f, 0.25f, 0, 0),
  222. new Keyframe(0.2f, 0, 0, 0),
  223. new Keyframe(0.3f, 0.75f, 0, 0),
  224. new Keyframe(0.4f, 0, 0, 0),
  225. new Keyframe(0.5f, 1, 0, 0),
  226. new Keyframe(0.6f, 0, 0, 0),
  227. new Keyframe(0.7f, 0.75f, 0, 0),
  228. new Keyframe(0.8f, 0, 0, 0),
  229. new Keyframe(0.9f, 0.25f, 0, 0),
  230. new Keyframe(1, 0, 0, 0)
  231. });
  232. }
  233. internal static AnimationCurve GetStandardCurve(ImpulseShapes shape)
  234. {
  235. if (sStandardShapes == null)
  236. CreateStandardShapes();
  237. return sStandardShapes[(int)shape];
  238. }
  239. internal AnimationCurve ImpulseCurve
  240. {
  241. get
  242. {
  243. if (m_ImpulseShape == ImpulseShapes.Custom)
  244. {
  245. if (m_CustomImpulseShape == null)
  246. m_CustomImpulseShape = AnimationCurve.EaseInOut(0f, 0f, 1, 1f);
  247. return m_CustomImpulseShape;
  248. }
  249. return GetStandardCurve(m_ImpulseShape);
  250. }
  251. }
  252. /// <summary>Generate an impulse event at a location in space,
  253. /// and broadcast it on the appropriate impulse channel</summary>
  254. /// <param name="position">Event originates at this position in world space</param>
  255. /// <param name="velocity">This direction is considered to be "down" for the purposes of the
  256. /// event signal, and the magnitude of the signal will be scaled according to the
  257. /// length of this vector</param>
  258. public void CreateEvent(Vector3 position, Vector3 velocity)
  259. {
  260. CreateAndReturnEvent(position, velocity);
  261. }
  262. /// <summary>Generate an impulse event at a location in space,
  263. /// and broadcast it on the appropriate impulse channel</summary>
  264. /// <param name="position">Event originates at this position in world space</param>
  265. /// <param name="velocity">This direction is considered to be "down" for the purposes of the
  266. /// event signal, and the magnitude of the signal will be scaled according to the
  267. /// length of this vector</param>
  268. /// <returns>The newly-created impulse event. This can be used to dynmically adjust the
  269. /// event settings while the event is active. Note that this event object may be recycled
  270. /// for future events, so the pointer should not be retained for too long.</returns>
  271. public CinemachineImpulseManager.ImpulseEvent CreateAndReturnEvent(
  272. Vector3 position, Vector3 velocity)
  273. {
  274. // Legacy mode
  275. if (m_ImpulseType == ImpulseTypes.Legacy)
  276. return LegacyCreateAndReturnEvent(position, velocity);
  277. const float kBigNumber = 9999999.0f;
  278. if ((m_ImpulseShape == ImpulseShapes.Custom && m_CustomImpulseShape == null)
  279. || Mathf.Abs(m_DissipationDistance) < UnityVectorExtensions.Epsilon
  280. || Mathf.Abs(m_ImpulseDuration) < UnityVectorExtensions.Epsilon)
  281. return null;
  282. CinemachineImpulseManager.ImpulseEvent e
  283. = CinemachineImpulseManager.Instance.NewImpulseEvent();
  284. e.m_Envelope = new CinemachineImpulseManager.EnvelopeDefinition
  285. {
  286. m_SustainTime = m_ImpulseDuration
  287. };
  288. e.m_SignalSource = new SignalSource(this, velocity);
  289. e.m_Position = position;
  290. e.m_Radius = m_ImpulseType == ImpulseTypes.Uniform ? kBigNumber : 0;
  291. e.m_Channel = m_ImpulseChannel;
  292. e.m_DirectionMode = CinemachineImpulseManager.ImpulseEvent.DirectionMode.Fixed;
  293. e.m_DissipationDistance = m_ImpulseType == ImpulseTypes.Uniform ? 0 : m_DissipationDistance;
  294. e.m_PropagationSpeed = m_ImpulseType == ImpulseTypes.Propagating ? m_PropagationSpeed : kBigNumber;
  295. e.m_CustomDissipation = m_DissipationRate;
  296. CinemachineImpulseManager.Instance.AddImpulseEvent(e);
  297. return e;
  298. }
  299. CinemachineImpulseManager.ImpulseEvent LegacyCreateAndReturnEvent(
  300. Vector3 position, Vector3 velocity)
  301. {
  302. if (m_RawSignal == null || Mathf.Abs(m_TimeEnvelope.Duration) < UnityVectorExtensions.Epsilon)
  303. return null;
  304. CinemachineImpulseManager.ImpulseEvent e
  305. = CinemachineImpulseManager.Instance.NewImpulseEvent();
  306. e.m_Envelope = m_TimeEnvelope;
  307. // Scale the time-envelope decay as the root of the amplitude scale
  308. e.m_Envelope = m_TimeEnvelope;
  309. if (m_TimeEnvelope.m_ScaleWithImpact)
  310. e.m_Envelope.m_DecayTime *= Mathf.Sqrt(velocity.magnitude);
  311. e.m_SignalSource = new LegacySignalSource(this, velocity);
  312. e.m_Position = position;
  313. e.m_Radius = m_ImpactRadius;
  314. e.m_Channel = m_ImpulseChannel;
  315. e.m_DirectionMode = m_DirectionMode;
  316. e.m_DissipationMode = m_DissipationMode;
  317. e.m_DissipationDistance = m_DissipationDistance;
  318. e.m_PropagationSpeed = m_PropagationSpeed;
  319. CinemachineImpulseManager.Instance.AddImpulseEvent(e);
  320. return e;
  321. }
  322. // Wrap the raw signal to handle gain, RepeatMode, randomization, and velocity
  323. class SignalSource : ISignalSource6D
  324. {
  325. CinemachineImpulseDefinition m_Def;
  326. Vector3 m_Velocity;
  327. public SignalSource(CinemachineImpulseDefinition def, Vector3 velocity)
  328. {
  329. m_Def = def;
  330. m_Velocity = velocity;
  331. }
  332. public float SignalDuration { get { return m_Def.m_ImpulseDuration; } }
  333. public void GetSignal(float timeSinceSignalStart, out Vector3 pos, out Quaternion rot)
  334. {
  335. pos = m_Velocity * m_Def.ImpulseCurve.Evaluate(timeSinceSignalStart / SignalDuration);
  336. rot = Quaternion.identity;
  337. }
  338. }
  339. // Wrap the raw signal to handle gain, RepeatMode, randomization, and velocity
  340. class LegacySignalSource : ISignalSource6D
  341. {
  342. CinemachineImpulseDefinition m_Def;
  343. Vector3 m_Velocity;
  344. float m_StartTimeOffset = 0;
  345. public LegacySignalSource(CinemachineImpulseDefinition def, Vector3 velocity)
  346. {
  347. m_Def = def;
  348. m_Velocity = velocity;
  349. if (m_Def.m_Randomize && m_Def.m_RawSignal.SignalDuration <= 0)
  350. m_StartTimeOffset = UnityEngine.Random.Range(-1000f, 1000f);
  351. }
  352. public float SignalDuration { get { return m_Def.m_RawSignal.SignalDuration; } }
  353. public void GetSignal(float timeSinceSignalStart, out Vector3 pos, out Quaternion rot)
  354. {
  355. float time = m_StartTimeOffset + timeSinceSignalStart * m_Def.m_FrequencyGain;
  356. // Do we have to fit the signal into the envelope?
  357. float signalDuration = SignalDuration;
  358. if (signalDuration > 0)
  359. {
  360. if (m_Def.m_RepeatMode == RepeatMode.Loop)
  361. time %= signalDuration;
  362. else if (m_Def.m_TimeEnvelope.Duration > UnityVectorExtensions.Epsilon)
  363. time *= m_Def.m_TimeEnvelope.Duration / signalDuration; // stretch
  364. }
  365. m_Def.m_RawSignal.GetSignal(time, out pos, out rot);
  366. float gain = m_Velocity.magnitude;
  367. Vector3 dir = m_Velocity.normalized;
  368. gain *= m_Def.m_AmplitudeGain;
  369. pos *= gain;
  370. pos = Quaternion.FromToRotation(Vector3.down, m_Velocity) * pos;
  371. rot = Quaternion.SlerpUnclamped(Quaternion.identity, rot, gain);
  372. }
  373. }
  374. }
  375. }