293 lines
8.8 KiB
C#
293 lines
8.8 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace Streamingle.Contents.BossRaid
|
|
{
|
|
/// <summary>
|
|
/// 보스 레이드 효과음/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
|
|
/// </summary>
|
|
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";
|
|
|
|
// 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<string, AudioClip> _clipCache = new Dictionary<string, AudioClip>();
|
|
private Coroutine _bgmFadeCoroutine;
|
|
|
|
#endregion
|
|
|
|
#region Unity Messages
|
|
|
|
private void Awake()
|
|
{
|
|
EnsureAudioSources();
|
|
PreloadAll();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// 키 이름으로 SFX를 재생합니다.
|
|
/// </summary>
|
|
public void PlaySFX(string key)
|
|
{
|
|
var clip = GetClip(key);
|
|
if (clip == null || sfxSource == null) return;
|
|
sfxSource.PlayOneShot(clip, sfxVolume);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 키 이름으로 SFX를 랜덤 피치로 재생합니다.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 키 이름으로 BGM을 페이드 인 재생합니다.
|
|
/// </summary>
|
|
public void PlayBGM(string key)
|
|
{
|
|
var clip = GetClip(key);
|
|
if (clip == null || bgmSource == null) return;
|
|
|
|
if (_bgmFadeCoroutine != null)
|
|
StopCoroutine(_bgmFadeCoroutine);
|
|
|
|
_bgmFadeCoroutine = StartCoroutine(CrossfadeBGM(clip));
|
|
}
|
|
|
|
/// <summary>
|
|
/// BGM을 페이드 아웃으로 정지합니다.
|
|
/// </summary>
|
|
public void StopBGM()
|
|
{
|
|
if (bgmSource == null) return;
|
|
|
|
if (_bgmFadeCoroutine != null)
|
|
StopCoroutine(_bgmFadeCoroutine);
|
|
|
|
_bgmFadeCoroutine = StartCoroutine(FadeOutBGM());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 오디오를 즉시 정지합니다.
|
|
/// </summary>
|
|
public void StopAll()
|
|
{
|
|
if (_bgmFadeCoroutine != null)
|
|
{
|
|
StopCoroutine(_bgmFadeCoroutine);
|
|
_bgmFadeCoroutine = null;
|
|
}
|
|
sfxSource?.Stop();
|
|
if (bgmSource != null)
|
|
{
|
|
bgmSource.Stop();
|
|
bgmSource.clip = null;
|
|
bgmSource.volume = 0f;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// BGM 볼륨을 설정합니다. (0~1)
|
|
/// </summary>
|
|
public float BGMVolume
|
|
{
|
|
get => bgmVolume;
|
|
set
|
|
{
|
|
bgmVolume = Mathf.Clamp01(value);
|
|
if (bgmSource != null && bgmSource.isPlaying)
|
|
bgmSource.volume = bgmVolume;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// SFX 볼륨을 설정합니다. (0~1)
|
|
/// </summary>
|
|
public float SFXVolume
|
|
{
|
|
get => sfxVolume;
|
|
set => sfxVolume = Mathf.Clamp01(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 캐시에서 로드된 클립 수를 반환합니다.
|
|
/// </summary>
|
|
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,
|
|
BGM_BATTLE_NORMAL, BGM_BATTLE_RAGE, BGM_VICTORY,
|
|
};
|
|
|
|
int loaded = 0;
|
|
foreach (var key in keys)
|
|
{
|
|
var clip = Resources.Load<AudioClip>(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<AudioClip>(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<AudioSource>();
|
|
sfxSource.playOnAwake = false;
|
|
sfxSource.spatialBlend = 0f;
|
|
}
|
|
|
|
if (bgmSource == null)
|
|
{
|
|
var bgmObj = new GameObject("BossRaid_BGM");
|
|
bgmObj.transform.SetParent(transform);
|
|
bgmSource = bgmObj.AddComponent<AudioSource>();
|
|
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
|
|
}
|
|
}
|