AudioZone.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // River Modeler
  2. // Staggart Creations (http://staggart.xyz)
  3. // Copyright protected under Unity Asset Store EULA
  4. // Copying or referencing source code for the production of new asset store content is strictly prohibited.
  5. using System;
  6. using System.Collections.Generic;
  7. using UnityEngine;
  8. using UnityEngine.Audio;
  9. using Random = UnityEngine.Random;
  10. #if MATHEMATICS
  11. using Unity.Mathematics;
  12. #endif
  13. #if SPLINES
  14. using UnityEngine.Splines;
  15. using Interpolators = UnityEngine.Splines.Interpolators;
  16. #endif
  17. #if UNITY_EDITOR
  18. using UnityEditor;
  19. #endif
  20. namespace sc.modeling.river.runtime
  21. {
  22. [ExecuteAlways]
  23. [AddComponentMenu("Water/River/River Audio Zone")]
  24. [HelpURL("https://staggart.xyz/river-modeler-docs/?section=audio-zone")]
  25. public class AudioZone : MonoBehaviour
  26. {
  27. public RiverModeler river;
  28. public enum Confines
  29. {
  30. EntireMesh,
  31. Bounds
  32. }
  33. public Confines confines;
  34. public Vector3 boundsSize = Vector3.one * 10f;
  35. public Vector2 minMaxSlopeAngle = new Vector2(0f, 90f);
  36. [Range(0f,100f)]
  37. public float resolution = 100f;
  38. [Tooltip("An audio clip will be randomly picked from this array, for added variation")]
  39. public List<AudioClip> clips = new List<AudioClip>();
  40. public AudioMixerGroup outputMixer;
  41. [Range(0f,1f)]
  42. public float maxVolume = 1f;
  43. [Range(0f, 0.5f)]
  44. public float pitchModulation = 0.1f;
  45. [Range(0f, 1f)]
  46. [Tooltip("Blend between Mono/2D (0) and Stereo/3D (0).")]
  47. public float directionality = 0.75f;
  48. [Tooltip("Attenuate the audio emitters based on their distance from the scene-view camera.")]
  49. public bool updateInSceneView = true;
  50. [Min(0f)]
  51. public float minAudibleDistance = 20f;
  52. [Min(1f)]
  53. public float maxAudibleDistance = 200f;
  54. [SerializeField]
  55. private AudioSource[] emitters = Array.Empty<AudioSource>();
  56. [NonSerialized]
  57. private Bounds bounds;
  58. private void OnEnable()
  59. {
  60. RiverModeler.onPreRebuildRiver += OnRiverChange;
  61. if (Application.isPlaying) Play();
  62. #if UNITY_EDITOR
  63. SceneView.duringSceneGui += OnSceneGUI;
  64. #endif
  65. }
  66. private void OnDisable()
  67. {
  68. RiverModeler.onPreRebuildRiver -= OnRiverChange;
  69. #if UNITY_EDITOR
  70. SceneView.duringSceneGui -= OnSceneGUI;
  71. #endif
  72. }
  73. private void OnRiverChange(RiverModeler instance, RiverModeler.ChangeFlags updateFlags)
  74. {
  75. if (updateFlags.HasFlag(RiverModeler.ChangeFlags.All) || updateFlags.HasFlag(RiverModeler.ChangeFlags.Spline) && river != null && river.GetHashCode() == instance.GetHashCode())
  76. {
  77. //Debug.Log("Updating audio zone: " + updateFlags);
  78. Rebuild();
  79. }
  80. }
  81. void Reset()
  82. {
  83. river = GetComponentInParent<RiverModeler>();
  84. if(!river) river = GetComponent<RiverModeler>();
  85. }
  86. private void Start()
  87. {
  88. Play();
  89. }
  90. [ContextMenu("Play")]
  91. public void Play()
  92. {
  93. for (int i = 0; i < emitters.Length; i++)
  94. {
  95. if (!emitters[i] || !emitters[i].clip) continue;
  96. //Offset the starting point
  97. emitters[i].time = Random.Range(0f, emitters[i].clip.length);
  98. emitters[i].Play();
  99. }
  100. }
  101. #if MATHEMATICS
  102. public struct SpawnPoint
  103. {
  104. public float3 position;
  105. public float radius;
  106. }
  107. private List<SpawnPoint> spawnPoints;
  108. #endif
  109. [NonSerialized]
  110. private AudioListener audioListener;
  111. public void Rebuild()
  112. {
  113. #if SPLINES && MATHEMATICS
  114. if (clips.Count == 0) return;
  115. spawnPoints = new List<SpawnPoint>();
  116. var splineContainer = river.splineContainer;
  117. Interpolators.LerpFloat3 scaleInterpolator = new Interpolators.LerpFloat3();
  118. float3 prevSpawnPos = Vector3.positiveInfinity;
  119. float prevEmitterRadius = 0f;
  120. if (confines == Confines.Bounds) bounds = new Bounds(this.transform.position, boundsSize);
  121. for (int s = 0; s < splineContainer.Splines.Count; s++)
  122. {
  123. float length = splineContainer.Splines[s].CalculateLength(splineContainer.transform.localToWorldMatrix);
  124. int sampleCount = Mathf.CeilToInt((length * (resolution * 0.01f)) / (river.settings.triangulation.vertexDistance));
  125. for (int y = 0; y <= sampleCount; y++)
  126. {
  127. float t = (float)y / (float)(sampleCount); //Normalized position
  128. splineContainer.Splines[s].Evaluate(t, out var position, out var tangent, out var normal);
  129. if (confines == Confines.Bounds)
  130. {
  131. //Early out if spline point falls outside of bounds
  132. if (bounds.Contains(splineContainer.transform.TransformPoint(position)) == false) continue;
  133. }
  134. float3 scale = river.ScaleData[s] != null ? river.ScaleData[s].Evaluate(splineContainer.Splines[s], t, river.ScaleData[s].PathIndexUnit, scaleInterpolator) : Vector3.one;
  135. SpawnPoint spawnPoint = new SpawnPoint();
  136. spawnPoint.position = splineContainer.transform.TransformPoint(position);
  137. spawnPoint.radius = math.max(minAudibleDistance, (scale.x * river.settings.shape.width) * 0.5f);
  138. float prevEmitterDist = math.distancesq(prevSpawnPos + (math.normalize(tangent) * prevEmitterRadius), spawnPoint.position);
  139. float angle = ((float)math.acos(math.dot(normal, Vector3.up)) * Mathf.Rad2Deg);
  140. float slopeMask = 0f;
  141. if (angle > minMaxSlopeAngle.x && angle < minMaxSlopeAngle.y) slopeMask = 1f;
  142. if (slopeMask > 0 && prevEmitterDist > (spawnPoint.radius * spawnPoint.radius))
  143. {
  144. spawnPoints.Add(spawnPoint);
  145. prevSpawnPos = spawnPoint.position;
  146. prevEmitterRadius = spawnPoint.radius;
  147. }
  148. }
  149. }
  150. if (emitters.Length != spawnPoints.Count)
  151. {
  152. RecreateEmitters();
  153. }
  154. UpdateEmitters();
  155. #endif
  156. }
  157. void RecreateEmitters()
  158. {
  159. #if MATHEMATICS
  160. //Delete current first, to ensure no colliders are in the way
  161. for (int i = 0; i < emitters.Length; i++)
  162. {
  163. if (emitters[i])
  164. {
  165. DestroyImmediate(emitters[i].gameObject);
  166. }
  167. }
  168. AudioSource[] audioSources = GetComponentsInChildren<AudioSource>();
  169. for (int i = 0; i < audioSources.Length; i++)
  170. {
  171. DestroyImmediate(audioSources[i].gameObject);
  172. }
  173. emitters = new AudioSource[spawnPoints.Count];
  174. for (int i = 0; i < emitters.Length; i++)
  175. {
  176. GameObject obj = new GameObject($"AudioEmitter_{i}");
  177. obj.transform.parent = this.transform;
  178. AudioSource emitter = obj.AddComponent<AudioSource>();
  179. emitter.transform.position = spawnPoints[i].position;
  180. emitters[i] = emitter;
  181. }
  182. #endif
  183. }
  184. void UpdateEmitters()
  185. {
  186. #if MATHEMATICS
  187. for (int i = 0; i < emitters.Length; i++)
  188. {
  189. if (!emitters[i]) continue;
  190. emitters[i].playOnAwake = false;
  191. emitters[i].clip = clips[Random.Range(0, clips.Count)];
  192. emitters[i].gameObject.name = $"AudioEmitter_{i}";
  193. emitters[i].outputAudioMixerGroup = outputMixer;
  194. emitters[i].loop = true;
  195. emitters[i].spatialBlend = directionality;
  196. emitters[i].rolloffMode = AudioRolloffMode.Linear;
  197. emitters[i].pitch = UnityEngine.Random.Range(1f - pitchModulation, 1f + pitchModulation);
  198. emitters[i].volume = 0f;
  199. emitters[i].minDistance = minAudibleDistance + spawnPoints[i].radius;
  200. emitters[i].maxDistance = emitters[i].minDistance + maxAudibleDistance;
  201. emitters[i].dopplerLevel = 0f;
  202. }
  203. Play();
  204. #endif
  205. }
  206. void Update()
  207. {
  208. if (Application.isPlaying == false) return;
  209. #if UNITY_2022_1_OR_NEWER
  210. if (!audioListener) audioListener = FindFirstObjectByType<AudioListener>();
  211. #else
  212. if (!audioListener) audioListener = FindObjectOfType<AudioListener>();
  213. #endif
  214. if (!audioListener) return;
  215. ApplyVolumeAttenuation(audioListener.transform.position);
  216. }
  217. #if UNITY_EDITOR
  218. private void OnSceneGUI(SceneView sceneView)
  219. {
  220. if (Application.isPlaying) return;
  221. if (updateInSceneView && sceneView.audioPlay)
  222. {
  223. ApplyVolumeAttenuation(sceneView.camera.transform.position);
  224. }
  225. else
  226. {
  227. ApplyVolumeAttenuation(Vector3.one * 100000f);
  228. }
  229. }
  230. #endif
  231. private void ApplyVolumeAttenuation(Vector3 listener)
  232. {
  233. #if MATHEMATICS
  234. for (int i = 0; i < emitters.Length; i++)
  235. {
  236. if (!emitters[i]) continue;
  237. var dist = math.distance(emitters[i].transform.position, listener);
  238. var minDist = emitters[i].minDistance;
  239. var maxDist = emitters[i].maxDistance;
  240. emitters[i].volume = math.clamp(1f-((dist - minDist) / (maxDist - minDist)), 0, maxVolume);
  241. }
  242. #endif
  243. }
  244. private void OnDrawGizmosSelected()
  245. {
  246. Color color = Color.yellow;
  247. for (int i = 0; i < emitters.Length; i++)
  248. {
  249. if (!emitters[i]) continue;
  250. color.a = emitters[i].volume / maxVolume;
  251. Gizmos.color = color;
  252. if (color.a > 0)
  253. {
  254. Gizmos.DrawWireSphere(emitters[i].transform.position, emitters[i].minDistance + 0.01f);
  255. }
  256. }
  257. if (confines == Confines.Bounds)
  258. {
  259. Gizmos.DrawWireCube(this.transform.position, boundsSize);
  260. }
  261. }
  262. }
  263. }