Files
soccar2d/Assets/Scripts/Utils/Audio/AudioManager.cs
T

609 lines
18 KiB
C#

using System.Collections;
using DG.Tweening;
using UnityEngine;
using UnityEngine.SceneManagement;
public class AudioManager : MonoBehaviour
{
const string SfxEnabledPrefsKey = "Audio.SfxEnabled";
const string MusicEnabledPrefsKey = "Audio.MusicEnabled";
[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;
LoadAudioPrefs();
}
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
void LoadAudioPrefs()
{
ToggleSFX(PlayerPrefs.GetInt(SfxEnabledPrefsKey, 1) == 1, savePreference: false);
ToggleMusic(PlayerPrefs.GetInt(MusicEnabledPrefsKey, 1) == 1, savePreference: false);
}
public void ToggleSFX(bool isOn)
{
ToggleSFX(isOn, savePreference: true);
}
public void ToggleMusic(bool isOn)
{
ToggleMusic(isOn, savePreference: true);
}
public bool IsSFXOn()
{
if (uiSfxSource != null)
return !uiSfxSource.mute;
if (gameSfxSource != null)
return !gameSfxSource.mute;
if (crowdSfxSourceA != null)
return !crowdSfxSourceA.mute;
if (crowdSfxSourceB != null)
return !crowdSfxSourceB.mute;
return PlayerPrefs.GetInt(SfxEnabledPrefsKey, 1) == 1;
}
public bool IsMusicOn()
{
if (musicSource != null)
return !musicSource.mute;
return PlayerPrefs.GetInt(MusicEnabledPrefsKey, 1) == 1;
}
public void SetSfxVolume(float volume)
{
ToggleSFX(volume > 0f);
}
public void SetMusicVolume(float volume)
{
ToggleMusic(volume > 0f);
}
void ToggleSFX(bool isOn, bool savePreference)
{
if (uiSfxSource != null)
uiSfxSource.mute = !isOn;
if (gameSfxSource != null)
gameSfxSource.mute = !isOn;
if (crowdSfxSourceA != null)
crowdSfxSourceA.mute = !isOn;
if (crowdSfxSourceB != null)
crowdSfxSourceB.mute = !isOn;
if (!savePreference)
return;
PlayerPrefs.SetInt(SfxEnabledPrefsKey, isOn ? 1 : 0);
PlayerPrefs.Save();
}
void ToggleMusic(bool isOn, bool savePreference)
{
if (musicSource != null)
musicSource.mute = !isOn;
if (!savePreference)
return;
PlayerPrefs.SetInt(MusicEnabledPrefsKey, isOn ? 1 : 0);
PlayerPrefs.Save();
}
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
}