using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Streamingle.Contents.BossRaid { /// /// 보스 레이드 효과음/BGM 관리. /// Resources/Audio 폴더에서 파일명 규칙으로 자동 로드합니다. /// /// 파일명 규칙: /// sfx_hit_normal, sfx_hit_critical, sfx_hit_miss, /// sfx_boss_appear, sfx_boss_rage, sfx_boss_death, /// sfx_victory, sfx_damage_popup, sfx_phase_transition, /// bgm_battle_normal, bgm_battle_rage, bgm_victory /// public class BossRaidAudio : MonoBehaviour { #region Constants private const string AudioPath = "Audio/"; // SFX 키 public const string SFX_HIT_NORMAL = "sfx_hit_normal"; public const string SFX_HIT_CRITICAL = "sfx_hit_critical"; public const string SFX_HIT_MISS = "sfx_hit_miss"; public const string SFX_BOSS_APPEAR = "sfx_boss_appear"; public const string SFX_BOSS_RAGE = "sfx_boss_rage"; public const string SFX_BOSS_DEATH = "sfx_boss_death"; public const string SFX_VICTORY = "sfx_victory"; public const string SFX_DAMAGE_POPUP = "sfx_damage_popup"; public const string SFX_PHASE_TRANSITION = "sfx_phase_transition"; // 보스 공격 public const string SFX_BOSS_BREATH = "sfx_boss_breath"; public const string SFX_BOSS_SLAM = "sfx_boss_slam"; public const string SFX_BOSS_CHARGE = "sfx_boss_charge"; // 유저 피격 public const string SFX_PLAYER_HIT = "sfx_player_hit"; public const string SFX_PLAYER_DEATH = "sfx_player_death"; public const string SFX_GAME_OVER = "sfx_game_over"; // BGM 키 public const string BGM_BATTLE_NORMAL = "bgm_battle_normal"; public const string BGM_BATTLE_RAGE = "bgm_battle_rage"; public const string BGM_VICTORY = "bgm_victory"; #endregion #region Fields [Header("AudioSource")] [SerializeField] private AudioSource sfxSource; [SerializeField] private AudioSource bgmSource; [Header("BGM 설정")] [SerializeField] [Range(0f, 1f)] private float bgmVolume = 0.5f; [SerializeField] private float bgmFadeDuration = 1f; [Header("SFX 설정")] [SerializeField] [Range(0f, 1f)] private float sfxVolume = 1f; private Dictionary _clipCache = new Dictionary(); private Coroutine _bgmFadeCoroutine; #endregion #region Unity Messages private void Awake() { EnsureAudioSources(); PreloadAll(); } #endregion #region Public Methods /// /// 키 이름으로 SFX를 재생합니다. /// public void PlaySFX(string key) { var clip = GetClip(key); if (clip == null || sfxSource == null) return; sfxSource.PlayOneShot(clip, sfxVolume); } /// /// 키 이름으로 SFX를 랜덤 피치로 재생합니다. /// public void PlaySFXRandomPitch(string key, float minPitch = 0.9f, float maxPitch = 1.1f) { var clip = GetClip(key); if (clip == null || sfxSource == null) return; float originalPitch = sfxSource.pitch; sfxSource.pitch = Random.Range(minPitch, maxPitch); sfxSource.PlayOneShot(clip, sfxVolume); sfxSource.pitch = originalPitch; } /// /// 키 이름으로 BGM을 페이드 인 재생합니다. /// public void PlayBGM(string key) { var clip = GetClip(key); if (clip == null || bgmSource == null) return; if (_bgmFadeCoroutine != null) StopCoroutine(_bgmFadeCoroutine); _bgmFadeCoroutine = StartCoroutine(CrossfadeBGM(clip)); } /// /// BGM을 페이드 아웃으로 정지합니다. /// public void StopBGM() { if (bgmSource == null) return; if (_bgmFadeCoroutine != null) StopCoroutine(_bgmFadeCoroutine); _bgmFadeCoroutine = StartCoroutine(FadeOutBGM()); } /// /// 모든 오디오를 즉시 정지합니다. /// public void StopAll() { if (_bgmFadeCoroutine != null) { StopCoroutine(_bgmFadeCoroutine); _bgmFadeCoroutine = null; } sfxSource?.Stop(); if (bgmSource != null) { bgmSource.Stop(); bgmSource.clip = null; bgmSource.volume = 0f; } } /// /// BGM 볼륨을 설정합니다. (0~1) /// public float BGMVolume { get => bgmVolume; set { bgmVolume = Mathf.Clamp01(value); if (bgmSource != null && bgmSource.isPlaying) bgmSource.volume = bgmVolume; } } /// /// SFX 볼륨을 설정합니다. (0~1) /// public float SFXVolume { get => sfxVolume; set => sfxVolume = Mathf.Clamp01(value); } /// /// 캐시에서 로드된 클립 수를 반환합니다. /// public int LoadedClipCount => _clipCache.Count; #endregion #region Private Methods private void PreloadAll() { string[] keys = new[] { SFX_HIT_NORMAL, SFX_HIT_CRITICAL, SFX_HIT_MISS, SFX_BOSS_APPEAR, SFX_BOSS_RAGE, SFX_BOSS_DEATH, SFX_VICTORY, SFX_DAMAGE_POPUP, SFX_PHASE_TRANSITION, SFX_BOSS_BREATH, SFX_BOSS_SLAM, SFX_BOSS_CHARGE, SFX_PLAYER_HIT, SFX_PLAYER_DEATH, SFX_GAME_OVER, BGM_BATTLE_NORMAL, BGM_BATTLE_RAGE, BGM_VICTORY, }; int loaded = 0; foreach (var key in keys) { var clip = Resources.Load(AudioPath + key); if (clip != null) { _clipCache[key] = clip; loaded++; } } Debug.Log($"[BossRaidAudio] {loaded}/{keys.Length}개 오디오 로드 완료"); } private AudioClip GetClip(string key) { if (string.IsNullOrEmpty(key)) return null; if (_clipCache.TryGetValue(key, out var cached)) return cached; // 캐시에 없으면 다시 로드 시도 var clip = Resources.Load(AudioPath + key); if (clip != null) _clipCache[key] = clip; return clip; } private void EnsureAudioSources() { if (sfxSource == null) { var sfxObj = new GameObject("BossRaid_SFX"); sfxObj.transform.SetParent(transform); sfxSource = sfxObj.AddComponent(); sfxSource.playOnAwake = false; sfxSource.spatialBlend = 0f; } if (bgmSource == null) { var bgmObj = new GameObject("BossRaid_BGM"); bgmObj.transform.SetParent(transform); bgmSource = bgmObj.AddComponent(); bgmSource.playOnAwake = false; bgmSource.loop = true; bgmSource.spatialBlend = 0f; bgmSource.volume = 0f; } } private IEnumerator CrossfadeBGM(AudioClip newClip) { if (bgmSource.isPlaying) { float elapsed = 0f; float startVol = bgmSource.volume; float halfDuration = bgmFadeDuration * 0.5f; while (elapsed < halfDuration) { elapsed += Time.unscaledDeltaTime; bgmSource.volume = Mathf.Lerp(startVol, 0f, elapsed / halfDuration); yield return null; } } bgmSource.clip = newClip; bgmSource.volume = 0f; bgmSource.Play(); float fadeElapsed = 0f; float fadeHalf = bgmFadeDuration * 0.5f; while (fadeElapsed < fadeHalf) { fadeElapsed += Time.unscaledDeltaTime; bgmSource.volume = Mathf.Lerp(0f, bgmVolume, fadeElapsed / fadeHalf); yield return null; } bgmSource.volume = bgmVolume; _bgmFadeCoroutine = null; } private IEnumerator FadeOutBGM() { float startVol = bgmSource.volume; float elapsed = 0f; while (elapsed < bgmFadeDuration) { elapsed += Time.unscaledDeltaTime; bgmSource.volume = Mathf.Lerp(startVol, 0f, elapsed / bgmFadeDuration); yield return null; } bgmSource.Stop(); bgmSource.volume = 0f; _bgmFadeCoroutine = null; } #endregion } }