91 lines
3.3 KiB
C#
91 lines
3.3 KiB
C#
// Assets/_Project/Scripts/Audio/AudioManager.cs
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace TD.Audio
|
||
{
|
||
public class AudioManager : MonoBehaviour
|
||
{
|
||
[System.Serializable]
|
||
public struct CategoryConfig
|
||
{
|
||
public AudioCategory category;
|
||
public int maxVoices;
|
||
|
||
// NOTE: currently inert. It seeds each pooled AudioSource at init, but every
|
||
// Play() call overwrites src.volume with (per-sound volume × SFX slider), so
|
||
// this value never reaches the mix. Left as-is rather than "fixed" because
|
||
// multiplying it in would halve every existing sound (authored scenes use 0.5).
|
||
// Wire it up only alongside a pass to re-level the per-sound volumes.
|
||
[Range(0f, 1f)]
|
||
public float volume;
|
||
}
|
||
|
||
public static AudioManager Instance { get; private set; }
|
||
|
||
[SerializeField] private CategoryConfig[] categories;
|
||
|
||
private Dictionary<AudioCategory, AudioSource[]> pools;
|
||
private Dictionary<AudioCategory, int> indices;
|
||
|
||
private void Awake()
|
||
{
|
||
if (Instance != null)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
Instance = this;
|
||
InitializePools();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (Instance == this) Instance = null;
|
||
}
|
||
|
||
private void InitializePools()
|
||
{
|
||
pools = new Dictionary<AudioCategory, AudioSource[]>();
|
||
indices = new Dictionary<AudioCategory, int>();
|
||
|
||
foreach (var config in categories)
|
||
{
|
||
var pool = new AudioSource[config.maxVoices];
|
||
for (int i = 0; i < config.maxVoices; i++)
|
||
{
|
||
var src = gameObject.AddComponent<AudioSource>();
|
||
src.playOnAwake = false;
|
||
src.volume = config.volume;
|
||
pool[i] = src;
|
||
}
|
||
pools[config.category] = pool;
|
||
indices[config.category] = 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Plays a one-shot on the given category's voice pool. <paramref name="volume"/> is
|
||
/// the per-sound authored level; it's scaled by the player's SFX slider
|
||
/// (<see cref="AudioVolumeSettings.SfxScalar"/>) on the way in.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// The scalar is sampled at play time, so a sound already in flight keeps the volume
|
||
/// it started with when the slider moves. Every SFX here is short, so that's
|
||
/// imperceptible and avoids having to track live voices.
|
||
/// </remarks>
|
||
public void Play(AudioClip clip, AudioCategory category, float pitch = 1f, float volume = 1f)
|
||
{
|
||
if (clip == null) return;
|
||
if (!pools.TryGetValue(category, out var pool)) return;
|
||
|
||
int idx = indices[category];
|
||
var src = pool[idx % pool.Length];
|
||
src.pitch = pitch;
|
||
src.volume = volume * AudioVolumeSettings.SfxScalar;
|
||
src.clip = clip;
|
||
src.Play();
|
||
indices[category] = idx + 1;
|
||
}
|
||
}
|
||
}
|