Audio Manager
Complete audio manager with music crossfade, SFX pooling, volume control, and persistence.
How to Use
Create an empty GameObject named 'AudioManager'
Attach this script
Add two AudioSource components for music (A and B)
Call AudioManager.Instance.PlayMusic(clip) for music
Call AudioManager.Instance.PlaySFX(clip) for sound effects
Features
- Smooth music crossfade between two AudioSource channels
- SFX object pooling with configurable pool size
- Random pitch variation on sound effects for natural audio
- Separate music and SFX volume controls
- Fade-out music with configurable duration
- Singleton pattern with DontDestroyOnLoad persistence
When to Use This
Essential for any game that needs background music and sound effects — which is virtually every game. Use this as your central audio hub in platformers, RPGs, shooters, puzzle games, and mobile titles. Particularly useful when you need seamless music transitions between menus, levels, and boss fights.
Common Mistakes
You must manually add two AudioSource components to the GameObject and assign them to musicSourceA and musicSourceB — the script does not create them automatically. If sfxPoolSize is too small for your game (e.g., a bullet-hell shooter), sounds will cut each other off because the pool reuses the first source when all are busy. Calling PlayMusic() before the singleton initializes in Awake will fail silently.
Source Code
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class AudioManager : MonoBehaviour
{
public static AudioManager Instance { get; private set; }
[Header("Audio Sources")]
[SerializeField] private AudioSource musicSourceA;
[SerializeField] private AudioSource musicSourceB;
[SerializeField] private int sfxPoolSize = 10;
[Header("Settings")]
[SerializeField] private float musicVolume = 0.7f;
[SerializeField] private float sfxVolume = 1f;
[SerializeField] private float crossfadeDuration = 1.5f;
private List<AudioSource> sfxPool;
private bool isMusicA = true;
private Coroutine crossfadeRoutine;
private AudioSource ActiveMusic => isMusicA ? musicSourceA : musicSourceB;
private AudioSource InactiveMusic => isMusicA ? musicSourceB : musicSourceA;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeSFXPool();
}
else
{
Destroy(gameObject);
}
}
private void InitializeSFXPool()
{
sfxPool = new List<AudioSource>();
for (int i = 0; i < sfxPoolSize; i++)
{
var source = gameObject.AddComponent<AudioSource>();
source.playOnAwake = false;
sfxPool.Add(source);
}
}
public void PlayMusic(AudioClip clip, bool crossfade = true)
{
if (clip == null) return;
if (crossfade && ActiveMusic.isPlaying)
{
if (crossfadeRoutine != null)
StopCoroutine(crossfadeRoutine);
crossfadeRoutine = StartCoroutine(CrossfadeMusic(clip));
}
else
{
ActiveMusic.clip = clip;
ActiveMusic.volume = musicVolume;
ActiveMusic.loop = true;
ActiveMusic.Play();
}
}
private IEnumerator CrossfadeMusic(AudioClip newClip)
{
InactiveMusic.clip = newClip;
InactiveMusic.volume = 0f;
InactiveMusic.loop = true;
InactiveMusic.Play();
float elapsed = 0f;
while (elapsed < crossfadeDuration)
{
elapsed += Time.unscaledDeltaTime;
float t = elapsed / crossfadeDuration;
ActiveMusic.volume = Mathf.Lerp(musicVolume, 0f, t);
InactiveMusic.volume = Mathf.Lerp(0f, musicVolume, t);
yield return null;
}
ActiveMusic.Stop();
isMusicA = !isMusicA;
}
public void PlaySFX(AudioClip clip, float volumeScale = 1f)
{
if (clip == null) return;
AudioSource source = GetAvailableSFXSource();
source.clip = clip;
source.volume = sfxVolume * volumeScale;
source.pitch = Random.Range(0.95f, 1.05f);
source.Play();
}
private AudioSource GetAvailableSFXSource()
{
foreach (var source in sfxPool)
{
if (!source.isPlaying)
return source;
}
// All busy — reuse the first one
return sfxPool[0];
}
public void SetMusicVolume(float volume)
{
musicVolume = Mathf.Clamp01(volume);
ActiveMusic.volume = musicVolume;
}
public void SetSFXVolume(float volume)
{
sfxVolume = Mathf.Clamp01(volume);
}
public void StopMusic(float fadeOutDuration = 0.5f)
{
StartCoroutine(FadeOutMusic(fadeOutDuration));
}
private IEnumerator FadeOutMusic(float duration)
{
float startVolume = ActiveMusic.volume;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.unscaledDeltaTime;
ActiveMusic.volume = Mathf.Lerp(startVolume, 0f, elapsed / duration);
yield return null;
}
ActiveMusic.Stop();
}
}