234 lines
9.4 KiB
C#
Raw Normal View History

2025-09-19 19:43:49 +05:00
using UnityEngine;
2025-09-19 14:56:58 +05:00
using System.Collections.Generic;
2025-09-19 19:43:49 +05:00
using Cysharp.Threading.Tasks;
using BulletHellTemplate.Audio;
2025-09-19 14:56:58 +05:00
namespace BulletHellTemplate
{
2025-09-19 19:43:49 +05:00
2025-09-19 14:56:58 +05:00
/// <summary>
2025-09-19 19:43:49 +05:00
/// Manages all game audio, storing per-category volumes (Master, VFX, Ambience, Custom tags)
/// and computing the effective output as <c>Master × Category</c>.
/// Volumes are persisted with <see cref="SecurePrefs"/> in an encrypted JSON file so
/// they survive the “clean login” flow.
2025-09-19 14:56:58 +05:00
/// </summary>
2025-09-19 19:43:49 +05:00
public sealed class AudioManager : MonoBehaviour
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
/*────────────────────────── Serialized Fields ──────────────────────────*/
[Header("Audio Sources")]
[Tooltip("Main AudioSource for sounds that follow the listener (Master output).")]
2025-09-19 14:56:58 +05:00
public AudioSource masterAudioSource;
2025-09-19 19:43:49 +05:00
[Tooltip("Dedicated looping AudioSource for ambient tracks.")]
public AudioSource ambientAudioSource;
[Tooltip("One-shot AudioSource for loading/menu clips.")]
public AudioSource loadingAudioSource;
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
[Header("Volume Settings (01)")]
[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
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
[Tooltip("Extra categories (e.g. “ui”, “others”)")]
public List<CustomAudioTag> customTagVolumes = new();
2025-09-19 14:56:58 +05:00
[Header("Audio Limitations")]
2025-09-19 19:43:49 +05:00
[Tooltip("Global cap simultaneous clips (0 = unlimited).")]
public int maxConcurrentAudio = 20;
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
/*────────────────────────── Runtime ───────────────────────────*/
private int _currentAudioCount;
2025-09-19 14:56:58 +05:00
public static AudioManager Singleton;
2025-09-19 19:43:49 +05:00
/*────────────────────────── 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 ─────────────────────────*/
2025-09-19 14:56:58 +05:00
private void Awake()
{
2025-09-19 19:43:49 +05:00
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();
}
/// <summary>Sets a custom tag volume (e.g. “ui”, “others”).</summary>
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();
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/*────────────────────────── Playback Helpers ──────────────────────────*/
2025-09-19 14:56:58 +05:00
/// <summary>
/// Plays an ambient audio clip in a loop with the specified tag.
/// Does not affect the current audio count or maxConcurrentAudio.
/// </summary>
/// <param name="clip">The audio clip to play.</param>
/// <param name="tag">The tag used to determine the volume.</param>
2025-09-19 19:43:49 +05:00
public void PlayAmbientAudio(AudioClip clip, string tag = "ambient")
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
if (clip == null) return;
loadingAudioSource.Stop();
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
ambientAudioSource.Stop();
2025-09-19 14:56:58 +05:00
ambientAudioSource.clip = clip;
2025-09-19 19:43:49 +05:00
ambientAudioSource.volume = GetEffectiveVolume(tag);
2025-09-19 14:56:58 +05:00
ambientAudioSource.Play();
}
/// <summary>
/// Plays a loading menu audio clip and stops the ambient audio.
/// The ambient audio will be stopped and prevented from playing again until reloaded.
/// </summary>
/// <param name="clip">The audio clip to play.</param>
/// <param name="tag">The tag used to determine the volume.</param>
2025-09-19 19:43:49 +05:00
public void PlayLoadingMenu(AudioClip clip, string tag = "master")
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
if (clip == null) return;
ambientAudioSource.Stop();
loadingAudioSource.volume = GetEffectiveVolume(tag);
loadingAudioSource.PlayOneShot(clip);
2025-09-19 14:56:58 +05:00
}
/// <summary>
2025-09-19 19:43:49 +05:00
/// New signature plays the clip at an arbitrary **world position**.
/// Ideal para explosões, projéteis, etc.
2025-09-19 14:56:58 +05:00
/// </summary>
2025-09-19 19:43:49 +05:00
public void PlayAudio(AudioClip clip, string tag, Vector3 worldPos)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
if (clip == null) return;
if (maxConcurrentAudio > 0 && _currentAudioCount >= maxConcurrentAudio) return;
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
float vol = GetEffectiveVolume(tag);
SoundEffectsPool.PlayClip(clip, worldPos, vol);
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
_currentAudioCount++;
ReduceAudioCountAsync(clip.length).Forget();
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/// <summary>
/// Old signature plays *at the listener* (2D).
/// </summary>
public void PlayAudio(AudioClip clip, string tag)
=> PlayAudio(clip, tag, Camera.main ? Camera.main.transform.position : Vector3.zero);
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
/// <summary>
/// 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.
/// </summary>
public void StopAllAudioPlay()
{
masterAudioSource.Stop();
ambientAudioSource.Stop();
loadingAudioSource.Stop();
_currentAudioCount = 0;
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
public void StopLoadingAudioPlay() => loadingAudioSource.Stop();
2025-09-19 14:56:58 +05:00
/// <summary>
/// Gets the volume based on the tag.
/// </summary>
2025-09-19 19:43:49 +05:00
private float GetEffectiveVolume(string tag)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
tag = tag.ToLowerInvariant();
if (tag == "master") return masterVolume;
float category = tag switch
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
"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;
2025-09-19 14:56:58 +05:00
}
/// <summary>
/// Reduces the audio count after a certain amount of time (the clip length).
/// </summary>
2025-09-19 19:43:49 +05:00
private async UniTaskVoid ReduceAudioCountAsync(float seconds)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
await UniTask.Delay(System.TimeSpan.FromSeconds(seconds));
_currentAudioCount = Mathf.Max(0, _currentAudioCount - 1);
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/*────────────────────────── 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;
}
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/// <summary>Simple pair (tag, volume) used by <see cref="AudioManager"/>.</summary>
[System.Serializable]
public struct CustomAudioTag
{
public string tag;
[Range(0, 1)] public float volume;
}
2025-09-19 14:56:58 +05:00
}