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