intermediate Audio

Audio Manager

Complete audio manager with music crossfade, SFX pooling, volume control, and persistence.

Unity 2022.3+ · 4.5 KB · AudioManager.cs

How to Use

1

Create an empty GameObject named 'AudioManager'

2

Attach this script

3

Add two AudioSource components for music (A and B)

4

Call AudioManager.Instance.PlayMusic(clip) for music

5

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

AudioManager.cs
C#
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();
    }
}