├── .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 | 
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 |
--------------------------------------------------------------------------------