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
}
}