모두 주석을 달면서 문서화 해뒀으니 하나씩 읽으면서 내려오면 설명해드리겠습니다.

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

public enum BGM
{

}

public enum SFX
{

}

/// <summary>
/// 게임의 전체적인 오디오 시스템을 관리하는 싱글톤 컨트롤러입니다.
/// BGM과 SFX의 재생, 볼륨 조절, 믹서 그룹 관리를 담당합니다.
/// </summary>
public class AudioMixerController : MonoSingleton<AudioMixerController>
{
    private const string AUDIO_PATH = "Audio"; // 오디오 리소스 경로

    public AudioMixer mixer; // 오디오 믹서

    private Dictionary<BGM, AudioClip> bgmPlayer = new Dictionary<BGM, AudioClip>(); // BGM 클립 저장소
    private Dictionary<SFX, AudioClip> sfxPlayer = new Dictionary<SFX, AudioClip>(); // SFX 클립 저장소

    private AudioSource curBgm; // 현재 재생 중인 BGM 오디오 소스

    protected override void Init()
    {
        base.Init();

        // 오디오 믹서 리소스 로드
        mixer = Resources.Load($"{AUDIO_PATH}/Mixer", typeof(AudioMixer)) as AudioMixer;
        curBgm = GetComponent<AudioSource>(); // 현재 GameObject의 AudioSource 컴포넌트 가져오기
        LoadBGMPlayer(); // BGM 클립 로드
        LoadSFXPlayer(); // SFX 클립 로드
    }

    /// <summary>
    /// BGM 클립을 로드합니다.
    /// </summary>
    private void LoadBGMPlayer()
    {
        // BGM 열거형의 모든 값을 반복
        foreach (var bgm in Enum.GetValues(typeof(BGM)))
        {
            var audioName = bgm.ToString(); // BGM 이름 가져오기
            var pathStr = $"{AUDIO_PATH}/BGM/{audioName}"; // BGM 경로 생성
            var audioClip = Resources.Load(pathStr, typeof(AudioClip)) as AudioClip; // 오디오 클립 로드
            if (!audioClip)
            {
                Logger.ErrorLog($"{audioName} clip does not exist."); // 클립이 없으면 에러 로그 출력
            }

            bgmPlayer[(BGM)bgm] = audioClip; // BGM 클립을 딕셔너리에 추가
        }
    }

    /// <summary>
    /// SFX 클립을 로드합니다.
    /// </summary>
    private void LoadSFXPlayer()
    {
        // SFX 열거형의 모든 값을 반복
        foreach (var sfx in Enum.GetValues(typeof(SFX)))
        {
            var audioName = sfx.ToString(); // SFX 이름 가져오기
            var pathStr = $"{AUDIO_PATH}/SFX/{audioName}"; // SFX 경로 생성
            var audioClip = Resources.Load(pathStr, typeof(AudioClip)) as AudioClip; // 오디오 클립 로드
            if (!audioClip)
            {
                Logger.ErrorLog($"{audioName} clip does not exist."); // 클립이 없으면 에러 로그 출력
            }

            sfxPlayer[(SFX)sfx] = audioClip; // SFX 클립을 딕셔너리에 추가
        }
    }

    /// <summary>
    /// BGM을 재생합니다.
    /// </summary>
    /// <param name="bgm">재생할 BGM</param>
    public void PlayBGM(BGM bgm)
    {
        if (curBgm.isPlaying)
        {
            curBgm.Stop(); // 현재 재생 중인 BGM이 있으면 정지
        }

        if (!bgmPlayer.ContainsKey(bgm))
        {
            Logger.ErrorLog($"Invalid clip name. {bgm}"); // 유효하지 않은 클립 이름이면 에러 로그 출력
            return;
        }

        curBgm.clip = bgmPlayer[bgm]; // 선택한 BGM 클립 설정
        curBgm.Play(); // BGM 재생
    }

    /// <summary>
    /// 현재 BGM을 일시 정지합니다.
    /// </summary>
    public void PauseBGM()
    {
        if (curBgm.isPlaying) curBgm.Pause(); // 현재 BGM이 재생 중이면 일시 정지
    }

    /// <summary>
    /// 일시 정지된 BGM을 재개합니다.
    /// </summary>
    public void ResumeBGM()
    {
        if (curBgm.isPlaying) curBgm.UnPause(); // 현재 BGM이 재생 중이면 재개
    }

    /// <summary>
    /// 현재 BGM을 정지합니다.
    /// </summary>
    public void StopBGM()
    {
        if (curBgm.isPlaying) curBgm.Stop(); // 현재 BGM이 재생 중이면 정지
    }

    /// <summary>
    /// 지정된 BGM 클립을 반환합니다.
    /// </summary>
    /// <param name="bgm">BGM 열거형</param>
    /// <returns>해당 BGM 클립</returns>
    public AudioClip GetBGMClip(BGM bgm)
    {
        if (!bgmPlayer.ContainsKey(bgm))
        {
            Logger.ErrorLog($"Invalid clip name. {bgm}"); // 유효하지 않은 클립 이름이면 에러 로그 출력
            return null;
        }
        else
        {
            return bgmPlayer[bgm]; // 해당 BGM 클립 반환
        }
    }

    /// <summary>
    /// 지정된 SFX 클립을 반환합니다.
    /// </summary>
    /// <param name="sfx">SFX 열거형</param>
    /// <returns>해당 SFX 클립</returns>
    public AudioClip GetSFXClip(SFX sfx)
    {
        if (!sfxPlayer.ContainsKey(sfx))
        {
            Logger.ErrorLog($"Invalid clip name. {sfx}"); // 유효하지 않은 클립 이름이면 에러 로그 출력
            return null;
        }
        else
        {
            return sfxPlayer[sfx]; // 해당 SFX 클립 반환
        }
    }

    /// <summary>
    /// 마스터 볼륨을 설정합니다.
    /// </summary>
    /// <param name="volume">설정할 볼륨 값</param>
    public void SetMasterVolume(float volume)
    {
        mixer.SetFloat("Master", Mathf.Log10(volume) * 20); // 로그 스케일로 볼륨 설정
    }

    /// <summary>
    /// BGM 볼륨을 설정합니다.
    /// </summary>
    /// <param name="volume">설정할 BGM 볼륨 값</param>
    public void SetBGMVolume(float volume)
    {
        mixer.SetFloat("BGM", Mathf.Log10(volume) * 20); // 로그 스케일로 BGM 볼륨 설정
    }

    /// <summary>
    /// SFX 볼륨을 설정합니다.
    /// </summary>
    /// <param name="volume">설정할 SFX 볼륨 값</param>
    public void SetSFXVolume(float volume)
    {
        mixer.SetFloat("SFX", Mathf.Log10(volume) * 20); // 로그 스케일로 SFX 볼륨 설정
    }

    /// <summary>
    /// 3D 사운드의 최소 거리를 설정합니다.
    /// </summary>
    /// <param name="minDistance">소리가 감쇄되기 시작하는 최소 거리</param>
    public void SetMinDistance(float minDistance)
    {
        if (curBgm != null)
        {
            curBgm.minDistance = minDistance; // 최소 거리 설정
        }
    }

    /// <summary>
    /// 3D 사운드의 최대 거리를 설정합니다.
    /// </summary>
    /// <param name="maxDistance">소리가 들리지 않게 되는 최대 거리</param>
    public void SetMaxDistance(float maxDistance)
    {
        if (curBgm != null)
        {
            curBgm.maxDistance = maxDistance; // 최대 거리 설정
        }
    }

    /// <summary>
    /// 3D 사운드의 공간 블렌드를 설정합니다.
    /// </summary>
    /// <param name="blend">0(2D) ~ 1(3D) 사이의 값</param>
    public void SetSpatialBlend(float blend)
    {
        if (curBgm != null)
        {
            curBgm.spatialBlend = Mathf.Clamp01(blend); // 공간 블렌드 설정
        }
    }
}

 

using UnityEngine;

/// <summary>
/// 사운드 타입을 나타내는 열거형
/// </summary>
public enum SoundType
{
    BGM,
    SFX
}

/// <summary>
/// 오디오 유닛을 관리하는 클래스
/// BGM 및 SFX의 재생을 담당합니다.
/// </summary>
public class AudioUnit : MonoBehaviour
{
    private AudioSource audioSource; // 오디오 소스 컴포넌트

    public SoundType type; // 사운드 타입 (BGM 또는 SFX)

    /// <summary>
    /// 초기화 메서드
    /// </summary>
    void Start()
    {
        // AudioSource 컴포넌트를 가져오거나 추가합니다.
        if (TryGetComponent(out AudioSource source))
        {
            audioSource = source; // 기존 AudioSource 사용
        }
        else
        {
            audioSource = gameObject.AddComponent<AudioSource>(); // 새 AudioSource 추가
        }

        audioSource.playOnAwake = false; // 시작 시 자동 재생 비활성화
        audioSource.loop = false; // 반복 재생 비활성화

        // 믹서 그룹 설정
        var amg = AudioMixerController.Instance.mixer.FindMatchingGroups("Master");
        audioSource.outputAudioMixerGroup = amg[(int)type + 1]; // 사운드 타입에 따라 믹서 그룹 설정
    }

    /// <summary>
    /// SFX를 재생합니다.
    /// </summary>
    public void PlaySFX()
    {
        var clip = audioSource.clip; // 현재 클립 가져오기
        audioSource.PlayOneShot(clip); // 클립을 한 번 재생
    }

    /// <summary>
    /// 지정된 SFX를 재생합니다.
    /// </summary>
    /// <param name="sfx">재생할 SFX</param>
    public void PlaySFX(SFX sfx)
    {
        var clip = AudioMixerController.Instance.GetSFXClip(sfx); // SFX 클립 가져오기
        audioSource.PlayOneShot(clip); // 클립을 한 번 재생
    }

    /// <summary>
    /// 최소 거리 설정 메서드
    /// </summary>
    /// <param name="minDistance">최소 거리</param>
    public void SetMinDistance(float minDistance)
    {
        audioSource.minDistance = minDistance; // 최소 거리 설정
    }

    /// <summary>
    /// 최대 거리 설정 메서드
    /// </summary>
    /// <param name="maxDistance">최대 거리</param>
    public void SetMaxDistance(float maxDistance)
    {
        audioSource.maxDistance = maxDistance; // 최대 거리 설정
    }

    /// <summary>
    /// 공간 블렌드 설정 메서드
    /// </summary>
    /// <param name="blend">공간 블렌드 값</param>
    public void SetSpatialBlend(float blend)
    {
        audioSource.spatialBlend = Mathf.Clamp01(blend); // 공간 블렌드 설정
    }
}

 

시스템 구성

  • AudioMixerController: 전체 오디오 시스템을 관리하는 매니저
  • AudioUnit: 개별 사운드 재생을 담당하는 컴포넌트
  • Audio Mixer : Master, BGM, SFX 채널 그룹으로 구성되어있음

사용방법(인스턴스 가정)

AudioMixerController.Instance.PlayBGM(BGM.이름); // BGM재생
AudioMixerController.Instance.PauseBGM(); // BGM일시정지
AudioMixerController.Instance.ResumeBGM(); // BGM 재개
AudioMixerController.Instance.StopBGM(); // BGM 정지

AudioUnit 컴포넌트를 통한 재생

AudioUnit audioUnit = GetComponent<AudioUnit>();

audioUnit.PlaySFX(SFX.이름);

볼륨조절

AudioMixerController.Instance.SetMasterVolume(0.5f);
AudioMixerController.Instance.SetBGMVolume(0.7f);
AudioMixerController.Instance.SetSFXVolume(0.8f);

3D 사운드 설정

AudioUnit audioUnit = GetComponent<AudioUnit>();
// 3D 사운드 범위 설정
audioUnit.SetMinDistance(1f); // 최소 거리
audioUnit.SetMaxDistance(20f); // 최대 거리
// 2D/3D 블렌드 설정 (0: 2D, 1: 3D)
audioUnit.SetSpatialBlend(1f);

 

주의사항

1. 오디오 클립은 반드시 Resources/Audio 폴더 내에 위치해야 합니다.


2. BGM과 SFX는 각각 Resources/Audio/BGM과 Resources/Audio/SFX 폴더에 저장해야 합니다.


3. 파일명은 반드시 enum에 정의된 이름과 일치해야 합니다.


4. 3D 사운드를 사용할 때는 AudioListener 컴포넌트가 씬에 존재해야 합니다 (보통 기본적으로 메인 카메라에 부착되어있음)

 

TIP


1. 자주 사용되는 SFX는 AudioUnit을 미리 생성해두어 재사용
2. 동시에 재생되는 사운드 수 제한을 위해 Object Pooling 사용 권장
3. 멀리 있는 사운드는 자동으로 컬링되도록 MaxDistance 적절히 설정