using UnityEngine; using UnityEngine.SceneManagement; [RequireComponent(typeof(AudioSource))] public class SoundManager : MonoBehaviour { // 전역 어디서나 효과음과 BGM을 호출할 수 있는 싱글톤 인스턴스 public static SoundManager Instance { get; private set; } [Header("BGM 배경음악 오디오 클립 등록")] public AudioClip lobbyBgmClip; // 로비 씬 배경음악 public AudioClip battleBgmClip; // 배틀 스테이지 배경음악 [Header("효과음 오디오 클립 등록 슬롯")] public AudioClip buttonClickClip; // 버튼 클릭음 public AudioClip attackSwingClip; // 플레이어/적 공격 휘두르기 소리 public AudioClip punchHitClip; // 피격 타격음 public AudioClip enemyDeathClip; // 적 사망음 public AudioClip playerDeathClip; // 플레이어 사망음 public AudioClip transitionClip; // 성벽 통과 씬 전환음 public AudioClip typingClip; // 대화창 글자 타이핑음 [Header("타자기 효과음 풀링 설정")] [SerializeField] private int typingPoolSize = 5; private AudioSource[] typingAudioPool; private int nextPoolIndex = 0; private AudioSource globalSFXSource; // 효과음 전용 소스 private AudioSource bgmAudioSource; // BGM 전용 루프 소스 private void Awake() { // 싱글톤 보장 if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // 1. 효과음 전용 오디오 소스 설정 globalSFXSource = GetComponent(); if (globalSFXSource == null) { globalSFXSource = gameObject.AddComponent(); } // 2. BGM 전용 독립 오디오 소스 생성 및 셋팅 (루프 기능 및 독립 볼륨) bgmAudioSource = gameObject.AddComponent(); bgmAudioSource.playOnAwake = false; bgmAudioSource.loop = true; // 3. 효과음 풀링 초기화 InitializeTypingPool(); } private void OnEnable() { // 유니티 씬 로드 완료 이벤트를 리스닝합니다. (씬 전환 시 BGM 자동 매핑용) SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDisable() { // 이벤트 해제 SceneManager.sceneLoaded -= OnSceneLoaded; } // 씬이 완벽하게 로드되었을 때 실행되는 콜백 함수 private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { PlayBgmForScene(scene.name); } // 씬 이름 기반 배경음악 교체 메서드 public void PlayBgmForScene(string sceneName) { AudioClip targetBgm = null; // 씬 이름과 매치되는 배경음악 선별 if (sceneName == "Lobby") { targetBgm = lobbyBgmClip; } else if (sceneName == "battleStage") { targetBgm = battleBgmClip; } if (bgmAudioSource == null) return; // 현재 이미 똑같은 배경음악이 돌고 있다면 끊김 없이 연주 유지 if (bgmAudioSource.clip == targetBgm && bgmAudioSource.isPlaying) return; // 이전 배경음악 정지 및 새 음악 매핑 후 루프 재생 bgmAudioSource.Stop(); bgmAudioSource.clip = targetBgm; if (targetBgm != null) { bgmAudioSource.volume = 0.45f; // 효과음에 비해 잔잔하도록 볼륨 기본 설정 bgmAudioSource.Play(); Debug.Log($"[SoundManager] '{sceneName}' 씬 진입 감지 -> BGM 자동 재생: {targetBgm.name}"); } else { Debug.LogWarning($"[SoundManager] '{sceneName}' 씬에 등록된 BGM 오디오 클립이 없습니다."); } } private void InitializeTypingPool() { typingAudioPool = new AudioSource[typingPoolSize]; for (int i = 0; i < typingPoolSize; i++) { GameObject child = new GameObject($"TypingAudioChannel_{i}"); child.transform.SetParent(this.transform); AudioSource source = child.AddComponent(); source.playOnAwake = false; source.loop = false; typingAudioPool[i] = source; } } // 기본 단발성 효과음 재생 public void PlayOneShot(AudioClip clip, float volume = 1f) { if (clip != null && globalSFXSource != null) { globalSFXSource.PlayOneShot(clip, volume); } } // 효과음 바인딩 메서드들 public void PlayButtonClick() => PlayOneShot(buttonClickClip, 0.7f); public void PlayAttackSwing() => PlayOneShot(attackSwingClip, 0.85f); public void PlayPunchHit() => PlayOneShot(punchHitClip, 0.95f); public void PlayEnemyDeath() => PlayOneShot(enemyDeathClip, 1.0f); public void PlayPlayerDeath() => PlayOneShot(playerDeathClip, 1.0f); public void PlayTransition() => PlayOneShot(transitionClip, 0.9f); // 타자기 효과음 public void PlayTyping() { if (typingClip == null || typingAudioPool == null || typingAudioPool.Length == 0) return; AudioSource channel = typingAudioPool[nextPoolIndex]; nextPoolIndex = (nextPoolIndex + 1) % typingPoolSize; if (channel != null) { channel.clip = typingClip; channel.volume = 0.22f; channel.pitch = Random.Range(0.85f, 1.15f); channel.Play(); } } }