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

345 lines
12 KiB
C#
Raw Normal View History

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
}
}