using UnityEngine; using System.Collections.Generic; using Cysharp.Threading.Tasks; using BulletHellTemplate.Audio; namespace BulletHellTemplate { /// /// Manages all game audio, storing per-category volumes (Master, VFX, Ambience, Custom tags) /// and computing the effective output as Master × Category. /// Volumes are persisted with in an encrypted JSON file so /// they survive the “clean login” flow. /// public sealed class AudioManager : MonoBehaviour { /*────────────────────────── Serialized Fields ──────────────────────────*/ [Header("Audio Sources")] [Tooltip("Main AudioSource for sounds that follow the listener (Master output).")] public AudioSource masterAudioSource; [Tooltip("Dedicated looping AudioSource for ambient tracks.")] public AudioSource ambientAudioSource; [Tooltip("One-shot AudioSource for loading/menu clips.")] public AudioSource loadingAudioSource; [Header("Volume Settings (0–1)")] [Range(0, 1)] public float masterVolume = 1f; // Multiplicative root [Range(0, 1)] public float vfxVolume = 1f; // Relative to Master [Range(0, 1)] public float ambienceVolume = 1f; // Relative to Master [Tooltip("Extra categories (e.g. “ui”, “others”)")] public List customTagVolumes = new(); [Header("Audio Limitations")] [Tooltip("Global cap – simultaneous clips (0 = unlimited).")] public int maxConcurrentAudio = 20; /*────────────────────────── Runtime ───────────────────────────*/ private int _currentAudioCount; public static AudioManager Singleton; /*────────────────────────── Persistence Keys ──────────────────────────*/ private const string KEY_MASTER = "AUDIO_MASTER_VOLUME"; private const string KEY_VFX = "AUDIO_VFX_VOLUME"; private const string KEY_AMBIENCE = "AUDIO_AMBIENCE_VOLUME"; /*────────────────────────── Unity - Lifecycle ─────────────────────────*/ private void Awake() { if (Singleton != null) { Destroy(gameObject); return; } Singleton = this; DontDestroyOnLoad(gameObject); ambientAudioSource.loop = true; LoadVolumeSettings(); ApplyVolumeSettings(); } /*────────────────────────── Public API – Volume Setters ───────────────*/ public void SetMasterVolume(float value) { masterVolume = Mathf.Clamp01(value); ApplyVolumeSettings(); SaveVolumeSettings(); } public void SetVFXVolume(float value) { vfxVolume = Mathf.Clamp01(value); ApplyVolumeSettings(); SaveVolumeSettings(); } public void SetAmbienceVolume(float value) { ambienceVolume = Mathf.Clamp01(value); ApplyVolumeSettings(); SaveVolumeSettings(); } /// Sets a custom tag volume (e.g. “ui”, “others”). public void SetCustomTagVolume(string tag, float value) { value = Mathf.Clamp01(value); for (int i = 0; i < customTagVolumes.Count; ++i) if (customTagVolumes[i].tag == tag) { customTagVolumes[i] = new CustomAudioTag { tag = tag, volume = value }; SaveVolumeSettings(); return; } customTagVolumes.Add(new CustomAudioTag { tag = tag, volume = value }); SaveVolumeSettings(); } /*────────────────────────── Playback Helpers ──────────────────────────*/ /// /// Plays an ambient audio clip in a loop with the specified tag. /// Does not affect the current audio count or maxConcurrentAudio. /// /// The audio clip to play. /// The tag used to determine the volume. public void PlayAmbientAudio(AudioClip clip, string tag = "ambient") { if (clip == null) return; loadingAudioSource.Stop(); ambientAudioSource.Stop(); ambientAudioSource.clip = clip; ambientAudioSource.volume = GetEffectiveVolume(tag); ambientAudioSource.Play(); } /// /// Plays a loading menu audio clip and stops the ambient audio. /// The ambient audio will be stopped and prevented from playing again until reloaded. /// /// The audio clip to play. /// The tag used to determine the volume. public void PlayLoadingMenu(AudioClip clip, string tag = "master") { if (clip == null) return; ambientAudioSource.Stop(); loadingAudioSource.volume = GetEffectiveVolume(tag); loadingAudioSource.PlayOneShot(clip); } /// /// New signature – plays the clip at an arbitrary **world position**. /// Ideal para explosões, projéteis, etc. /// public void PlayAudio(AudioClip clip, string tag, Vector3 worldPos) { if (clip == null) return; if (maxConcurrentAudio > 0 && _currentAudioCount >= maxConcurrentAudio) return; float vol = GetEffectiveVolume(tag); SoundEffectsPool.PlayClip(clip, worldPos, vol); _currentAudioCount++; ReduceAudioCountAsync(clip.length).Forget(); } /// /// Old signature – plays *at the listener* (2D). /// public void PlayAudio(AudioClip clip, string tag) => PlayAudio(clip, tag, Camera.main ? Camera.main.transform.position : Vector3.zero); /// /// Stops all currently playing audio, including ambient and loading audio. /// This function is useful when the game ends or the player dies, /// ensuring that no audio is left playing. /// public void StopAllAudioPlay() { masterAudioSource.Stop(); ambientAudioSource.Stop(); loadingAudioSource.Stop(); _currentAudioCount = 0; } public void StopLoadingAudioPlay() => loadingAudioSource.Stop(); /// /// Gets the volume based on the tag. /// private float GetEffectiveVolume(string tag) { tag = tag.ToLowerInvariant(); if (tag == "master") return masterVolume; float category = tag switch { "vfx" => vfxVolume, "ambient" => ambienceVolume, _ => GetCustomTag(tag) }; return masterVolume * category; } private float GetCustomTag(string tag) { foreach (var t in customTagVolumes) if (t.tag == tag) return t.volume; Debug.LogWarning($"Audio tag '{tag}' not found – using 1."); return 1f; } /// /// Reduces the audio count after a certain amount of time (the clip length). /// private async UniTaskVoid ReduceAudioCountAsync(float seconds) { await UniTask.Delay(System.TimeSpan.FromSeconds(seconds)); _currentAudioCount = Mathf.Max(0, _currentAudioCount - 1); } /*────────────────────────── Persistence ──────────────────────────*/ private void LoadVolumeSettings() { masterVolume = SecurePrefs.GetDecryptedFloatFromFile(KEY_MASTER, masterVolume); vfxVolume = SecurePrefs.GetDecryptedFloatFromFile(KEY_VFX, vfxVolume); ambienceVolume = SecurePrefs.GetDecryptedFloatFromFile(KEY_AMBIENCE, ambienceVolume); } private void SaveVolumeSettings() { SecurePrefs.SetEncryptedFloatToFile(KEY_MASTER, masterVolume); SecurePrefs.SetEncryptedFloatToFile(KEY_VFX, vfxVolume); SecurePrefs.SetEncryptedFloatToFile(KEY_AMBIENCE, ambienceVolume); } private void ApplyVolumeSettings() { masterAudioSource.volume = masterVolume; ambientAudioSource.volume = masterVolume * ambienceVolume; loadingAudioSource.volume = masterVolume; } } /// Simple pair (tag, volume) used by . [System.Serializable] public struct CustomAudioTag { public string tag; [Range(0, 1)] public float volume; } }