195 lines
11 KiB
C#
195 lines
11 KiB
C#
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
|
|
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
|
|
|
|
// ============================================================
|
|
// AudioManager
|
|
//
|
|
// 역할: BGM 전환을 크로스페이드로 처리하는 싱글톤 매니저
|
|
//
|
|
// 사용법:
|
|
// AudioManager.Instance.PlayBGM(clip); // 즉시 전환
|
|
// AudioManager.Instance.PlayBGM(clip, 1.5f); // 1.5초 페이드
|
|
// AudioManager.Instance.StopBGM(); // 정지
|
|
// AudioManager.Instance.StopBGM(1.0f); // 1초 페이드아웃
|
|
//
|
|
// 구조:
|
|
// AudioSource A ─┐
|
|
// ├─ 크로스페이드 (한쪽 올리고 한쪽 내리기)
|
|
// AudioSource B ─┘
|
|
// ============================================================
|
|
|
|
public class AudioManager : MonoBehaviour // 클래스를 선언할거에요 -> AudioManager를
|
|
{
|
|
// ─────────────────────────────────────────────────────────
|
|
// 싱글톤
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public static AudioManager Instance { get; private set; } // 프로퍼티를 선언할거에요 -> 싱글톤 인스턴스를
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Inspector 설정
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
[Header("=== BGM 설정 ===")]
|
|
|
|
[Tooltip("BGM 기본 볼륨 (0~1)")]
|
|
[SerializeField] [Range(0f, 1f)] private float bgmVolume = 0.5f; // 변수를 선언할거에요 -> BGM 볼륨을
|
|
|
|
[Tooltip("기본 페이드 시간 (초)")]
|
|
[SerializeField] private float defaultFadeDuration = 1.0f; // 변수를 선언할거에요 -> 기본 페이드 시간을
|
|
|
|
[Tooltip("씬 시작 시 재생할 기본 BGM (없으면 무음)")]
|
|
[SerializeField] private AudioClip defaultBGM; // 변수를 선언할거에요 -> 시작 BGM을
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 내부 변수
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private AudioSource _sourceA; // 변수를 선언할거에요 -> BGM 채널 A를
|
|
private AudioSource _sourceB; // 변수를 선언할거에요 -> BGM 채널 B를
|
|
private bool _isAActive = true; // 변수를 선언할거에요 -> 현재 활성 채널이 A인지를
|
|
private Coroutine _fadeCoroutine; // 변수를 선언할거에요 -> 진행 중인 페이드 코루틴 핸들을
|
|
private AudioClip _currentClip; // 변수를 선언할거에요 -> 현재 재생 중인 클립을
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 초기화
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private void Awake() // 함수를 실행할거에요 -> 초기화를
|
|
{
|
|
// 싱글톤 중복 방지
|
|
if (Instance != null && Instance != this) // 조건이 맞으면 실행할거에요 -> 이미 인스턴스가 있으면
|
|
{
|
|
Destroy(gameObject); // 제거할거에요 -> 중복 오브젝트를
|
|
return; // 중단할거에요
|
|
}
|
|
|
|
Instance = this; // 설정할거에요 -> 싱글톤 인스턴스를
|
|
DontDestroyOnLoad(gameObject); // 유지할거에요 -> 씬 전환해도 파괴되지 않게
|
|
|
|
// BGM 채널 A 생성
|
|
_sourceA = gameObject.AddComponent<AudioSource>(); // 추가할거에요 -> AudioSource A를
|
|
_sourceA.loop = true; // 설정할거에요 -> 루프 재생으로
|
|
_sourceA.playOnAwake = false; // 끌거에요 -> 자동 재생을
|
|
_sourceA.volume = 0f; // 설정할거에요 -> 초기 볼륨을 0으로
|
|
|
|
// BGM 채널 B 생성
|
|
_sourceB = gameObject.AddComponent<AudioSource>(); // 추가할거에요 -> AudioSource B를
|
|
_sourceB.loop = true; // 설정할거에요 -> 루프 재생으로
|
|
_sourceB.playOnAwake = false; // 끌거에요 -> 자동 재생을
|
|
_sourceB.volume = 0f; // 설정할거에요 -> 초기 볼륨을 0으로
|
|
}
|
|
|
|
private void Start() // 함수를 실행할거에요 -> 시작 시
|
|
{
|
|
if (defaultBGM != null) // 조건이 맞으면 실행할거에요 -> 기본 BGM이 있으면
|
|
PlayBGM(defaultBGM, defaultFadeDuration); // 재생할거에요 -> 기본 BGM을 페이드로
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 공개 API
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
/// <summary> BGM 전환 (페이드 시간 생략 시 기본값 사용) </summary>
|
|
public void PlayBGM(AudioClip clip, float fadeDuration = -1f) // 함수를 선언할거에요 -> BGM을 재생하는 PlayBGM을
|
|
{
|
|
if (clip == null) return; // 중단할거에요 -> 클립 없으면
|
|
|
|
// 이미 같은 클립이 재생 중이면 무시
|
|
if (_currentClip == clip) return; // 중단할거에요 -> 동일 클립이면
|
|
|
|
float duration = fadeDuration < 0f ? defaultFadeDuration : fadeDuration; // 결정할거에요 -> 페이드 시간을
|
|
_currentClip = clip; // 저장할거에요 -> 현재 클립을
|
|
|
|
// 진행 중인 페이드 취소
|
|
if (_fadeCoroutine != null) // 조건이 맞으면 실행할거에요 -> 페이드 중이면
|
|
{
|
|
StopCoroutine(_fadeCoroutine); // 취소할거에요 -> 이전 페이드를
|
|
_fadeCoroutine = null; // 초기화할거에요 -> 핸들을
|
|
}
|
|
|
|
_fadeCoroutine = StartCoroutine(CrossFadeRoutine(clip, duration)); // 시작할거에요 -> 크로스페이드 코루틴을
|
|
}
|
|
|
|
/// <summary> BGM 정지 </summary>
|
|
public void StopBGM(float fadeDuration = -1f) // 함수를 선언할거에요 -> BGM을 정지하는 StopBGM을
|
|
{
|
|
float duration = fadeDuration < 0f ? defaultFadeDuration : fadeDuration; // 결정할거에요 -> 페이드 시간을
|
|
_currentClip = null; // 초기화할거에요 -> 현재 클립을
|
|
|
|
if (_fadeCoroutine != null) StopCoroutine(_fadeCoroutine); // 취소할거에요 -> 이전 페이드를
|
|
_fadeCoroutine = StartCoroutine(FadeOutRoutine(duration)); // 시작할거에요 -> 페이드아웃 코루틴을
|
|
}
|
|
|
|
/// <summary> BGM 볼륨 변경 </summary>
|
|
public void SetVolume(float volume) // 함수를 선언할거에요 -> 볼륨을 변경하는 SetVolume을
|
|
{
|
|
bgmVolume = Mathf.Clamp01(volume); // 저장할거에요 -> 0~1로 제한한 볼륨을
|
|
ActiveSource.volume = bgmVolume; // 적용할거에요 -> 현재 활성 채널에
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 크로스페이드 코루틴
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private IEnumerator CrossFadeRoutine(AudioClip newClip, float duration) // 코루틴을 정의할거에요 -> 크로스페이드를
|
|
{
|
|
AudioSource incoming = InactiveSource; // 가져올거에요 -> 새 클립 재생할 채널을 (현재 비활성 채널)
|
|
AudioSource outgoing = ActiveSource; // 가져올거에요 -> 페이드아웃할 채널을 (현재 활성 채널)
|
|
|
|
// 새 채널 준비
|
|
incoming.clip = newClip; // 설정할거에요 -> 새 클립을
|
|
incoming.volume = 0f; // 설정할거에요 -> 볼륨을 0으로
|
|
incoming.Play(); // 재생할거에요 -> 새 BGM을
|
|
|
|
_isAActive = !_isAActive; // 전환할거에요 -> 활성 채널을
|
|
|
|
float elapsed = 0f; // 초기화할거에요 -> 경과 시간을
|
|
float outgoingStart = outgoing.volume; // 저장할거에요 -> 기존 채널 시작 볼륨을
|
|
|
|
while (elapsed < duration) // 반복할거에요 -> 페이드 시간 동안
|
|
{
|
|
elapsed += Time.deltaTime; // 더할거에요 -> 경과 시간을
|
|
float t = Mathf.Clamp01(elapsed / duration); // 계산할거에요 -> 진행률을 (0~1)
|
|
|
|
incoming.volume = Mathf.Lerp(0f, bgmVolume, t); // 올릴거에요 -> 새 채널 볼륨을
|
|
outgoing.volume = Mathf.Lerp(outgoingStart, 0f, t); // 내릴거에요 -> 기존 채널 볼륨을
|
|
|
|
yield return null; // 대기할거에요 -> 다음 프레임까지
|
|
}
|
|
|
|
incoming.volume = bgmVolume; // 고정할거에요 -> 최종 볼륨을
|
|
outgoing.volume = 0f; // 고정할거에요 -> 기존 채널을 0으로
|
|
outgoing.Stop(); // 정지할거에요 -> 기존 채널을
|
|
outgoing.clip = null; // 초기화할거에요 -> 기존 클립을
|
|
|
|
_fadeCoroutine = null; // 초기화할거에요 -> 핸들을
|
|
}
|
|
|
|
private IEnumerator FadeOutRoutine(float duration) // 코루틴을 정의할거에요 -> 페이드아웃을
|
|
{
|
|
AudioSource active = ActiveSource; // 가져올거에요 -> 활성 채널을
|
|
float startVolume = active.volume; // 저장할거에요 -> 시작 볼륨을
|
|
float elapsed = 0f; // 초기화할거에요 -> 경과 시간을
|
|
|
|
while (elapsed < duration) // 반복할거에요 -> 페이드 시간 동안
|
|
{
|
|
elapsed += Time.deltaTime; // 더할거에요 -> 경과 시간을
|
|
active.volume = Mathf.Lerp(startVolume, 0f, elapsed / duration); // 내릴거에요 -> 볼륨을
|
|
yield return null; // 대기할거에요 -> 다음 프레임까지
|
|
}
|
|
|
|
active.volume = 0f; // 고정할거에요 -> 0으로
|
|
active.Stop(); // 정지할거에요 -> 채널을
|
|
active.clip = null; // 초기화할거에요 -> 클립을
|
|
_fadeCoroutine = null; // 초기화할거에요 -> 핸들을
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 내부 유틸
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private AudioSource ActiveSource => _isAActive ? _sourceA : _sourceB; // 프로퍼티를 선언할거에요 -> 현재 활성 채널을
|
|
private AudioSource InactiveSource => _isAActive ? _sourceB : _sourceA; // 프로퍼티를 선언할거에요 -> 현재 비활성 채널을
|
|
}
|