using System; using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.Audio; using UnityEngine.SceneManagement; namespace DDD { [Serializable] public class SfxPitch { [field: SerializeField] public bool IsIgnoreTimeScale { get; set; } [field: SerializeField] public float PitchValue { get; set; } public SfxPitch(bool isIgnoreTimeScale, float pitchValue) { IsIgnoreTimeScale = isIgnoreTimeScale; PitchValue = pitchValue; } } public class AudioManager : Singleton { [Title("오디오 데이터")] [SerializeField] private BgmDataSo _bgmDataSo; [SerializeField] private SfxDataSo _sfxDataSo; [SerializeField] private int _sfxChannelCount = 32; [Title("오디오 믹서")] [SerializeField] private AudioMixer _audioMixer; [SerializeField] private AudioMixerGroup _masterMixerGroup; [SerializeField] private AudioMixerGroup _bgmMixerGroup; [SerializeField] private AudioMixerGroup _sfxMixerGroup; [Title("중복 사운드 처리")] [SerializeField, Range(0f, 1f)] private float _sfxMinInterval = 0.09f; private Dictionary _bgmDictionary; private Dictionary _sfxDictionary; private Dictionary _sfxPitchDictionary; private Dictionary _sfxPlayTimeDictionary; private AudioSource _bgmAudioSource; protected override void OnAwake() { InitializeDictionary(); InitializeComponents(); SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { StopBgm(); StopSfxAll(); } private void InitializeDictionary() { _bgmDictionary = new Dictionary(_bgmDataSo.BgmDataList.Count); foreach (var element in _bgmDataSo.BgmDataList) { _bgmDictionary.Add(element.BgmName, element.Clip); } _sfxDictionary = new Dictionary(_sfxDataSo.SfxDataList.Count); foreach (var element in _sfxDataSo.SfxDataList) { _sfxDictionary.Add(element.SfxName, element.Clip); } _sfxPlayTimeDictionary = new Dictionary(_sfxDataSo.SfxDataList.Count); } private void InitializeComponents() { _bgmAudioSource = gameObject.AddComponent(); _bgmAudioSource.outputAudioMixerGroup = _bgmMixerGroup; _bgmAudioSource.loop = true; _sfxPitchDictionary = new Dictionary(_sfxChannelCount); for (var i = 0; i < _sfxChannelCount; i++) { var sfxAudioSource = gameObject.AddComponent(); sfxAudioSource.outputAudioMixerGroup = _sfxMixerGroup; sfxAudioSource.loop = false; _sfxPitchDictionary[sfxAudioSource] = new SfxPitch(false, sfxAudioSource.pitch); } } public void PlayBgm(string bgmName) { if (_bgmDictionary.TryGetValue(bgmName, out var value)) { _bgmAudioSource.clip = value; _bgmAudioSource.Play(); } else { print("Bgm not found: " + bgmName); } } public void StopBgm() { _bgmAudioSource.Stop(); } public void PlaySfx(string sfxName, bool loop = false, bool ignoreTimeScale = false, float? duration = null) { if (_sfxDictionary.TryGetValue(sfxName, out var value)) { float currentTime = ignoreTimeScale ? Time.unscaledTime : Time.time; if (_sfxPlayTimeDictionary.TryGetValue(sfxName, out var lastPlayTime)) { if (currentTime - lastPlayTime < _sfxMinInterval) { return; // 최소 간격 조건 미충족 시 재생하지 않음 } } var availableSfxAudioSource = GetAvailableSfxAudioSource(); availableSfxAudioSource.clip = value; availableSfxAudioSource.loop = loop; if (ignoreTimeScale) { _sfxPitchDictionary[availableSfxAudioSource] = new SfxPitch(true, 1f); availableSfxAudioSource.pitch = 1f; } else { float pitch = duration.HasValue ? value.length / duration.Value : 1f; _sfxPitchDictionary[availableSfxAudioSource] = new SfxPitch(false, pitch); availableSfxAudioSource.pitch = _sfxPitchDictionary[availableSfxAudioSource].PitchValue * Time.timeScale; } availableSfxAudioSource.Play(); _sfxPlayTimeDictionary[sfxName] = currentTime; } else { print("Sfx not found: " + sfxName); } } public void StopSfx(string sfxName) { if (_sfxDictionary.TryGetValue(sfxName, out var clip)) { foreach (var element in _sfxPitchDictionary.Keys) { if (element.clip == clip && element.isPlaying) { element.Stop(); } } } else { Debug.LogWarning("Sfx not found: " + sfxName); } } public void StopSfxAll() { foreach (var element in _sfxPitchDictionary.Keys) { if (element.isPlaying) { element.Stop(); } } } private AudioSource GetAvailableSfxAudioSource() { foreach (var element in _sfxPitchDictionary.Keys) { if (!element.isPlaying) { return element; } } // 모든 AudioSource가 사용 중이면 첫 번째 AudioSource를 재사용 using var enumerator = _sfxPitchDictionary.Keys.GetEnumerator(); enumerator.MoveNext(); return enumerator.Current; } /// /// 0.0001 ~ 1값 /// /// public void SetMasterVolume(float volume) { var newVolume = Mathf.Log10(volume) * 20f; _audioMixer.SetFloat("Master", newVolume); } public void SetBgmVolume(float volume) { var newVolume = Mathf.Log10(volume) * 20f; _audioMixer.SetFloat("Bgm", newVolume); } public void SetSfxVolume(float volume) { var newVolume = Mathf.Log10(volume) * 20f; _audioMixer.SetFloat("Sfx", newVolume); } public void SetPitchSfxAll(float pitch) { foreach (var element in _sfxPitchDictionary) { if (element.Value.IsIgnoreTimeScale) continue; element.Key.pitch = element.Value.PitchValue * pitch; } } } }