├── .gitignore ├── Assets ├── _Project.meta └── _Project │ ├── Prefabs.meta │ ├── Prefabs │ ├── SoundEmitter.prefab │ └── SoundEmitter.prefab.meta │ ├── Scripts.meta │ ├── Scripts │ ├── AudioSystem.meta │ └── AudioSystem │ │ ├── AudioExtensions.cs │ │ ├── AudioExtensions.cs.meta │ │ ├── GameObjectExtensions.cs │ │ ├── GameObjectExtensions.cs.meta │ │ ├── MusicManager.cs │ │ ├── MusicManager.cs.meta │ │ ├── PersistentSingleton.cs │ │ ├── PersistentSingleton.cs.meta │ │ ├── SoundBuilder.cs │ │ ├── SoundBuilder.cs.meta │ │ ├── SoundData.cs │ │ ├── SoundData.cs.meta │ │ ├── SoundEmitter.cs │ │ ├── SoundEmitter.cs.meta │ │ ├── SoundManager.cs │ │ └── SoundManager.cs.meta │ ├── Settings.meta │ └── Settings │ ├── Master.mixer │ └── Master.mixer.meta ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uildFullScreen/ 10 | /[Bb]uilds/ 11 | /[Ll]ogs/ 12 | /[Uu]ser[Ss]ettings/ 13 | /CCDBuildData/ 14 | 15 | # Ignore everything under Assets except the _Project folder 16 | Assets/* 17 | !Assets/_Project* 18 | 19 | # MemoryCaptures can get excessive in size. 20 | # They also could contain extremely sensitive data 21 | /[Mm]emoryCaptures/ 22 | 23 | # Recordings can get excessive in size 24 | /[Rr]ecordings/ 25 | 26 | # Uncomment this line if you wish to ignore the asset store tools plugin 27 | # /[Aa]ssets/AssetStoreTools* 28 | 29 | # Autogenerated Jetbrains Rider plugin 30 | /[Aa]ssets/Plugins/Editor/JetBrains* 31 | 32 | # Visual Studio cache directory 33 | .vs/ 34 | 35 | # Gradle cache directory 36 | .gradle/ 37 | 38 | # Autogenerated VS/MD/Consulo solution and project files 39 | ExportedObj/ 40 | .consulo/ 41 | *.csproj 42 | *.unityproj 43 | *.sln 44 | *.suo 45 | *.tmp 46 | *.user 47 | *.userprefs 48 | *.pidb 49 | *.booproj 50 | *.svd 51 | *.pdb 52 | *.mdb 53 | *.opendb 54 | *.VC.db 55 | 56 | # Unity3D generated meta files 57 | *.pidb.meta 58 | *.pdb.meta 59 | *.mdb.meta 60 | 61 | # Unity3D generated file on crash reports 62 | sysinfo.txt 63 | 64 | # Builds 65 | *.apk 66 | *.aab 67 | *.unitypackage 68 | *.app 69 | 70 | # Crashlytics generated file 71 | crashlytics-build.properties 72 | 73 | # Packed Addressables 74 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 75 | 76 | # Temporary auto-generated Android Assets 77 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 78 | /[Aa]ssets/[Ss]treamingAssets/aa/* 79 | 80 | # Custom 81 | Assets/SceneDependencyCache* 82 | Assets/NetCodeGenerated* 83 | .idea/ 84 | .DS_Store 85 | RiderScriptEditorPersistedState.asset 86 | Packages/packages-lock.json 87 | -------------------------------------------------------------------------------- /Assets/_Project.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b82a81def5e3b1c4bac8d990129a8c22 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/_Project/Prefabs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c761a737a2c517f47b8703182b964c3f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/_Project/Prefabs/SoundEmitter.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &6090579874278877196 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 2192071873588530787} 12 | - component: {fileID: 2814089227792753307} 13 | - component: {fileID: 8400026968397468002} 14 | m_Layer: 0 15 | m_Name: SoundEmitter 16 | m_TagString: Untagged 17 | m_Icon: {fileID: 0} 18 | m_NavMeshLayer: 0 19 | m_StaticEditorFlags: 0 20 | m_IsActive: 1 21 | --- !u!4 &2192071873588530787 22 | Transform: 23 | m_ObjectHideFlags: 0 24 | m_CorrespondingSourceObject: {fileID: 0} 25 | m_PrefabInstance: {fileID: 0} 26 | m_PrefabAsset: {fileID: 0} 27 | m_GameObject: {fileID: 6090579874278877196} 28 | serializedVersion: 2 29 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 30 | m_LocalPosition: {x: -9.367252, y: 9.663172, z: -26.63866} 31 | m_LocalScale: {x: 1, y: 1, z: 1} 32 | m_ConstrainProportionsScale: 0 33 | m_Children: [] 34 | m_Father: {fileID: 0} 35 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 36 | --- !u!82 &2814089227792753307 37 | AudioSource: 38 | m_ObjectHideFlags: 0 39 | m_CorrespondingSourceObject: {fileID: 0} 40 | m_PrefabInstance: {fileID: 0} 41 | m_PrefabAsset: {fileID: 0} 42 | m_GameObject: {fileID: 6090579874278877196} 43 | m_Enabled: 1 44 | serializedVersion: 4 45 | OutputAudioMixerGroup: {fileID: 0} 46 | m_audioClip: {fileID: 0} 47 | m_Resource: {fileID: 0} 48 | m_PlayOnAwake: 1 49 | m_Volume: 1 50 | m_Pitch: 1 51 | Loop: 0 52 | Mute: 0 53 | Spatialize: 0 54 | SpatializePostEffects: 0 55 | Priority: 128 56 | DopplerLevel: 1 57 | MinDistance: 1 58 | MaxDistance: 500 59 | Pan2D: 0 60 | rolloffMode: 0 61 | BypassEffects: 0 62 | BypassListenerEffects: 0 63 | BypassReverbZones: 0 64 | rolloffCustomCurve: 65 | serializedVersion: 2 66 | m_Curve: 67 | - serializedVersion: 3 68 | time: 0 69 | value: 1 70 | inSlope: 0 71 | outSlope: 0 72 | tangentMode: 0 73 | weightedMode: 0 74 | inWeight: 0.33333334 75 | outWeight: 0.33333334 76 | - serializedVersion: 3 77 | time: 1 78 | value: 0 79 | inSlope: 0 80 | outSlope: 0 81 | tangentMode: 0 82 | weightedMode: 0 83 | inWeight: 0.33333334 84 | outWeight: 0.33333334 85 | m_PreInfinity: 2 86 | m_PostInfinity: 2 87 | m_RotationOrder: 4 88 | panLevelCustomCurve: 89 | serializedVersion: 2 90 | m_Curve: 91 | - serializedVersion: 3 92 | time: 0 93 | value: 0 94 | inSlope: 0 95 | outSlope: 0 96 | tangentMode: 0 97 | weightedMode: 0 98 | inWeight: 0.33333334 99 | outWeight: 0.33333334 100 | m_PreInfinity: 2 101 | m_PostInfinity: 2 102 | m_RotationOrder: 4 103 | spreadCustomCurve: 104 | serializedVersion: 2 105 | m_Curve: 106 | - serializedVersion: 3 107 | time: 0 108 | value: 0 109 | inSlope: 0 110 | outSlope: 0 111 | tangentMode: 0 112 | weightedMode: 0 113 | inWeight: 0.33333334 114 | outWeight: 0.33333334 115 | m_PreInfinity: 2 116 | m_PostInfinity: 2 117 | m_RotationOrder: 4 118 | reverbZoneMixCustomCurve: 119 | serializedVersion: 2 120 | m_Curve: 121 | - serializedVersion: 3 122 | time: 0 123 | value: 1 124 | inSlope: 0 125 | outSlope: 0 126 | tangentMode: 0 127 | weightedMode: 0 128 | inWeight: 0.33333334 129 | outWeight: 0.33333334 130 | m_PreInfinity: 2 131 | m_PostInfinity: 2 132 | m_RotationOrder: 4 133 | --- !u!114 &8400026968397468002 134 | MonoBehaviour: 135 | m_ObjectHideFlags: 0 136 | m_CorrespondingSourceObject: {fileID: 0} 137 | m_PrefabInstance: {fileID: 0} 138 | m_PrefabAsset: {fileID: 0} 139 | m_GameObject: {fileID: 6090579874278877196} 140 | m_Enabled: 1 141 | m_EditorHideFlags: 0 142 | m_Script: {fileID: 11500000, guid: d33fa71f5c76490799f6b2f371b3e18d, type: 3} 143 | m_Name: 144 | m_EditorClassIdentifier: 145 | -------------------------------------------------------------------------------- /Assets/_Project/Prefabs/SoundEmitter.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c24cda77957e7147ae757011cf75f47 3 | PrefabImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a6120bd7612c214fa782bbca1220aac 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e0bd7f2cd5d96d4a97070512e781767 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/AudioExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace AudioSystem { 4 | public static class AudioExtensions { 5 | /// 6 | /// Converts a float value representing a volume slider position into a logarithmic volume, 7 | /// giving us a smoother and more natural-sounding progression when a volume slider is moved. 8 | /// The math here performs the following steps: 9 | /// - Ensures the slider value is equal to or greater than 0.0001 to avoid passing 0 to the logarithm function. 10 | /// - Takes the base-10 logarithm of the slider value. 11 | /// - Multiplies the result by 20. In audio engineering, a change of 1 unit in a dB scale is approximately equivalent 12 | /// to what the human ear perceives as a doubling or halving of the volume, hence the multiplication by 20. 13 | /// 14 | /// This method is useful for normalizing UI Volume Sliders used with Unity's Audio Mixer. 15 | /// 16 | public static float ToLogarithmicVolume(this float sliderValue) { 17 | return Mathf.Log10(Mathf.Max(sliderValue, 0.0001f)) * 20; 18 | } 19 | 20 | /// 21 | /// Given a fraction in the range of [0, 1], convert it to a logarithmic scale (also in range [0, 1]) 22 | /// that mimics the way we hear volume (since human perception of sound volume is logarithmic). 23 | /// The math here performs the following steps: 24 | /// - Within the Log10 function, we're adding 9 times the original fraction to 1 before taking the logarithm. 25 | /// This makes sure that the fraction is smoothly scaled to our logarithmic curve, and it fits the range [0, 1]. 26 | /// - Takes the base-10 logarithm of the interpolated fraction. 27 | /// - Divides the result by Log10(10) simply to normalize the result and ensure it fits within the [0, 1] range, 28 | /// since as we know the input to Log10 function can vary between 1 and 10 after the interpolation. 29 | /// 30 | /// This method is useful for improved fading effects between Audio Clips. 31 | /// 32 | public static float ToLogarithmicFraction(this float fraction) { 33 | return Mathf.Log10(1 + 9 * fraction) / Mathf.Log10(10); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/AudioExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 150fe40b5f5e44b5b9b1ed22b9cfec0e 3 | timeCreated: 1717724629 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/GameObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace AudioSystem { 4 | public static class GameObjectExtensions { 5 | public static T GetOrAdd(this GameObject gameObject) where T : Component { 6 | T component = gameObject.GetComponent(); 7 | if (!component) component = gameObject.AddComponent(); 8 | 9 | return component; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/GameObjectExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5226199e15d544899e4f4179166fe48e 3 | timeCreated: 1717721285 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/MusicManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.Audio; 4 | 5 | namespace AudioSystem { 6 | [RequireComponent(typeof(MusicManager))] 7 | public class MusicManager : PersistentSingleton { 8 | const float crossFadeTime = 1.0f; 9 | float fading; 10 | AudioSource current; 11 | AudioSource previous; 12 | readonly Queue playlist = new(); 13 | 14 | [SerializeField] List initialPlaylist; 15 | [SerializeField] AudioMixerGroup musicMixerGroup; 16 | 17 | void Start() { 18 | foreach (var clip in initialPlaylist) { 19 | AddToPlaylist(clip); 20 | } 21 | } 22 | 23 | public void AddToPlaylist(AudioClip clip) { 24 | playlist.Enqueue(clip); 25 | if (current == null && previous == null) { 26 | PlayNextTrack(); 27 | } 28 | } 29 | 30 | public void Clear() => playlist.Clear(); 31 | 32 | public void PlayNextTrack() { 33 | if (playlist.TryDequeue(out AudioClip nextTrack)) { 34 | Play(nextTrack); 35 | } 36 | } 37 | 38 | public void Play(AudioClip clip) { 39 | if (current && current.clip == clip) return; 40 | 41 | if (previous) { 42 | Destroy(previous); 43 | previous = null; 44 | } 45 | 46 | previous = current; 47 | 48 | current = gameObject.GetOrAdd(); 49 | current.clip = clip; 50 | current.outputAudioMixerGroup = musicMixerGroup; // Set mixer group 51 | current.loop = false; // For playlist functionality, we want tracks to play once 52 | current.volume = 0; 53 | current.bypassListenerEffects = true; 54 | current.Play(); 55 | 56 | fading = 0.001f; 57 | } 58 | 59 | void Update() { 60 | HandleCrossFade(); 61 | 62 | if (current && !current.isPlaying && playlist.Count > 0) { 63 | PlayNextTrack(); 64 | } 65 | } 66 | 67 | void HandleCrossFade() { 68 | if (fading <= 0f) return; 69 | 70 | fading += Time.deltaTime; 71 | 72 | float fraction = Mathf.Clamp01(fading / crossFadeTime); 73 | 74 | // Logarithmic fade 75 | float logFraction = fraction.ToLogarithmicFraction(); 76 | 77 | if (previous) previous.volume = 1.0f - logFraction; 78 | if (current) current.volume = logFraction; 79 | 80 | if (fraction >= 1) { 81 | fading = 0.0f; 82 | if (previous) { 83 | Destroy(previous); 84 | previous = null; 85 | } 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/MusicManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 48a80ecc506b456aabc8a9fe88ddecd8 3 | timeCreated: 1717724621 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/PersistentSingleton.cs: -------------------------------------------------------------------------------- 1 | namespace AudioSystem { 2 | using UnityEngine; 3 | 4 | public class PersistentSingleton : MonoBehaviour where T : Component { 5 | public bool AutoUnparentOnAwake = true; 6 | 7 | protected static T instance; 8 | 9 | public static bool HasInstance => instance != null; 10 | public static T TryGetInstance() => HasInstance ? instance : null; 11 | 12 | public static T Instance { 13 | get { 14 | if (instance == null) { 15 | instance = FindAnyObjectByType(); 16 | if (instance == null) { 17 | var go = new GameObject(typeof(T).Name + " Auto-Generated"); 18 | instance = go.AddComponent(); 19 | } 20 | } 21 | 22 | return instance; 23 | } 24 | } 25 | 26 | /// 27 | /// Make sure to call base.Awake() in override if you need awake. 28 | /// 29 | protected virtual void Awake() { 30 | InitializeSingleton(); 31 | } 32 | 33 | protected virtual void InitializeSingleton() { 34 | if (!Application.isPlaying) return; 35 | 36 | if (AutoUnparentOnAwake) { 37 | transform.SetParent(null); 38 | } 39 | 40 | if (instance == null) { 41 | instance = this as T; 42 | DontDestroyOnLoad(gameObject); 43 | } 44 | else { 45 | if (instance != this) { 46 | Destroy(gameObject); 47 | } 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/PersistentSingleton.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d9bba77a0b0743d892ec798abeadf338 3 | timeCreated: 1717721453 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundBuilder.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace AudioSystem { 4 | public class SoundBuilder { 5 | readonly SoundManager soundManager; 6 | Vector3 position = Vector3.zero; 7 | bool randomPitch; 8 | 9 | public SoundBuilder(SoundManager soundManager) { 10 | this.soundManager = soundManager; 11 | } 12 | 13 | public SoundBuilder WithPosition(Vector3 position) { 14 | this.position = position; 15 | return this; 16 | } 17 | 18 | public SoundBuilder WithRandomPitch() { 19 | this.randomPitch = true; 20 | return this; 21 | } 22 | 23 | public void Play(SoundData soundData) { 24 | if (soundData == null) { 25 | Debug.LogError("SoundData is null"); 26 | return; 27 | } 28 | 29 | if (!soundManager.CanPlaySound(soundData)) return; 30 | 31 | SoundEmitter soundEmitter = soundManager.Get(); 32 | soundEmitter.Initialize(soundData); 33 | soundEmitter.transform.position = position; 34 | soundEmitter.transform.parent = soundManager.transform; 35 | 36 | if (randomPitch) { 37 | soundEmitter.WithRandomPitch(); 38 | } 39 | 40 | if (soundData.frequentSound) { 41 | soundEmitter.Node = soundManager.FrequentSoundEmitters.AddLast(soundEmitter); 42 | } 43 | 44 | soundEmitter.Play(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bfd4612fd48042b7bd4306e6221e3fc3 3 | timeCreated: 1717873143 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Audio; 4 | 5 | namespace AudioSystem { 6 | [Serializable] 7 | public class SoundData { 8 | public AudioClip clip; 9 | public AudioMixerGroup mixerGroup; 10 | public bool loop; 11 | public bool playOnAwake; 12 | public bool frequentSound; 13 | 14 | public bool mute; 15 | public bool bypassEffects; 16 | public bool bypassListenerEffects; 17 | public bool bypassReverbZones; 18 | 19 | public int priority = 128; 20 | public float volume = 1f; 21 | public float pitch = 1f; 22 | public float panStereo; 23 | public float spatialBlend; 24 | public float reverbZoneMix = 1f; 25 | public float dopplerLevel = 1f; 26 | public float spread; 27 | 28 | public float minDistance = 1f; 29 | public float maxDistance = 500f; 30 | 31 | public bool ignoreListenerVolume; 32 | public bool ignoreListenerPause; 33 | 34 | public AudioRolloffMode rolloffMode = AudioRolloffMode.Logarithmic; 35 | } 36 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f82bef45c934bcea274627533144cd0 3 | timeCreated: 1717724504 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundEmitter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using Random = UnityEngine.Random; 5 | 6 | namespace AudioSystem { 7 | [RequireComponent(typeof(AudioSource))] 8 | public class SoundEmitter : MonoBehaviour { 9 | public SoundData Data { get; private set; } 10 | public LinkedListNode Node { get; set; } 11 | 12 | AudioSource audioSource; 13 | Coroutine playingCoroutine; 14 | 15 | void Awake() { 16 | audioSource = gameObject.GetOrAdd(); 17 | } 18 | 19 | public void Initialize(SoundData data) { 20 | Data = data; 21 | audioSource.clip = data.clip; 22 | audioSource.outputAudioMixerGroup = data.mixerGroup; 23 | audioSource.loop = data.loop; 24 | audioSource.playOnAwake = data.playOnAwake; 25 | 26 | audioSource.mute = data.mute; 27 | audioSource.bypassEffects = data.bypassEffects; 28 | audioSource.bypassListenerEffects = data.bypassListenerEffects; 29 | audioSource.bypassReverbZones = data.bypassReverbZones; 30 | 31 | audioSource.priority = data.priority; 32 | audioSource.volume = data.volume; 33 | audioSource.pitch = data.pitch; 34 | audioSource.panStereo = data.panStereo; 35 | audioSource.spatialBlend = data.spatialBlend; 36 | audioSource.reverbZoneMix = data.reverbZoneMix; 37 | audioSource.dopplerLevel = data.dopplerLevel; 38 | audioSource.spread = data.spread; 39 | 40 | audioSource.minDistance = data.minDistance; 41 | audioSource.maxDistance = data.maxDistance; 42 | 43 | audioSource.ignoreListenerVolume = data.ignoreListenerVolume; 44 | audioSource.ignoreListenerPause = data.ignoreListenerPause; 45 | 46 | audioSource.rolloffMode = data.rolloffMode; 47 | } 48 | 49 | public void Play() { 50 | if (playingCoroutine != null) { 51 | StopCoroutine(playingCoroutine); 52 | } 53 | 54 | audioSource.Play(); 55 | playingCoroutine = StartCoroutine(WaitForSoundToEnd()); 56 | } 57 | 58 | IEnumerator WaitForSoundToEnd() { 59 | yield return new WaitWhile(() => audioSource.isPlaying); 60 | Stop(); 61 | } 62 | 63 | public void Stop() { 64 | if (playingCoroutine != null) { 65 | StopCoroutine(playingCoroutine); 66 | playingCoroutine = null; 67 | } 68 | 69 | audioSource.Stop(); 70 | SoundManager.Instance.ReturnToPool(this); 71 | } 72 | 73 | public void WithRandomPitch(float min = -0.05f, float max = 0.05f) { 74 | audioSource.pitch += Random.Range(min, max); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundEmitter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d33fa71f5c76490799f6b2f371b3e18d 3 | timeCreated: 1717871203 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.Pool; 4 | 5 | namespace AudioSystem { 6 | public class SoundManager : PersistentSingleton { 7 | IObjectPool soundEmitterPool; 8 | readonly List activeSoundEmitters = new(); 9 | public readonly LinkedList FrequentSoundEmitters = new(); 10 | 11 | [SerializeField] SoundEmitter soundEmitterPrefab; 12 | [SerializeField] bool collectionCheck = true; 13 | [SerializeField] int defaultCapacity = 10; 14 | [SerializeField] int maxPoolSize = 100; 15 | [SerializeField] int maxSoundInstances = 30; 16 | 17 | void Start() { 18 | InitializePool(); 19 | } 20 | 21 | public SoundBuilder CreateSoundBuilder() => new SoundBuilder(this); 22 | 23 | public bool CanPlaySound(SoundData data) { 24 | if (!data.frequentSound) return true; 25 | 26 | if (FrequentSoundEmitters.Count >= maxSoundInstances) { 27 | try { 28 | FrequentSoundEmitters.First.Value.Stop(); 29 | return true; 30 | } catch { 31 | Debug.Log("SoundEmitter is already released"); 32 | } 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | public SoundEmitter Get() { 39 | return soundEmitterPool.Get(); 40 | } 41 | 42 | public void ReturnToPool(SoundEmitter soundEmitter) { 43 | soundEmitterPool.Release(soundEmitter); 44 | } 45 | 46 | public void StopAll() { 47 | foreach (var soundEmitter in activeSoundEmitters) { 48 | soundEmitter.Stop(); 49 | } 50 | 51 | FrequentSoundEmitters.Clear(); 52 | } 53 | 54 | void InitializePool() { 55 | soundEmitterPool = new ObjectPool( 56 | CreateSoundEmitter, 57 | OnTakeFromPool, 58 | OnReturnedToPool, 59 | OnDestroyPoolObject, 60 | collectionCheck, 61 | defaultCapacity, 62 | maxPoolSize); 63 | } 64 | 65 | SoundEmitter CreateSoundEmitter() { 66 | var soundEmitter = Instantiate(soundEmitterPrefab); 67 | soundEmitter.gameObject.SetActive(false); 68 | return soundEmitter; 69 | } 70 | 71 | void OnTakeFromPool(SoundEmitter soundEmitter) { 72 | soundEmitter.gameObject.SetActive(true); 73 | activeSoundEmitters.Add(soundEmitter); 74 | } 75 | 76 | void OnReturnedToPool(SoundEmitter soundEmitter) { 77 | if (soundEmitter.Node != null) { 78 | FrequentSoundEmitters.Remove(soundEmitter.Node); 79 | soundEmitter.Node = null; 80 | } 81 | soundEmitter.gameObject.SetActive(false); 82 | activeSoundEmitters.Remove(soundEmitter); 83 | } 84 | 85 | void OnDestroyPoolObject(SoundEmitter soundEmitter) { 86 | Destroy(soundEmitter.gameObject); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AudioSystem/SoundManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b3a6f7fee90847f8a6abe3fa2964e45b 3 | timeCreated: 1717871210 -------------------------------------------------------------------------------- /Assets/_Project/Settings.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 709f11a7f3c4041caa4ef136ea32d874 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/_Project/Settings/Master.mixer: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!244 &-3479513970103536478 4 | AudioMixerEffectController: 5 | m_ObjectHideFlags: 3 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_Name: 10 | m_EffectID: 0b3470fcc3bd6984d85246c617642591 11 | m_EffectName: Attenuation 12 | m_MixLevel: c963f5a374b29484584c6a060b01ff9c 13 | m_Parameters: [] 14 | m_SendTarget: {fileID: 0} 15 | m_EnableWetMix: 0 16 | m_Bypass: 0 17 | --- !u!243 &-1655472622737748322 18 | AudioMixerGroupController: 19 | m_ObjectHideFlags: 0 20 | m_CorrespondingSourceObject: {fileID: 0} 21 | m_PrefabInstance: {fileID: 0} 22 | m_PrefabAsset: {fileID: 0} 23 | m_Name: Environment 24 | m_AudioMixer: {fileID: 24100000} 25 | m_GroupID: 5f940843b2845634e950dbdb832a3993 26 | m_Children: [] 27 | m_Volume: 97d4cf2c5e4004146bd641ef1179f546 28 | m_Pitch: 465296ddd7150674c80051b6eee5a863 29 | m_Send: 00000000000000000000000000000000 30 | m_Effects: 31 | - {fileID: 9079211407069808162} 32 | m_UserColorIndex: 0 33 | m_Mute: 0 34 | m_Solo: 0 35 | m_BypassEffects: 0 36 | --- !u!244 &-84163514445764924 37 | AudioMixerEffectController: 38 | m_ObjectHideFlags: 3 39 | m_CorrespondingSourceObject: {fileID: 0} 40 | m_PrefabInstance: {fileID: 0} 41 | m_PrefabAsset: {fileID: 0} 42 | m_Name: 43 | m_EffectID: c122be526e938eb4987f8532f3bb0085 44 | m_EffectName: Attenuation 45 | m_MixLevel: ab6a7895d67758f4080cbb8fe437145c 46 | m_Parameters: [] 47 | m_SendTarget: {fileID: 0} 48 | m_EnableWetMix: 0 49 | m_Bypass: 0 50 | --- !u!241 &24100000 51 | AudioMixerController: 52 | m_ObjectHideFlags: 0 53 | m_CorrespondingSourceObject: {fileID: 0} 54 | m_PrefabInstance: {fileID: 0} 55 | m_PrefabAsset: {fileID: 0} 56 | m_Name: Master 57 | m_OutputGroup: {fileID: 0} 58 | m_MasterGroup: {fileID: 24300002} 59 | m_Snapshots: 60 | - {fileID: 24500006} 61 | m_StartSnapshot: {fileID: 24500006} 62 | m_SuspendThreshold: -80 63 | m_EnableSuspend: 1 64 | m_UpdateMode: 0 65 | m_ExposedParameters: [] 66 | m_AudioMixerGroupViews: 67 | - guids: 68 | - f1d0727d73b1976469bdfebe3e9918e4 69 | - 1c6c1fd052003a74c8b9ee226ffd5fa7 70 | - 9247e594eae80244db9532a0a1844269 71 | - 5f940843b2845634e950dbdb832a3993 72 | - 967464823a400124a8285efc5a205328 73 | name: View 74 | m_CurrentViewIndex: 0 75 | m_TargetSnapshot: {fileID: 24500006} 76 | --- !u!243 &24300002 77 | AudioMixerGroupController: 78 | m_ObjectHideFlags: 0 79 | m_CorrespondingSourceObject: {fileID: 0} 80 | m_PrefabInstance: {fileID: 0} 81 | m_PrefabAsset: {fileID: 0} 82 | m_Name: Master 83 | m_AudioMixer: {fileID: 24100000} 84 | m_GroupID: f1d0727d73b1976469bdfebe3e9918e4 85 | m_Children: 86 | - {fileID: -1655472622737748322} 87 | - {fileID: 4796280562796518393} 88 | - {fileID: 1468588115202338304} 89 | - {fileID: 3609905348827210527} 90 | m_Volume: ab0de6f887707724a83d7720af802470 91 | m_Pitch: e3cc1ec9d207c13468f5e80bb28e1728 92 | m_Send: 00000000000000000000000000000000 93 | m_Effects: 94 | - {fileID: 24400004} 95 | m_UserColorIndex: 0 96 | m_Mute: 0 97 | m_Solo: 0 98 | m_BypassEffects: 0 99 | --- !u!244 &24400004 100 | AudioMixerEffectController: 101 | m_ObjectHideFlags: 3 102 | m_CorrespondingSourceObject: {fileID: 0} 103 | m_PrefabInstance: {fileID: 0} 104 | m_PrefabAsset: {fileID: 0} 105 | m_Name: 106 | m_EffectID: aa093af1de25e374da0968f8d13467b9 107 | m_EffectName: Attenuation 108 | m_MixLevel: 7457614fea7b6354cbd2f64313913fc0 109 | m_Parameters: [] 110 | m_SendTarget: {fileID: 0} 111 | m_EnableWetMix: 0 112 | m_Bypass: 0 113 | --- !u!245 &24500006 114 | AudioMixerSnapshotController: 115 | m_ObjectHideFlags: 0 116 | m_CorrespondingSourceObject: {fileID: 0} 117 | m_PrefabInstance: {fileID: 0} 118 | m_PrefabAsset: {fileID: 0} 119 | m_Name: Snapshot 120 | m_AudioMixer: {fileID: 24100000} 121 | m_SnapshotID: 15d5913235d5a004bafdb1b5000e98f3 122 | m_FloatValues: {} 123 | m_TransitionOverrides: {} 124 | --- !u!244 &304446381736414582 125 | AudioMixerEffectController: 126 | m_ObjectHideFlags: 3 127 | m_CorrespondingSourceObject: {fileID: 0} 128 | m_PrefabInstance: {fileID: 0} 129 | m_PrefabAsset: {fileID: 0} 130 | m_Name: 131 | m_EffectID: ab8246296f881d74aa2a94da7d520c37 132 | m_EffectName: Attenuation 133 | m_MixLevel: fa238ed87503b664f974daf58bd142fd 134 | m_Parameters: [] 135 | m_SendTarget: {fileID: 0} 136 | m_EnableWetMix: 0 137 | m_Bypass: 0 138 | --- !u!243 &1468588115202338304 139 | AudioMixerGroupController: 140 | m_ObjectHideFlags: 0 141 | m_CorrespondingSourceObject: {fileID: 0} 142 | m_PrefabInstance: {fileID: 0} 143 | m_PrefabAsset: {fileID: 0} 144 | m_Name: Music 145 | m_AudioMixer: {fileID: 24100000} 146 | m_GroupID: 9247e594eae80244db9532a0a1844269 147 | m_Children: [] 148 | m_Volume: 9d5c9af6275d61a41b67c2301c1cd214 149 | m_Pitch: 79eb175c1fc9623438ca6264dd55422f 150 | m_Send: 00000000000000000000000000000000 151 | m_Effects: 152 | - {fileID: -84163514445764924} 153 | m_UserColorIndex: 0 154 | m_Mute: 0 155 | m_Solo: 0 156 | m_BypassEffects: 0 157 | --- !u!243 &3609905348827210527 158 | AudioMixerGroupController: 159 | m_ObjectHideFlags: 0 160 | m_CorrespondingSourceObject: {fileID: 0} 161 | m_PrefabInstance: {fileID: 0} 162 | m_PrefabAsset: {fileID: 0} 163 | m_Name: UI/Menu 164 | m_AudioMixer: {fileID: 24100000} 165 | m_GroupID: 967464823a400124a8285efc5a205328 166 | m_Children: [] 167 | m_Volume: 6803aef1b986bcb4888da01c154ba7be 168 | m_Pitch: 6c96a6952c045ce4996487f7c93904b3 169 | m_Send: 00000000000000000000000000000000 170 | m_Effects: 171 | - {fileID: 304446381736414582} 172 | m_UserColorIndex: 0 173 | m_Mute: 0 174 | m_Solo: 0 175 | m_BypassEffects: 0 176 | --- !u!243 &4796280562796518393 177 | AudioMixerGroupController: 178 | m_ObjectHideFlags: 0 179 | m_CorrespondingSourceObject: {fileID: 0} 180 | m_PrefabInstance: {fileID: 0} 181 | m_PrefabAsset: {fileID: 0} 182 | m_Name: SFX 183 | m_AudioMixer: {fileID: 24100000} 184 | m_GroupID: 1c6c1fd052003a74c8b9ee226ffd5fa7 185 | m_Children: [] 186 | m_Volume: 6279395897c5eef498a645c2fe8cc710 187 | m_Pitch: 155acbbf514843c42a23d855d05e7395 188 | m_Send: 00000000000000000000000000000000 189 | m_Effects: 190 | - {fileID: -3479513970103536478} 191 | m_UserColorIndex: 0 192 | m_Mute: 0 193 | m_Solo: 0 194 | m_BypassEffects: 0 195 | --- !u!244 &9079211407069808162 196 | AudioMixerEffectController: 197 | m_ObjectHideFlags: 3 198 | m_CorrespondingSourceObject: {fileID: 0} 199 | m_PrefabInstance: {fileID: 0} 200 | m_PrefabAsset: {fileID: 0} 201 | m_Name: 202 | m_EffectID: bc46a455559d311449549e695641458f 203 | m_EffectName: Attenuation 204 | m_MixLevel: e01edf55209d40c4d82df1185bce95b0 205 | m_Parameters: [] 206 | m_SendTarget: {fileID: 0} 207 | m_EnableWetMix: 0 208 | m_Bypass: 0 209 | -------------------------------------------------------------------------------- /Assets/_Project/Settings/Master.mixer.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0648aff6a08d3f0468df28399773aba6 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 24100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Audio Pooling 2 | ![SoundPooling](https://github.com/adammyhre/Unity-Audio-Pooling/assets/38876398/eeac93e7-1ba4-45d9-bf08-200f2c980428) 3 | 4 | Take control of the sounds in your game by pooling your Audio Sources! Reduce the number of real voices required to play a multitude of audio clips, manage their lifecycle, and enhance performance. Learn how to streamline your audio management and make your game sound fantastic with efficient audio source pooling! 5 | 6 | ## Example Usage 7 | 8 | 1. Create a prefab with a `SoundEmitter` component for pooling. 9 | 2. Add a `SoundManager` component to a new empty Game Object in your project. 10 | 3. Add an Audio Mixer with at least one channel. 11 | 4. Add a `SoundData` field with references to an `AudioClip` and an `AudioMixerGroup` 12 | 13 | ```csharp 14 | [SerializeField] SoundData soundData; 15 | 16 | // Cache SoundBuilder for performance 17 | SoundBuilder soundBuilder = SoundManager.Instance.CreateSoundBuilder(); 18 | 19 | soundBuilder 20 | .WithRandomPitch() 21 | .WithPosition(transform.position) 22 | .Play(soundData); 23 | } 24 | ``` 25 | 26 | ## YouTube 27 | 28 | - [Game Audio Optimization in Unity](https://youtu.be/BgpqoRFCNOs) 29 | 30 | You can also check out my [YouTube channel](https://www.youtube.com/@git-amend?sub_confirmation=1) for more Unity content. 31 | --------------------------------------------------------------------------------