EscapeShooter/My project/Assets/Project Files/Modules/Audio/Scripts/Runtime/AudioController.cs

345 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Watermelon
{
public class AudioController
{
private static AudioController instance;
private bool isInitialized;
private List<AudioSourceCase> audioSourcesPool;
private AudioRegistry registry;
private AudioListener audioListener;
private AudioSave save;
// Default 3D audio settings
private readonly float maxDistance;
private readonly float spread;
private readonly AnimationCurve rolloffCurve;
private event OnVolumeChangedCallback volumeChanged;
private Dictionary<AudioType, float> volumeDictionary;
private AudioType[] audioTypes;
// ─── Constructor ─────────────────────────────────────────────────
/// <param name="poolSize">Initial AudioSource pool size.</param>
/// <param name="registry">Optional. Enables GetClip(name) lookups.</param>
/// <param name="maxDistance">Default max distance for 3D sounds.</param>
/// <param name="spread">Default spread for 3D sounds (0–360).</param>
/// <param name="rolloffCurve">Custom rolloff curve. Pass null for linear default.</param>
public AudioController(
int poolSize,
AudioRegistry registry = null,
float maxDistance = 30,
float spread = 180,
AnimationCurve rolloffCurve = null)
{
if (instance != null)
Debug.LogWarning("[AudioController]: Previous instance replaced. Call Unload() before creating a new one.");
instance = this;
this.maxDistance = maxDistance;
this.spread = spread;
this.rolloffCurve = rolloffCurve ?? new AnimationCurve(new Keyframe(0.0f, 1.0f), new Keyframe(1.0f, 0.0f));
this.registry = registry;
registry?.BuildRuntimeLookup();
save = SaveController.GetSaveObject<AudioSave>("audio");
audioTypes = EnumUtils.GetEnumArray<AudioType>();
volumeDictionary = new Dictionary<AudioType, float>();
if (save.VolumeDatas != null)
{
foreach (AudioSave.VolumeData volumeData in save.VolumeDatas)
{
volumeDictionary.Add(volumeData.AudioType, volumeData.Volume);
}
}
CreateAudioListener();
audioSourcesPool = new List<AudioSourceCase>();
for (int i = 0; i < poolSize; i++)
{
audioSourcesPool.Add(new AudioSourceCase());
}
isInitialized = true;
}
// ─── Static facade: properties ───────────────────────────────────
public static AudioListener AudioListener => instance?.audioListener;
public static bool IsInitialized => instance != null && instance.isInitialized;
// ─── Static facade: event ────────────────────────────────────────
public static event OnVolumeChangedCallback VolumeChanged
{
add
{
if (!CheckInitialized()) return;
instance.volumeChanged += value;
}
remove
{
if (instance != null)
instance.volumeChanged -= value;
}
}
// ─── Static facade: playback ─────────────────────────────────────
public static void PlaySound(AudioClip clip, float volumePercentage = 1.0f, float pitch = 1.0f, float minDelay = 0f)
{
if (!CheckInitialized()) return;
instance.PlaySoundInternal(clip, volumePercentage, pitch);
}
public static void PlaySound(AudioClip clip, Vector3 position, float volumePercentage = 1.0f, float pitch = 1.0f, float minDelay = 0f)
{
if (!CheckInitialized()) return;
instance.PlaySoundInternal(clip, position, volumePercentage, pitch);
}
// ─── Static facade: registry lookup ─────────────────────────────
public static AudioClip GetClip(string name)
{
if (!CheckInitialized()) return null;
if (instance.registry == null)
{
Debug.LogWarning("[AudioController]: No AudioRegistry assigned. Cannot get clip by name.");
return null;
}
return instance.registry.GetClip(name);
}
// ─── Static facade: volume ───────────────────────────────────────
public static float GetVolume(AudioType audioType)
{
if (!CheckInitialized()) return 1.0f;
return instance.GetVolumeInternal(audioType);
}
public static void SetVolume(AudioType audioType, float volume)
{
if (!CheckInitialized()) return;
instance.SetVolumeInternal(audioType, volume);
}
public static bool IsAudioTypeActive(AudioType audioType)
{
if (!CheckInitialized()) return false;
return instance.GetVolumeInternal(audioType) == 1.0f;
}
// ─── Static facade: sources & listener ──────────────────────────
public static void ReleaseSources()
{
if (!CheckInitialized()) return;
instance.ReleaseSourcesInternal();
}
// Called by AudioSourceCase during pool creation — no init warning needed.
public static void ApplyDefaultSettings(ref AudioSource audioSource)
{
if (instance == null) return;
instance.ApplyDefaultSettingsInternal(ref audioSource);
}
public static Transform AttachAudioListener(Transform parentObject)
{
if (!CheckInitialized()) return null;
return instance.AttachAudioListenerInternal(parentObject);
}
public static void ResetAudioListenerParent()
{
if (!CheckInitialized()) return;
instance.ResetAudioListenerParentInternal();
}
// ─── Helpers ─────────────────────────────────────────────────────
private static bool CheckInitialized([CallerMemberName] string caller = "")
{
if (instance == null || !instance.isInitialized)
{
Debug.LogWarning($"[AudioController]: '{caller}' called before initialization.");
return false;
}
return true;
}
// ─── Instance implementation ──────────────────────────────────────
private void ApplyDefaultSettingsInternal(ref AudioSource audioSource)
{
audioSource.maxDistance = maxDistance;
audioSource.spread = spread;
audioSource.rolloffMode = AudioRolloffMode.Custom;
audioSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, rolloffCurve);
}
private void CreateAudioListener()
{
if (audioListener != null)
return;
GameObject listenerObject = new GameObject("[AUDIO LISTENER]");
listenerObject.transform.position = Vector3.zero;
GameObject.DontDestroyOnLoad(listenerObject);
audioListener = listenerObject.AddComponent<AudioListener>();
}
private Transform AttachAudioListenerInternal(Transform parentObject)
{
if (audioListener == null)
CreateAudioListener();
Transform t = audioListener.transform;
t.SetParent(parentObject);
t.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
return t;
}
private void ResetAudioListenerParentInternal()
{
if (audioListener == null) return;
audioListener.transform.SetParent(null);
GameObject.DontDestroyOnLoad(audioListener.gameObject);
}
private void ReleaseSourcesInternal()
{
foreach (AudioSourceCase sourceCase in audioSourcesPool)
{
if (sourceCase.IsPlaying)
sourceCase.AudioSource.Stop();
}
}
private void PlaySoundInternal(AudioClip clip, float volumePercentage, float pitch)
{
if (clip == null)
{
Debug.LogError("[AudioController]: Audio clip is null");
return;
}
AudioSourceCase sourceCase = GetAudioSource();
AudioSource source = sourceCase.AudioSource;
source.spatialBlend = 0.0f; // 2D sound
source.pitch = pitch;
sourceCase.Play(clip, volumePercentage, AudioType.Sound);
}
private void PlaySoundInternal(AudioClip clip, Vector3 position, float volumePercentage, float pitch)
{
if (clip == null)
{
Debug.LogError("[AudioController]: Audio clip is null");
return;
}
AudioSourceCase sourceCase = GetAudioSource();
AudioSource source = sourceCase.AudioSource;
source.transform.position = position;
source.spatialBlend = 1.0f; // 3D sound
source.pitch = pitch;
sourceCase.Play(clip, volumePercentage, AudioType.Sound);
}
private AudioSourceCase GetAudioSource()
{
foreach (AudioSourceCase audioSource in audioSourcesPool)
{
if (!audioSource.IsPlaying)
return audioSource;
}
AudioSourceCase createdSource = new AudioSourceCase();
audioSourcesPool.Add(createdSource);
return createdSource;
}
private float GetVolumeInternal(AudioType audioType)
{
if (volumeDictionary.ContainsKey(audioType))
return volumeDictionary[audioType];
return 1.0f;
}
private void SetVolumeInternal(AudioType audioType, float volume)
{
foreach (AudioSourceCase audioSource in audioSourcesPool)
{
audioSource.OverrideVolume(audioType, volume);
}
volumeDictionary[audioType] = volume;
FlushVolumesToSave();
SaveController.MarkAsSaveIsRequired();
volumeChanged?.Invoke(audioType, volume);
}
private void FlushVolumesToSave()
{
save.VolumeDatas = new AudioSave.VolumeData[audioTypes.Length];
for (int i = 0; i < audioTypes.Length; i++)
save.VolumeDatas[i] = new AudioSave.VolumeData { AudioType = audioTypes[i], Volume = GetVolumeInternal(audioTypes[i]) };
}
public void Unload()
{
if (save != null && volumeDictionary != null)
FlushVolumesToSave();
audioSourcesPool = null;
registry = null;
audioListener = null;
save = null;
volumeDictionary = null;
audioTypes = null;
volumeChanged = null;
isInitialized = false;
instance = null;
}
public delegate void OnVolumeChangedCallback(AudioType audioType, float volume);
}
public enum AudioType
{
Music = 0,
Sound = 1
}
}