/// /// NAudio by Nothke /// Simple clip playing and audio source creation in a single line /// /// See function summaries and examples for usage /// /// DEFINES: /// /// Pooling creates a few sources on start and saves them in a queue, reusing them on every play, /// so sources are not created and destroyed every time played, freeing GC. /// If you don't want to use pooling, comment this line: //#define POOLING /// If you are using native audio spatializer, uncomment following line. It will enable spatialization in sources //#define ENABLE_SPATIALIZER_API /// If using Oculus audio - ONSP, uncomment following line. It will add the ONSPAudioSource script to sources //#define USE_OCULUS_AUDIO //#define SEEK_NOT_PLAYING_SOURCES using System.Collections.Generic; using UnityEngine; using UnityEngine.Audio; public static class NAudio { #if POOLING public static Queue sourcePool; const int POOL_SIZE = 50; #else // Just to make sure destroy won't cut the sound short in case of an increased latency const float DESTROY_AFTER_MARGIN_SECONDS = 0.2f; #endif const float DEFAULT_MIN_DISTANCE = 1; const float DEFAULT_SPREAD = 0; public static Transform root; #if POOLING static AudioSource GetNextSource() { AudioSource source = sourcePool.Dequeue(); sourcePool.Enqueue(source); #if SEEK_NOT_PLAYING_SOURCES int search = 0; while (source.isPlaying) { source = sourcePool.Dequeue(); sourcePool.Enqueue(source); if (search >= POOL_SIZE) { Debug.LogError("All sounds are playing, increase the pool size"); break; } search++; } #endif return source; } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] public static void InitializePool() { InitializePool(POOL_SIZE); } public static void InitializePool(int size) { if (sourcePool != null && sourcePool.Count > 0) { //Debug.LogWarning("Pool already exists"); foreach (var source in sourcePool) { if (source) { Object.Destroy(source); } } } sourcePool = new Queue(size); GameObject rootGO = new GameObject("NAudio_Pool"); root = rootGO.transform; for (int i = 0; i < size; i++) { sourcePool.Enqueue(CreateSource(root)); } } #endif /// /// Plays a clip at a position with properties /// /// Clip to play /// Position at which it will be played /// /// /// How 'wide' the panning will be in 3d. 0 means it will be heard from only one direction, 180 is all around, 360 is opposite direction. /// The distance at which source volume will be 1 and falloff. Technically this is the sound intensity. /// Add source to mixer group /// A transform to attach source to. Position is still world position before parenting. /// public static AudioSource Play( this AudioClip clip, Vector3 position, float volume = 1, float pitch = 1, float spread = DEFAULT_SPREAD, float minDistance = DEFAULT_MIN_DISTANCE, AudioMixerGroup mixerGroup = null, Transform parent = null) { if (volume == 0) return null; if (pitch == 0) return null; Debug.Assert(clip != null, "No AudioClip was passed"); #if POOLING if (sourcePool == null) InitializePool(); AudioSource source = GetNextSource(); if (!source) return null; GameObject go = source.gameObject; #else GameObject go = new GameObject("AudioTemp"); AudioSource source = go.AddComponent(); #endif go.transform.position = position; source.spatialBlend = 1; // Makes the source 3D source.minDistance = minDistance; source.loop = false; source.clip = clip; source.volume = volume; source.pitch = pitch; source.spread = spread; source.dopplerLevel = 0; source.outputAudioMixerGroup = mixerGroup; if (parent) source.transform.parent = parent; #if ENABLE_SPATIALIZER_API source.spatialize = true; #endif #if USE_OCULUS_AUDIO && !POOLING source.gameObject.AddComponent().SetParameters(ref source); #endif source.Play(); #if !POOLING // Division by zero prevented at the top of the function GameObject.Destroy(source.gameObject, clip.length * (1 / pitch) + DESTROY_AFTER_MARGIN_SECONDS); #endif return source; } /// /// Plays a random AudioClip from an array at a position /// /// An array of AudioClips /// Position at which it will be played /// /// /// How 'wide' the panning will be in 3d. 0 means it will be heard from only one direction, 180 is all around, 360 is opposite direction. /// The distance at which source volume will be 1 and falloff. Technically this is the sound intensity. /// Add source to mixer group /// A transform to attach source to. Position is still world position before parenting. /// Swaps the order of array so that the same clip never plays twice in succession. Changes the order of clips in the input array. /// public static AudioSource Play( this AudioClip[] clips, Vector3 position, float volume = 1, float pitch = 1, float spread = DEFAULT_SPREAD, float minDistance = DEFAULT_MIN_DISTANCE, AudioMixerGroup mixerGroup = null, Transform parent = null, bool noRepeatSwap = false) { Debug.Assert(clips != null, "NAudio: Clips array is null"); Debug.Assert(clips.Length != 0, "NAudio: No clips in array"); int i; if (clips.Length == 0) { i = 0; } else if (noRepeatSwap) { i = Random.Range(1, clips.Length); var tmp = clips[0]; clips[0] = clips[i]; clips[i] = tmp; i = 0; } else { i = Random.Range(0, clips.Length); } return Play(clips[i], position, volume, pitch, spread, minDistance, mixerGroup, parent); } #region 2D static AudioSource _source2D; static AudioSource source2D { get { if (!_source2D) _source2D = CreateSource(spatialBlend: 0, loop: false); return _source2D; } } public static AudioSource Play2D( this AudioClip clip, float volume = 1) { source2D.PlayOneShot(clip, volume); return source2D; } public static AudioSource Play2D( this AudioClip[] clips, float volume = 1) { source2D.PlayOneShot(clips[Random.Range(0, clips.Length)], volume); return source2D; } public static AudioSource Play2D( this AudioClip[] clips, float volume = 1, float pitch = 1, float spread = DEFAULT_SPREAD, float minDistance = DEFAULT_MIN_DISTANCE, AudioMixerGroup mixerGroup = null) { return Play(clips[Random.Range(0, clips.Length)], Vector3.zero, volume, pitch, spread, minDistance, mixerGroup); } #endregion // AUDIO SOURCE CREATION /// /// Creates an audio source with parameters /// /// Creates a source object as a child to this transform /// AudioClip that will be attached to this source and played when Play() is called /// /// /// /// /// /// /// /// /// public static AudioSource CreateSource( Transform at = null, AudioClip clip = null, float volume = 1, float pitch = 1, bool loop = true, bool playAtStart = false, float minDistance = DEFAULT_MIN_DISTANCE, float spread = DEFAULT_SPREAD, float spatialBlend = 1, AudioMixerGroup mixerGroup = null) { GameObject go = new GameObject("AudioLoop"); go.transform.parent = at; go.transform.localPosition = Vector3.zero; AudioSource source = go.AddComponent(); source.loop = loop; source.clip = clip; source.volume = volume; source.spatialBlend = spatialBlend; source.spread = spread; source.minDistance = minDistance; source.playOnAwake = playAtStart; source.outputAudioMixerGroup = mixerGroup; #if USE_OCULUS_AUDIO source.gameObject.AddComponent().SetParameters(ref source); #endif return source; } public static void PlayRandomTime(this AudioSource source) { if (source.clip == null) return; source.time = Random.Range(0, source.clip.length); source.Play(); } }