using System.Collections; using DG.Tweening; using UnityEngine; using UnityEngine.SceneManagement; public class AudioManager : MonoBehaviour { [Header("Sources")] public AudioSource musicSource; public AudioSource uiSfxSource; public AudioSource gameSfxSource; public AudioSource crowdSfxSourceA; public AudioSource crowdSfxSourceB; [Header("Music")] public AudioDirectory audioDirectory; [SerializeField] float musicFadeDuration = 1f; [Header("Crowd bed")] [SerializeField] float crowdFadeDuration = 1f; [SerializeField] float maxCrowdVolume = 1f; public static AudioManager instance; float _maxMusicVolume; bool _inGame; CrowdNoiseType _currentCrowdNoiseType; bool _crowdPrimaryIsA = true; bool _crowdBedInitialized; Coroutine _introRoutine; Coroutine _goalSfxSequenceRoutine; void Awake() { if (instance != null) { Destroy(gameObject); return; } instance = this; DontDestroyOnLoad(gameObject); _maxMusicVolume = musicSource != null ? musicSource.volume : 1f; } void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; } void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; } void Start() { var scene = SceneManager.GetActiveScene(); if (scene.IsValid()) ApplyMusicForScene(scene.name); } void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!scene.IsValid()) return; ApplyMusicForScene(scene.name); } void ApplyMusicForScene(string sceneName) { if (audioDirectory == null) return; bool comingFromGame = _inGame; if (sceneName == audioDirectory.gameSceneName) { _inGame = true; EnableCrowdSourcesForGame(); SetCrowdNoiseType(CrowdNoiseType.GameStart); if (musicSource != null) FadeOutMusic(); return; } _inGame = false; FadeOutAndDisableCrowdSources(); if (musicSource == null) return; if (sceneName == audioDirectory.menuSceneName) { if (comingFromGame) { StopIntroRoutine(); FadeInMenuMusic(); } else HandleMenuSceneMusicWhileIntroMayStillPlay(); } else if (sceneName == audioDirectory.introSceneName) { StartIntroMusic(); } } void StartIntroMusic() { if (audioDirectory.introMusic == null) { EnsureMenuMusic(); return; } if (musicSource.clip == audioDirectory.introMusic && musicSource.isPlaying) return; StopIntroRoutine(); DOTween.Kill(musicSource); musicSource.Stop(); musicSource.clip = audioDirectory.introMusic; musicSource.loop = false; musicSource.volume = _maxMusicVolume; musicSource.Play(); _introRoutine = StartCoroutine(IntroThenMenuWhenDone()); } IEnumerator IntroThenMenuWhenDone() { yield return new WaitWhile(() => musicSource != null && musicSource.isPlaying && musicSource.clip == audioDirectory.introMusic); _introRoutine = null; if (musicSource == null || instance == null) yield break; if (SceneManager.GetActiveScene().name == audioDirectory.gameSceneName) yield break; PlayMenuTrack(_maxMusicVolume); } void HandleMenuSceneMusicWhileIntroMayStillPlay() { if (IsIntroStillPlaying()) { if (_introRoutine == null) _introRoutine = StartCoroutine(IntroThenMenuWhenDone()); return; } if (!IsMenuMusicActiveAtFullVolume()) EnsureMenuMusic(); } bool IsIntroStillPlaying() { return audioDirectory.introMusic != null && musicSource.clip == audioDirectory.introMusic && musicSource.isPlaying; } bool IsMenuMusicActiveAtFullVolume() { return audioDirectory.menuMusic != null && musicSource.clip == audioDirectory.menuMusic && musicSource.isPlaying && Mathf.Approximately(musicSource.volume, _maxMusicVolume); } void EnsureMenuMusic() { if (audioDirectory.menuMusic == null) return; DOTween.Kill(musicSource); if (musicSource.clip == audioDirectory.menuMusic && musicSource.isPlaying && Mathf.Approximately(musicSource.volume, _maxMusicVolume)) return; PlayMenuTrack(_maxMusicVolume); } void PlayMenuTrack(float volume) { if (audioDirectory.menuMusic == null) return; musicSource.Stop(); musicSource.clip = audioDirectory.menuMusic; musicSource.loop = true; musicSource.volume = volume; musicSource.Play(); } void FadeOutMusic() { StopIntroRoutine(); DOTween.Kill(musicSource); musicSource.DOFade(0f, musicFadeDuration); } void FadeInMenuMusic() { if (audioDirectory.menuMusic == null) return; StopIntroRoutine(); DOTween.Kill(musicSource); musicSource.Stop(); musicSource.clip = audioDirectory.menuMusic; musicSource.loop = true; musicSource.volume = 0f; musicSource.Play(); musicSource.DOFade(_maxMusicVolume, musicFadeDuration); } void StopIntroRoutine() { if (_introRoutine != null) { StopCoroutine(_introRoutine); _introRoutine = null; } } #region SFX public void PlayWallHit(float vol=1){ gameSfxSource.PlayOneShot(audioDirectory.wallHits[Random.Range(0, audioDirectory.wallHits.Length)], vol); } public void PlayBallHit(float vol=1){ if (audioDirectory.ballHits != null && audioDirectory.ballHits.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.ballHits[Random.Range(0, audioDirectory.ballHits.Length)],vol); } public void PlayPuckHit(float vol=1){ if (audioDirectory.puckHits != null && audioDirectory.puckHits.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.puckHits[Random.Range(0, audioDirectory.puckHits.Length)],vol); } public void PlayBallHitNet(){ if (audioDirectory.ballHitNet != null && audioDirectory.ballHitNet.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.ballHitNet[Random.Range(0, audioDirectory.ballHitNet.Length)]); } public void PlayGoalScoredSfxSequence() { if (gameSfxSource == null || audioDirectory == null) return; if (_goalSfxSequenceRoutine != null) StopCoroutine(_goalSfxSequenceRoutine); _goalSfxSequenceRoutine = StartCoroutine(GoalScoredSfxSequenceRoutine()); } IEnumerator GoalScoredSfxSequenceRoutine() { CrowdNoiseType typeBefore = _currentCrowdNoiseType; AudioManager.instance.SetCrowdNoiseType(CrowdNoiseType.Cheer); AudioClip net = PickRandomClip(audioDirectory.ballHitNet); if (net != null) { gameSfxSource.PlayOneShot(net); yield return new WaitForSeconds(net.length); } AudioClip whistle = PickRandomClip(audioDirectory.refereeWhistle); if (whistle != null) { gameSfxSource.PlayOneShot(whistle); yield return new WaitForSeconds(whistle.length); } AudioClip goal = PickRandomClip(audioDirectory.refereeGoal); if (goal != null) { gameSfxSource.PlayOneShot(goal); yield return new WaitForSeconds(goal.length); } _goalSfxSequenceRoutine = null; AudioManager.instance.SetCrowdNoiseType(typeBefore); } static AudioClip PickRandomClip(AudioClip[] clips) { if (clips == null || clips.Length == 0) return null; return clips[Random.Range(0, clips.Length)]; } public void PlayCoinsSfx(){ if (audioDirectory.coinsSfx != null && audioDirectory.coinsSfx.Length > 0) uiSfxSource.PlayOneShot(audioDirectory.coinsSfx[Random.Range(0, audioDirectory.coinsSfx.Length)]); } public void PlayTimerBeep(){ if (audioDirectory.timerBeep != null && audioDirectory.timerBeep.Length > 0) uiSfxSource.PlayOneShot(audioDirectory.timerBeep[Random.Range(0, audioDirectory.timerBeep.Length)]); } public void PlayMatchMakingBeep(){ if (audioDirectory.matchMakingBeep != null && audioDirectory.matchMakingBeep.Length > 0) uiSfxSource.PlayOneShot(audioDirectory.matchMakingBeep[Random.Range(0, audioDirectory.matchMakingBeep.Length)]); } public void PlayMatchFound(){ if (audioDirectory.matchFound != null && audioDirectory.matchFound.Length > 0) uiSfxSource.PlayOneShot(audioDirectory.matchFound[Random.Range(0, audioDirectory.matchFound.Length)]); } public void PlayRefereeGoal(){ if (audioDirectory.refereeGoal != null && audioDirectory.refereeGoal.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.refereeGoal[Random.Range(0, audioDirectory.refereeGoal.Length)]); } public void PlayRefereeWhistle(){ if (audioDirectory.refereeWhistle != null && audioDirectory.refereeWhistle.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.refereeWhistle[Random.Range(0, audioDirectory.refereeWhistle.Length)]); } public void PlayCrowdAmbientNoise(){ if (audioDirectory.crowdAmbientNoise != null && audioDirectory.crowdAmbientNoise.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.crowdAmbientNoise[Random.Range(0, audioDirectory.crowdAmbientNoise.Length)]); } public void PlayCrowdAmbientNoiseNearGoal(){ if (audioDirectory.crowdAmbientNoiseNearGoal != null && audioDirectory.crowdAmbientNoiseNearGoal.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.crowdAmbientNoiseNearGoal[Random.Range(0, audioDirectory.crowdAmbientNoiseNearGoal.Length)]); } public void PlayCrowdCheering(){ if (audioDirectory.crowdCheering != null && audioDirectory.crowdCheering.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.crowdCheering[Random.Range(0, audioDirectory.crowdCheering.Length)]); } public void PlayCrowdNoiseMatchStart(){ if (audioDirectory.crowdNoiseMatchStart != null && audioDirectory.crowdNoiseMatchStart.Length > 0) gameSfxSource.PlayOneShot(audioDirectory.crowdNoiseMatchStart[Random.Range(0, audioDirectory.crowdNoiseMatchStart.Length)]); } public void SetIsBallNearGoal(bool nearGoal) { if (!_inGame) return; if (_currentCrowdNoiseType == CrowdNoiseType.Cheer || _currentCrowdNoiseType == CrowdNoiseType.GameStart) return; SetCrowdNoiseType(nearGoal ? CrowdNoiseType.NearGoal : CrowdNoiseType.Normal); } public void SetCrowdNoiseType(CrowdNoiseType type) => SetCrowdNoiseType(type, crowdFadeDuration); public void SetCrowdNoiseType(CrowdNoiseType type, float fadeDuration) { if (audioDirectory == null || crowdSfxSourceA == null || crowdSfxSourceB == null) return; if (_crowdBedInitialized && _currentCrowdNoiseType == type) return; float dur = fadeDuration >= 0f ? fadeDuration : crowdFadeDuration; AudioClip[] clips = GetCrowdClipsForType(audioDirectory, type); if (clips == null || clips.Length == 0) { FadeOutCrowdBed(dur); _currentCrowdNoiseType = type; _crowdBedInitialized = true; return; } AudioClip clip = clips[Random.Range(0, clips.Length)]; if (clip == null) { FadeOutCrowdBed(dur); _currentCrowdNoiseType = type; _crowdBedInitialized = true; return; } AudioSource outgoing = _crowdPrimaryIsA ? crowdSfxSourceA : crowdSfxSourceB; AudioSource incoming = _crowdPrimaryIsA ? crowdSfxSourceB : crowdSfxSourceA; DOTween.Kill(outgoing); DOTween.Kill(incoming); incoming.clip = clip; incoming.loop = true; incoming.volume = 0f; incoming.Play(); outgoing.DOFade(0f, dur).OnComplete(() => { outgoing.Stop(); outgoing.clip = null; }); incoming.DOFade(maxCrowdVolume, dur); _crowdPrimaryIsA = !_crowdPrimaryIsA; _currentCrowdNoiseType = type; _crowdBedInitialized = true; } static AudioClip[] GetCrowdClipsForType(AudioDirectory dir, CrowdNoiseType type) { if (dir == null) return null; switch (type) { case CrowdNoiseType.GameStart: return dir.crowdNoiseMatchStart; case CrowdNoiseType.Normal: return dir.crowdAmbientNoise; case CrowdNoiseType.NearGoal: return dir.crowdAmbientNoiseNearGoal; case CrowdNoiseType.Cheer: return dir.crowdCheering; default: return null; } } void EnableCrowdSourcesForGame() { if (crowdSfxSourceA != null) crowdSfxSourceA.enabled = true; if (crowdSfxSourceB != null) crowdSfxSourceB.enabled = true; } void FadeOutAndDisableCrowdSources() { DOTween.Kill(crowdSfxSourceA); DOTween.Kill(crowdSfxSourceB); int pending = 0; if (crowdSfxSourceA != null) pending++; if (crowdSfxSourceB != null) pending++; if (pending == 0) return; void FinishOne() { pending--; if (pending > 0) return; if (crowdSfxSourceA != null) crowdSfxSourceA.enabled = false; if (crowdSfxSourceB != null) crowdSfxSourceB.enabled = false; _crowdBedInitialized = false; _crowdPrimaryIsA = true; } FadeCrowdSourceOutOrImmediate(crowdSfxSourceA, crowdFadeDuration, FinishOne); FadeCrowdSourceOutOrImmediate(crowdSfxSourceB, crowdFadeDuration, FinishOne); } void FadeOutCrowdBed(float dur) { DOTween.Kill(crowdSfxSourceA); DOTween.Kill(crowdSfxSourceB); FadeCrowdSourceOut(crowdSfxSourceA, dur); FadeCrowdSourceOut(crowdSfxSourceB, dur); _crowdPrimaryIsA = true; } static void FadeCrowdSourceOutOrImmediate(AudioSource source, float dur, System.Action onComplete) { if (source == null) { onComplete?.Invoke(); return; } if (!source.enabled || (!source.isPlaying && Mathf.Approximately(source.volume, 0f))) { source.Stop(); source.clip = null; onComplete?.Invoke(); return; } source.DOFade(0f, dur).OnComplete(() => { source.Stop(); source.clip = null; onComplete?.Invoke(); }); } static void FadeCrowdSourceOut(AudioSource source, float dur) { if (source == null) return; if (!source.isPlaying) { source.clip = null; return; } source.DOFade(0f, dur).OnComplete(() => { source.Stop(); source.clip = null; }); } #endregion } public enum CrowdNoiseType{ GameStart, Normal, NearGoal, Cheer }