/*
Simple Sound Manager (c) 2016 Digital Ruby, LLC
http://www.digitalruby.com
Source code may no longer be redistributed in source format. Using this in apps and games is fine.
*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace DigitalRuby.SoundManagerNamespace
{
///
/// Provides an easy wrapper to looping audio sources with nice transitions for volume when starting and stopping
///
public class LoopingAudioSource
{
///
/// The audio source that is looping
///
public AudioSource AudioSource { get; private set; }
///
/// The target volume
///
public float TargetVolume { get; set; }
///
/// The original target volume - useful if the global sound volume changes you can still have the original target volume to multiply by.
///
public float OriginalTargetVolume { get; private set; }
///
/// Is this sound stopping?
///
public bool Stopping { get; private set; }
///
/// Whether the looping audio source persists in between scene changes
///
public bool Persist { get; private set; }
///
/// Tag for the looping audio source
///
public int Tag { get; set; }
private float startVolume;
private float startMultiplier;
private float stopMultiplier;
private float currentMultiplier;
private float timestamp;
private bool paused;
///
/// Constructor
///
/// Audio source, will be looped automatically
/// Start multiplier - seconds to reach peak sound
/// Stop multiplier - seconds to fade sound back to 0 volume when stopped
/// Whether to persist the looping audio source between scene changes
public LoopingAudioSource(AudioSource audioSource, float startMultiplier, float stopMultiplier, bool persist)
{
AudioSource = audioSource;
if (audioSource != null)
{
AudioSource.loop = true;
AudioSource.volume = 0.0f;
AudioSource.Stop();
}
this.startMultiplier = currentMultiplier = startMultiplier;
this.stopMultiplier = stopMultiplier;
Persist = persist;
}
///
/// Play this looping audio source
///
/// True if music, false if sound effect
public void Play(bool isMusic)
{
Play(1.0f, isMusic);
}
///
/// Play this looping audio source
///
/// Max volume
/// True if music, false if sound effect
/// True if played, false if already playing or error
public bool Play(float targetVolume, bool isMusic)
{
if (AudioSource != null)
{
AudioSource.volume = startVolume = (AudioSource.isPlaying ? AudioSource.volume : 0.0f);
AudioSource.loop = true;
currentMultiplier = startMultiplier;
OriginalTargetVolume = targetVolume;
TargetVolume = targetVolume;
Stopping = false;
timestamp = 0.0f;
if (!AudioSource.isPlaying)
{
AudioSource.Play();
return true;
}
}
return false;
}
///
/// Stop this looping audio source. The sound will fade out smoothly.
///
public void Stop()
{
if (AudioSource != null && AudioSource.isPlaying && !Stopping)
{
startVolume = AudioSource.volume;
TargetVolume = 0.0f;
currentMultiplier = stopMultiplier;
Stopping = true;
timestamp = 0.0f;
}
}
///
/// Pauses the looping audio source
///
public void Pause()
{
if (AudioSource != null && !paused && AudioSource.isPlaying)
{
paused = true;
AudioSource.Pause();
}
}
///
/// Resumes the looping audio source
///
public void Resume()
{
if (AudioSource != null && paused)
{
paused = false;
AudioSource.UnPause();
}
}
///
/// Update this looping audio source
///
/// True if finished playing, false otherwise
public bool Update()
{
if (AudioSource != null && AudioSource.isPlaying)
{
if ((AudioSource.volume = Mathf.Lerp(startVolume, TargetVolume, (timestamp += Time.deltaTime) / currentMultiplier)) == 0.0f && Stopping)
{
AudioSource.Stop();
Stopping = false;
return true;
}
else
{
return false;
}
}
return !paused;
}
}
///
/// Sound manager extension methods
///
public static class SoundManagerExtensions
{
///
/// Play an audio clip once using the global sound volume as a multiplier
///
/// AudioSource
/// Clip
public static void PlayOneShotSoundManaged(this AudioSource source, AudioClip clip)
{
SoundManager.PlayOneShotSound(source, clip, 1.0f);
}
///
/// Play an audio clip once using the global sound volume as a multiplier
///
/// AudioSource
/// Clip
/// Additional volume scale
public static void PlayOneShotSoundManaged(this AudioSource source, AudioClip clip, float volumeScale)
{
SoundManager.PlayOneShotSound(source, clip, volumeScale);
}
///
/// Play an audio clip once using the global music volume as a multiplier
///
/// AudioSource
/// Clip
public static void PlayOneShotMusicManaged(this AudioSource source, AudioClip clip)
{
SoundManager.PlayOneShotMusic(source, clip, 1.0f);
}
///
/// Play an audio clip once using the global music volume as a multiplier
///
/// AudioSource
/// Clip
/// Additional volume scale
public static void PlayOneShotMusicManaged(this AudioSource source, AudioClip clip, float volumeScale)
{
SoundManager.PlayOneShotMusic(source, clip, volumeScale);
}
///
/// Play a sound and loop it until stopped, using the global sound volume as a modifier
///
/// Audio source to play
public static void PlayLoopingSoundManaged(this AudioSource source)
{
SoundManager.PlayLoopingSound(source, 1.0f, 1.0f);
}
///
/// Play a sound and loop it until stopped, using the global sound volume as a modifier
///
/// Audio source to play
/// Additional volume scale
/// The number of seconds to fade in and out
public static void PlayLoopingSoundManaged(this AudioSource source, float volumeScale, float fadeSeconds)
{
SoundManager.PlayLoopingSound(source, volumeScale, fadeSeconds);
}
///
/// Play a music track and loop it until stopped, using the global music volume as a modifier
///
/// Audio source to play
public static void PlayLoopingMusicManaged(this AudioSource source)
{
SoundManager.PlayLoopingMusic(source, 1.0f, 1.0f, false);
}
///
/// Play a music track and loop it until stopped, using the global music volume as a modifier
///
/// Audio source to play
/// Additional volume scale
/// The number of seconds to fade in and out
/// Whether to persist the looping music between scene changes
public static void PlayLoopingMusicManaged(this AudioSource source, float volumeScale, float fadeSeconds, bool persist)
{
SoundManager.PlayLoopingMusic(source, volumeScale, fadeSeconds, persist);
}
///
/// Stop a looping sound
///
/// AudioSource to stop
public static void StopLoopingSoundManaged(this AudioSource source)
{
SoundManager.StopLoopingSound(source);
}
///
/// Stop a looping music track
///
/// AudioSource to stop
public static void StopLoopingMusicManaged(this AudioSource source)
{
SoundManager.StopLoopingMusic(source);
}
}
///
/// Do not add this script in the inspector. Just call the static methods from your own scripts or use the AudioSource extension methods.
///
public class SoundManager : MonoBehaviour
{
private static int persistTag = 0;
private static bool needsInitialize = true;
private static GameObject root;
private static SoundManager instance;
private static readonly List music = new List();
private static readonly List musicOneShot = new List();
private static readonly List sounds = new List();
private static readonly HashSet persistedSounds = new HashSet();
private static readonly Dictionary> soundsOneShot = new Dictionary>();
private static float soundVolume = 1.0f;
private static float musicVolume = 1.0f;
private static bool updated;
private static bool pauseSoundsOnApplicationPause = true;
///
/// Maximum number of the same audio clip to play at once
///
public static int MaxDuplicateAudioClips = 4;
///
/// Whether to stop sounds when a new level loads. Set to false for additive level loading.
///
public static bool StopSoundsOnLevelLoad = true;
private static void EnsureCreated()
{
if (needsInitialize)
{
needsInitialize = false;
root = new GameObject();
root.name = "DigitalRubySoundManager";
root.hideFlags = HideFlags.HideAndDontSave;
instance = root.AddComponent();
GameObject.DontDestroyOnLoad(root);
}
}
private void StopLoopingListOnLevelLoad(IList list)
{
for (int i = list.Count - 1; i >= 0; i--)
{
if (!list[i].Persist || !list[i].AudioSource.isPlaying)
{
list.RemoveAt(i);
}
}
}
private void ClearPersistedSounds()
{
foreach (LoopingAudioSource s in persistedSounds)
{
if (!s.AudioSource.isPlaying)
{
GameObject.Destroy(s.AudioSource.gameObject);
}
}
persistedSounds.Clear();
}
private void SceneManagerSceneLoaded(UnityEngine.SceneManagement.Scene s, UnityEngine.SceneManagement.LoadSceneMode m)
{
// Just in case this is called a bunch of times, we put a check here
if (updated && StopSoundsOnLevelLoad)
{
persistTag++;
updated = false;
Debug.LogWarningFormat("Reloaded level, new sound manager persist tag: {0}", persistTag);
StopNonLoopingSounds();
StopLoopingListOnLevelLoad(sounds);
StopLoopingListOnLevelLoad(music);
soundsOneShot.Clear();
ClearPersistedSounds();
}
}
private void Start()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded += SceneManagerSceneLoaded;
}
private void Update()
{
updated = true;
for (int i = sounds.Count - 1; i >= 0; i--)
{
if (sounds[i].Update())
{
sounds.RemoveAt(i);
}
}
for (int i = music.Count - 1; i >= 0; i--)
{
bool nullMusic = (music[i] == null || music[i].AudioSource == null);
if (nullMusic || music[i].Update())
{
if (!nullMusic && music[i].Tag != persistTag)
{
Debug.LogWarning("Destroying persisted audio from previous scene: " + music[i].AudioSource.gameObject.name);
// cleanup persisted audio from previous scenes
GameObject.Destroy(music[i].AudioSource.gameObject);
}
music.RemoveAt(i);
}
}
for (int i = musicOneShot.Count - 1; i >= 0; i--)
{
if (!musicOneShot[i].isPlaying)
{
musicOneShot.RemoveAt(i);
}
}
}
private void OnApplicationFocus(bool paused)
{
if (SoundManager.PauseSoundsOnApplicationPause)
{
if (paused)
{
SoundManager.ResumeAll();
}
else
{
SoundManager.PauseAll();
}
}
}
private static void UpdateSounds()
{
foreach (LoopingAudioSource s in sounds)
{
s.TargetVolume = s.OriginalTargetVolume * soundVolume;
}
}
private static void UpdateMusic()
{
foreach (LoopingAudioSource s in music)
{
if (!s.Stopping)
{
s.TargetVolume = s.OriginalTargetVolume * musicVolume;
}
}
foreach (AudioSource s in musicOneShot)
{
s.volume = musicVolume;
}
}
private static IEnumerator RemoveVolumeFromClip(AudioClip clip, float volume)
{
yield return new WaitForSeconds(clip.length);
List volumes;
if (soundsOneShot.TryGetValue(clip, out volumes))
{
volumes.Remove(volume);
}
}
private static void PlayLooping(AudioSource source, List sources, float volumeScale, float fadeSeconds, bool persist, bool stopAll)
{
EnsureCreated();
for (int i = sources.Count - 1; i >= 0; i--)
{
LoopingAudioSource s = sources[i];
if (s.AudioSource == source)
{
sources.RemoveAt(i);
}
if (stopAll)
{
s.Stop();
}
}
{
source.gameObject.SetActive(true);
LoopingAudioSource s = new LoopingAudioSource(source, fadeSeconds, fadeSeconds, persist);
s.Play(volumeScale, true);
s.Tag = persistTag;
sources.Add(s);
if (persist)
{
if (!source.gameObject.name.StartsWith("PersistedBySoundManager-"))
{
source.gameObject.name = "PersistedBySoundManager-" + source.gameObject.name + "-" + source.gameObject.GetInstanceID();
}
source.gameObject.transform.parent = null;
GameObject.DontDestroyOnLoad(source.gameObject);
persistedSounds.Add(s);
}
}
}
private static void StopLooping(AudioSource source, List sources)
{
foreach (LoopingAudioSource s in sources)
{
if (s.AudioSource == source)
{
s.Stop();
source = null;
break;
}
}
if (source != null)
{
source.Stop();
}
}
///
/// Play a sound once - sound volume will be affected by global sound volume
///
/// Audio source
/// Clip
public static void PlayOneShotSound(AudioSource source, AudioClip clip)
{
PlayOneShotSound(source, clip, 1.0f);
}
///
/// Play a sound once - sound volume will be affected by global sound volume
///
/// Audio source
/// Clip
/// Additional volume scale
public static void PlayOneShotSound(AudioSource source, AudioClip clip, float volumeScale)
{
EnsureCreated();
List volumes;
if (!soundsOneShot.TryGetValue(clip, out volumes))
{
volumes = new List();
soundsOneShot[clip] = volumes;
}
else if (volumes.Count == MaxDuplicateAudioClips)
{
return;
}
float minVolume = float.MaxValue;
float maxVolume = float.MinValue;
foreach (float volume in volumes)
{
minVolume = Mathf.Min(minVolume, volume);
maxVolume = Mathf.Max(maxVolume, volume);
}
float requestedVolume = (volumeScale * soundVolume);
if (maxVolume > 0.5f)
{
requestedVolume = (minVolume + maxVolume) / (float)(volumes.Count + 2);
}
// else requestedVolume can stay as is
volumes.Add(requestedVolume);
source.PlayOneShot(clip, requestedVolume);
instance.StartCoroutine(RemoveVolumeFromClip(clip, requestedVolume));
}
///
/// Play a looping sound - sound volume will be affected by global sound volume
///
/// Audio source to play looping
public static void PlayLoopingSound(AudioSource source)
{
PlayLoopingSound(source, 1.0f, 1.0f);
}
///
/// Play a looping sound - sound volume will be affected by global sound volume
///
/// Audio source to play looping
/// Additional volume scale
/// Seconds to fade in and out
public static void PlayLoopingSound(AudioSource source, float volumeScale, float fadeSeconds)
{
PlayLooping(source, sounds, volumeScale, fadeSeconds, false, false);
UpdateSounds();
}
///
/// Play a music track once - music volume will be affected by the global music volume
///
///
///
public static void PlayOneShotMusic(AudioSource source, AudioClip clip)
{
PlayOneShotMusic(source, clip, 1.0f);
}
///
/// Play a music track once - music volume will be affected by the global music volume
///
/// Audio source
/// Clip
/// Additional volume scale
public static void PlayOneShotMusic(AudioSource source, AudioClip clip, float volumeScale)
{
EnsureCreated();
int index = musicOneShot.IndexOf(source);
if (index >= 0)
{
musicOneShot.RemoveAt(index);
}
source.PlayOneShot(clip, volumeScale * musicVolume);
musicOneShot.Add(source);
}
///
/// Play a looping music track - music volume will be affected by the global music volume
///
/// Audio source
public static void PlayLoopingMusic(AudioSource source)
{
PlayLoopingMusic(source, 1.0f, 1.0f, false);
}
///
/// Play a looping music track - music volume will be affected by the global music volume
///
/// Audio source
/// Additional volume scale
/// Seconds to fade in and out
/// Whether to persist the looping music between scene changes
public static void PlayLoopingMusic(AudioSource source, float volumeScale, float fadeSeconds, bool persist)
{
PlayLooping(source, music, volumeScale, fadeSeconds, persist, true);
UpdateMusic();
}
///
/// Stop looping a sound for an audio source
///
/// Audio source to stop looping sound for
public static void StopLoopingSound(AudioSource source)
{
StopLooping(source, sounds);
}
///
/// Stop looping music for an audio source
///
/// Audio source to stop looping music for
public static void StopLoopingMusic(AudioSource source)
{
StopLooping(source, music);
}
///
/// Stop all looping sounds, music, and music one shots. Non-looping sounds are not stopped.
///
public static void StopAll()
{
StopAllLoopingSounds();
StopNonLoopingSounds();
}
///
/// Stop all looping sounds and music. Music one shots and non-looping sounds are not stopped.
///
public static void StopAllLoopingSounds()
{
foreach (LoopingAudioSource s in sounds)
{
s.Stop();
}
foreach (LoopingAudioSource s in music)
{
s.Stop();
}
}
///
/// Stop all non-looping sounds. Looping sounds and looping music are not stopped.
///
public static void StopNonLoopingSounds()
{
foreach (AudioSource s in musicOneShot)
{
s.Stop();
}
}
///
/// Pause all sounds
///
public static void PauseAll()
{
foreach (LoopingAudioSource s in sounds)
{
s.Pause();
}
foreach (LoopingAudioSource s in music)
{
s.Pause();
}
}
///
/// Unpause and resume all sounds
///
public static void ResumeAll()
{
foreach (LoopingAudioSource s in sounds)
{
s.Resume();
}
foreach (LoopingAudioSource s in music)
{
s.Resume();
}
}
///
/// Global music volume multiplier
///
public static float MusicVolume
{
get { return musicVolume; }
set
{
if (value != musicVolume)
{
musicVolume = value;
UpdateMusic();
}
}
}
///
/// Global sound volume multiplier
///
public static float SoundVolume
{
get { return soundVolume; }
set
{
if (value != soundVolume)
{
soundVolume = value;
UpdateSounds();
}
}
}
///
/// Whether to pause sounds when the application is paused and resume them when the application is activated.
/// Player option "Run In Background" must be selected to enable this. Default is true.
///
public static bool PauseSoundsOnApplicationPause
{
get { return pauseSoundsOnApplicationPause; }
set { pauseSoundsOnApplicationPause = value; }
}
}
}