97 lines
3.9 KiB
C#
97 lines
3.9 KiB
C#
// Assets/_Project/Scripts/Audio/AudioVolumeSettings.cs
|
|
using UnityEngine;
|
|
|
|
namespace TD.Audio
|
|
{
|
|
/// <summary>
|
|
/// Player-facing volume mix, persisted in <see cref="PlayerPrefs"/> and shared by
|
|
/// every audio consumer in the game. Two buses:
|
|
/// <list type="bullet">
|
|
/// <item><b>Music</b> — the looping scene track played by <see cref="MusicPlayer"/>.</item>
|
|
/// <item><b>SFX</b> — everything routed through <see cref="AudioManager"/>
|
|
/// (spells, tower fire, build/land, enemy death, sell, UI clicks).</item>
|
|
/// </list>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>Why not named AudioSettings.</b> <c>UnityEngine.AudioSettings</c> already
|
|
/// exists, so a <c>TD.Audio.AudioSettings</c> would be an ambiguous reference in any
|
|
/// file that has both <c>using UnityEngine;</c> and <c>using TD.Audio;</c>.</para>
|
|
///
|
|
/// <para><b>Units.</b> Public values are ints 0..100 — the numbers the settings slider
|
|
/// shows. Consumers multiply by <see cref="MusicScalar"/> / <see cref="SfxScalar"/>,
|
|
/// which apply a squared taper so the slider's lower half isn't perceptually dead
|
|
/// (linear amplitude drops off much faster than perceived loudness). At 100 the
|
|
/// scalar is exactly 1, so the default mix is byte-identical to the pre-settings
|
|
/// behaviour.</para>
|
|
///
|
|
/// <para><b>Live updates.</b> <see cref="OnChanged"/> fires on every mutation.
|
|
/// Continuous sources (music) subscribe and re-apply; one-shots read the scalar at
|
|
/// play time, so a mid-flight SFX keeps the volume it started with. That's fine —
|
|
/// they're all short.</para>
|
|
/// </remarks>
|
|
public static class AudioVolumeSettings
|
|
{
|
|
public const int MinVolume = 0;
|
|
public const int MaxVolume = 100;
|
|
|
|
private const int DefaultVolume = 100;
|
|
private const string MusicKey = "td.audio.music";
|
|
private const string SfxKey = "td.audio.sfx";
|
|
|
|
/// <summary>Raised whenever music or SFX volume changes.</summary>
|
|
public static event System.Action OnChanged;
|
|
|
|
// -1 = not yet read from PlayerPrefs. Statics are cleared on domain reload
|
|
// (entering play mode in the editor), so the lazy load re-runs each session.
|
|
private static int music = -1;
|
|
private static int sfx = -1;
|
|
|
|
public static int MusicVolume
|
|
{
|
|
get
|
|
{
|
|
if (music < 0) music = PlayerPrefs.GetInt(MusicKey, DefaultVolume);
|
|
return music;
|
|
}
|
|
set => Set(ref music, MusicKey, value);
|
|
}
|
|
|
|
public static int SfxVolume
|
|
{
|
|
get
|
|
{
|
|
if (sfx < 0) sfx = PlayerPrefs.GetInt(SfxKey, DefaultVolume);
|
|
return sfx;
|
|
}
|
|
set => Set(ref sfx, SfxKey, value);
|
|
}
|
|
|
|
/// <summary>Amplitude multiplier (0..1) for the music bus.</summary>
|
|
public static float MusicScalar => ToScalar(MusicVolume);
|
|
|
|
/// <summary>Amplitude multiplier (0..1) for the sound-effects bus.</summary>
|
|
public static float SfxScalar => ToScalar(SfxVolume);
|
|
|
|
/// <summary>
|
|
/// Flushes PlayerPrefs to disk. Setters only write the in-memory pref (cheap
|
|
/// enough to call on every frame of a slider drag); call this once when the
|
|
/// settings UI closes so the choice survives a crash.
|
|
/// </summary>
|
|
public static void Flush() => PlayerPrefs.Save();
|
|
|
|
private static void Set(ref int field, string key, int value)
|
|
{
|
|
int clamped = Mathf.Clamp(value, MinVolume, MaxVolume);
|
|
if (field == clamped) return;
|
|
field = clamped;
|
|
PlayerPrefs.SetInt(key, clamped);
|
|
OnChanged?.Invoke();
|
|
}
|
|
|
|
private static float ToScalar(int volume)
|
|
{
|
|
float linear = Mathf.Clamp01(volume / (float)MaxVolume);
|
|
return linear * linear;
|
|
}
|
|
}
|
|
}
|