Merge branch 'unified-audio-manager' into main
# Conflicts: # Assets/_Project/Definitions/Towers/TeslaCoilTower.asset
This commit is contained in:
commit
a7b2d1854d
11 changed files with 197 additions and 110 deletions
8
Assets/_Project/Scripts/Audio.meta
Normal file
8
Assets/_Project/Scripts/Audio.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d11cb1aeddfee3009879738a45665945
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
5
Assets/_Project/Scripts/Audio/AudioCategory.cs
Normal file
5
Assets/_Project/Scripts/Audio/AudioCategory.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Assets/_Project/Scripts/Audio/AudioCategory.cs
|
||||
namespace TD.Audio
|
||||
{
|
||||
public enum AudioCategory { TowerFire, EnemyHitDeath, UI, Music }
|
||||
}
|
||||
2
Assets/_Project/Scripts/Audio/AudioCategory.cs.meta
Normal file
2
Assets/_Project/Scripts/Audio/AudioCategory.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2f652b06e3d150798895893892820cc7
|
||||
74
Assets/_Project/Scripts/Audio/AudioManager.cs
Normal file
74
Assets/_Project/Scripts/Audio/AudioManager.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// 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;
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
public void Play(AudioClip clip, AudioCategory category, float pitch = 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.clip = clip;
|
||||
src.Play();
|
||||
indices[category] = idx + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Project/Scripts/Audio/AudioManager.cs.meta
Normal file
2
Assets/_Project/Scripts/Audio/AudioManager.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9ec21c01769ef58d1ac26ba89668eeaa
|
||||
36
Assets/_Project/Scripts/Audio/README.md
Normal file
36
Assets/_Project/Scripts/Audio/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Audio System
|
||||
|
||||
Centralised voice-limited audio for the game. Lives in `Assets/_Project/Scripts/Audio/`.
|
||||
|
||||
## How it works
|
||||
|
||||
`AudioManager` is a per-scene MonoBehaviour singleton. On `Awake` it pre-allocates a pool of `AudioSource` components (on its own GameObject) for each sound category. Callers request playback via `AudioManager.Instance.Play(clip, category, pitch)` — the manager picks the next source in that category's pool using round-robin, interrupting the oldest sound if all voices are busy.
|
||||
|
||||
## Categories (`AudioCategory` enum)
|
||||
|
||||
| Category | Typical use |
|
||||
|---|---|
|
||||
| `TowerFire` | Tower attack sounds |
|
||||
| `EnemyHitDeath` | Enemy damage and death sounds |
|
||||
| `UI` | Button clicks, placement confirmation, etc. |
|
||||
| `Music` | Background music |
|
||||
|
||||
Voice limits and volume per category are configured in the Inspector on the `AudioManager` scene GameObject.
|
||||
|
||||
## Scene setup
|
||||
|
||||
An empty GameObject named `AudioManager` must exist in each gameplay scene with the `AudioManager` component attached. Configure the `Categories` array in the Inspector — one entry per `AudioCategory` value.
|
||||
|
||||
Suggested defaults:
|
||||
- TowerFire: 4 voices, volume 1.0
|
||||
- EnemyHitDeath: 8 voices, volume 1.0
|
||||
- UI: 4 voices, volume 1.0
|
||||
- Music: 2 voices, volume 0.8
|
||||
|
||||
## Adding a new sound-emitting component
|
||||
|
||||
1. Get a reference to an `AudioClip` via `[SerializeField]`
|
||||
2. Call `AudioManager.Instance?.Play(clip, AudioCategory.YourCategory)` at the point the sound should trigger
|
||||
3. Pitch randomization (if desired) is the caller's responsibility: `Random.Range(minPitch, maxPitch)` passed as the third argument
|
||||
|
||||
See `TeslaTowerFireSound.cs` in `Scripts/Combat/` as a reference implementation.
|
||||
7
Assets/_Project/Scripts/Audio/README.md.meta
Normal file
7
Assets/_Project/Scripts/Audio/README.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4acf87ebbb79a6ce482e6e4767d27b41
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// Assets/_Project/Scripts/Combat/TeslaTowerFireSound.cs
|
||||
using TD.Audio;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TD.Combat
|
||||
|
|
@ -14,7 +15,6 @@ namespace TD.Combat
|
|||
/// as the Tesla Coil gains more audio (idle hum, charge-up, etc.).
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(TowerCombat))]
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
public class TeslaTowerFireSound : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Sound played each time the coil discharges.")]
|
||||
|
|
@ -25,12 +25,10 @@ namespace TD.Combat
|
|||
[SerializeField] private float maxPitch = 1.07f;
|
||||
|
||||
private TowerCombat combat;
|
||||
private AudioSource audioSource;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
combat = GetComponent<TowerCombat>();
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
combat = GetComponent<TowerCombat>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
|
|
@ -45,12 +43,7 @@ namespace TD.Combat
|
|||
|
||||
private void HandleAreaFired(Vector3[] positions)
|
||||
{
|
||||
if (fireClip != null)
|
||||
{
|
||||
audioSource.pitch = Random.Range(minPitch, maxPitch);
|
||||
audioSource.clip = fireClip;
|
||||
audioSource.Play();
|
||||
}
|
||||
AudioManager.Instance?.Play(fireClip, AudioCategory.TowerFire, Random.Range(minPitch, maxPitch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue