using UnityEngine; using UnityEngine.Audio; using AppleHills.Core; using AppleHills.Core.Interfaces; using System.Collections.Generic; using AudioSourceEvents; using System; using Core; using Core.Lifecycle; public class AudioManager : ManagedBehaviour, IPausable { /// /// Play all audio, just music or no audio at all when the game is paused. /// public enum PauseBehavior { PlayAllAudio, MusicOnly, NoAudio } public PauseBehavior currentPauseBehavior; public AudioMixer audioMixer; private AudioListener _audioListener; public AppleAudioSource currentlyPlayingVO; private static AudioManager _instance; private GameObject _player; public List criticalVOSources; public List VOSources; public List musicSources; public List ambienceSources; public List SFXSources; private IAudioEventSource _eventSource; private bool wasInterrupted; /// /// Singleton instance of the AudioManager. /// public static AudioManager Instance => _instance; // ManagedBehaviour configuration public override int ManagedAwakePriority => 30; // Audio infrastructure public override bool AutoRegisterPausable => true; // Auto-register as IPausable internal override void OnManagedAwake() { // Set instance immediately (early initialization) _instance = this; } internal override void OnManagedStart() { // Initialize lists if they were not set in inspector criticalVOSources = criticalVOSources ?? new List(); VOSources = VOSources ?? new List(); musicSources = musicSources ?? new List(); ambienceSources = ambienceSources ?? new List(); SFXSources = SFXSources ?? new List(); // Perform singleton-dependent initialization here if (QuickAccess.Instance != null) { _player = QuickAccess.Instance.PlayerGameObject; if (QuickAccess.Instance.MainCamera != null) { _audioListener = QuickAccess.Instance.MainCamera.GetComponent(); } } else { Logging.Warning("[AudioManager] QuickAccess.Instance is null during OnManagedStart. Some audio references may remain unset."); } // Diagnostic foreach (AppleAudioSource _audioSource in criticalVOSources) { Logging.Debug("Found source: " + _audioSource.name); } } public void SetAudioPauseBehavior(PauseBehavior newPauseBehavior) { switch (newPauseBehavior) { case PauseBehavior.PlayAllAudio: audioMixer.updateMode = AudioMixerUpdateMode.UnscaledTime; AudioListener.pause = false; break; case PauseBehavior.MusicOnly: audioMixer.updateMode = AudioMixerUpdateMode.UnscaledTime; break; //TODO: Pause all audio mixers except music mixer case PauseBehavior.NoAudio: audioMixer.updateMode = AudioMixerUpdateMode.Normal; AudioListener.pause = true; break; } } public LevelAudioObject GetCurrentLevelAudioObject() { Logging.Debug("Audio objects: " + FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None).Length); if (FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None).Length > 1) { Logging.Warning("Warning! More than one LevelAudioObject in the level! Using the first one found"); return FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)[0]; } if (FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None).Length == 0) { Logging.Warning("Error! No LevelAudioObject found, AudioManager might not function properly!"); return null; } else return FindFirstObjectByType(); } public void Pause() { SetAudioPauseBehavior(PauseBehavior.NoAudio); } public void DoResume() { SetAudioPauseBehavior(PauseBehavior.PlayAllAudio); } public void RegisterNewAudioSource(AppleAudioSource newAudioSource) { switch (newAudioSource.audioSourceType) { case AppleAudioSource.AudioSourceType.CriticalVO: criticalVOSources.Add(newAudioSource); break; case AppleAudioSource.AudioSourceType.VO: VOSources.Add(newAudioSource); break; case AppleAudioSource.AudioSourceType.SFX: SFXSources.Add(newAudioSource); break; case AppleAudioSource.AudioSourceType.Ambience: ambienceSources.Add(newAudioSource); break; case AppleAudioSource.AudioSourceType.Music: musicSources.Add(newAudioSource); break; } } /// /// Request playing a VO line. Returns true if whatever is playing is not critical, or weight of requested VO line is lower. /// public bool RequestPlayVO(AppleAudioSource requestedAudioSource) { // If nothing is playing, let the requested audio source play if (currentlyPlayingVO == null) { SetupNewAudioSource(requestedAudioSource); return true; } // If the requested audio is not critical, and the currently playing audio is, tell the request to get bent if (requestedAudioSource.audioSourceType == AppleAudioSource.AudioSourceType.VO && currentlyPlayingVO.audioSourceType == AppleAudioSource.AudioSourceType.CriticalVO) { return false; } // If the requested audio source is the same, interrupt and trigger it again if (currentlyPlayingVO == requestedAudioSource) { InterruptAudioSource(requestedAudioSource); SetupNewAudioSource(requestedAudioSource); return true; } // if the currently playing audio source is not critical, interrupt it and play the requested audio source if (currentlyPlayingVO.audioSourceType != AppleAudioSource.AudioSourceType.CriticalVO) { InterruptAudioSource(requestedAudioSource); SetupNewAudioSource(requestedAudioSource); return true; } // If the requested audio source has the same priority as currently playing source, check the priority of the requested clip if (currentlyPlayingVO.audioSourceType == AppleAudioSource.AudioSourceType.CriticalVO && currentlyPlayingVO.sourcePriority == requestedAudioSource.sourcePriority) { if (currentlyPlayingVO.clipPriority > requestedAudioSource.clipPriority) { InterruptAudioSource(requestedAudioSource); SetupNewAudioSource(requestedAudioSource); return true; } else { return false; } } // If the requested audio source has higher priority than the currently playing source, interrupt the current source and let the requested one play if (currentlyPlayingVO.audioSourceType == AppleAudioSource.AudioSourceType.CriticalVO && currentlyPlayingVO.sourcePriority > requestedAudioSource.sourcePriority) { currentlyPlayingVO.InterruptAudio(requestedAudioSource.name); InterruptAudioSource(requestedAudioSource); SetupNewAudioSource(requestedAudioSource); return true; } // If the requested audio source didn't clear any of the above cases, tell it to get rekt. else { return false; } } private void OnApplicationQuit() { // TODO: Release the handles safely ReleaseAllHandles(); } private void SetupNewAudioSource(AppleAudioSource audioSource) { if (audioSource.audioSource.resource == null) { } else { currentlyPlayingVO = audioSource; _eventSource = audioSource.audioSource.RequestEventHandlers(); _eventSource.AudioStopped += OnAudioStopped; _eventSource.AudioStarted += OnAudioStarted; } } private void OnAudioStopped(object sender, EventArgs e) { if (wasInterrupted) { ResetAudioSource(); } else { currentlyPlayingVO = null; ResetAudioSource(); } } private void OnAudioStarted(object sender, EventArgs e) { } private void ResetAudioSource() { _eventSource.AudioStopped -= OnAudioStopped; _eventSource.AudioStarted -= OnAudioStarted; wasInterrupted = false; } private void InterruptAudioSource(AppleAudioSource newAudioSource) { wasInterrupted = true; //currentlyPlayingVO.InterruptAudio(newAudioSource.name); InterruptAllVOSources(); ResetAudioSource(); currentlyPlayingVO = newAudioSource; } private void InterruptAllVOSources() { foreach (AppleAudioSource source in criticalVOSources) { if (source == null) { return; } else { source.InterruptAudio("GlobalInterrupt"); } } foreach (AppleAudioSource source in VOSources) { source.InterruptAudio("GlobalInterrupt"); } } }