1.先贴代码
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using UnityEngine.SceneManagement;
public class AudioManager : MonoSingleton<AudioManager>
{
[Header("Audio Settings")]
[SerializeField] private int initialPoolSize = 5; // 初始音效对象池大小
[SerializeField] private float defaultVolume = 1f; // 默认全局音量
[SerializeField] private int maxMusicChannels = 3; // 最大音乐通道数
// 分离的音量控制
[Header("Volume Control")]
[SerializeField] private float masterVolume = 1f; // 主音量
[SerializeField] private float musicVolume = 1f; // 背景音乐音量
[SerializeField] private float sfxVolume = 1f; // 音效音量
private Dictionary<string, AudioClip> audioClips = new Dictionary<string, AudioClip>(); // 普通音频资源字典
private Dictionary<string, AudioClip> globalAudioClips = new Dictionary<string, AudioClip>(); // 全局音频资源字典
private List<AudioSource> sfxSourcePool = new List<AudioSource>(); // 音效对象池
private Dictionary<string, AudioSource> musicChannels = new Dictionary<string, AudioSource>(); // 音乐通道字典
// 属性访问器
public float MasterVolume => masterVolume;
public float MusicVolume => musicVolume;
public float SFXVolume => sfxVolume;
private void Awake()
{
Initialize();
if (Instance != this)
{
Destroy(gameObject);
}
DontDestroyOnLoad(gameObject);
}
/// <summary>
/// 初始化音频管理器
/// </summary>
private void Initialize()
{
LoadAudioResources(""); // 加载普通音频资源
LoadGlobalAudioResources(""); // 加载全局音频资源
InitializeSFXPool(); // 初始化音效对象池
CreateMusicChannel("Main"); // 创建默认音乐通道
}
/// <summary>
/// 加载音频资源
/// </summary>
/// <param name="subPath">子目录路径</param>
/// <param name="isGlobal">是否为全局资源</param>
private void LoadAudioResources(string subPath = "", bool isGlobal = false)
{
string path = "AudioClips/" + subPath;
if (isGlobal) path = "GlobalAudioClips/" + subPath;
AudioClip[] clips = Resources.LoadAll<AudioClip>(path);
var targetDict = isGlobal ? globalAudioClips : audioClips;
foreach (AudioClip clip in clips)
{
string clipName = clip.name;
if (!targetDict.ContainsKey(clipName))
{
targetDict.Add(clipName, clip);
}
else
{
Debug.LogWarning($"发现重复的音频文件名: {clipName} ({(isGlobal ? "全局" : "普通")})");
}
}
}
/// <summary>
/// 加载全局音频资源
/// </summary>
private void LoadGlobalAudioResources(string subPath = "")
{
LoadAudioResources(subPath, true);
}
/// <summary>
/// 初始化音效对象池
/// </summary>
private void InitializeSFXPool()
{
for (int i = 0; i < initialPoolSize; i++)
{
CreateNewSFXSource();
}
}
#region 音效系统
/// <summary>
/// 创建新的音效源
/// </summary>
private AudioSource CreateNewSFXSource()
{
AudioSource newSource = gameObject.AddComponent<AudioSource>();
newSource.playOnAwake = false;
newSource.volume = GetFinalSFXVolume();
sfxSourcePool.Add(newSource);
return newSource;
}
/// <summary>
/// 获取可用音效源
/// </summary>
private AudioSource GetAvailableSFXSource()
{
foreach (AudioSource source in sfxSourcePool)
{
if (!source.isPlaying) return source;
}
return CreateNewSFXSource();
}
/// <summary>
/// 计算最终音效音量
/// </summary>
private float GetFinalSFXVolume(float volumeScale = 1f)
{
return masterVolume * sfxVolume * volumeScale;
}
/// <summary>
/// 计算最终音乐音量
/// </summary>
private float GetFinalMusicVolume(float volumeScale = 1f)
{
return masterVolume * musicVolume * volumeScale;
}
/// <summary>
/// 播放音效
/// </summary>
/// <param name="clipName">音频名称</param>
/// <param name="volumeScale">音量缩放</param>
/// <param name="isGlobal">是否为全局音频</param>
public void PlaySFX(string clipName, float volumeScale = 1f, bool isGlobal = false)
{
var targetDict = isGlobal ? globalAudioClips : audioClips;
if (targetDict.TryGetValue(clipName, out AudioClip clip))
{
AudioSource source = GetAvailableSFXSource();
source.clip = clip;
source.volume = GetFinalSFXVolume(volumeScale);
source.Play();
}
else
{
Debug.LogError($"{(isGlobal ? "全局" : "")}音效文件未找到: {clipName}");
}
}
/// <summary>
/// 播放全局音效(便捷方法)
/// </summary>
public void PlayGlobalSFX(string clipName, float volumeScale = 1f)
{
PlaySFX(clipName, volumeScale, true);
}
/// <summary>
/// 停止指定音效
/// </summary>
public void StopSFX(string clipName)
{
foreach (AudioSource source in sfxSourcePool)
{
if (source.isPlaying && source.clip != null && source.clip.name == clipName)
{
source.Stop();
}
}
}
/// <summary>
/// 停止所有音效
/// </summary>
public void StopAllSFX()
{
foreach (AudioSource source in sfxSourcePool)
{
source.Stop();
}
}
#endregion
#region 音乐系统
/// <summary>
/// 创建音乐通道
/// </summary>
private bool CreateMusicChannel(string channelName)
{
if (musicChannels.ContainsKey(channelName))
{
Debug.LogWarning($"音乐通道已存在: {channelName}");
return true;
}
if (musicChannels.Count >= maxMusicChannels)
{
Debug.LogError($"已达到最大音乐通道数:{maxMusicChannels}");
return false;
}
AudioSource newSource = gameObject.AddComponent<AudioSource>();
newSource.loop = true;
newSource.volume = GetFinalMusicVolume();
musicChannels.Add(channelName, newSource);
return true;
}
/// <summary>
/// 播放音乐(自动创建通道)
/// </summary>
public void PlayMusic(string channelName, string clipName, float volumeScale = 1f, bool isGlobal = false)
{
// 确保通道存在
if (!musicChannels.ContainsKey(channelName) && !CreateMusicChannel(channelName))
{
return; // 通道创建失败
}
var targetDict = isGlobal ? globalAudioClips : audioClips;
AudioSource source = musicChannels[channelName];
if (targetDict.TryGetValue(clipName, out AudioClip clip))
{
source.clip = clip;
source.volume = GetFinalMusicVolume(volumeScale);
source.Play();
}
else
{
Debug.LogError($"{(isGlobal ? "全局" : "")}音乐文件未找到:{clipName}");
}
}
/// <summary>
/// 播放全局音乐(便捷方法)
/// </summary>
public void PlayGlobalMusic(string channelName, string clipName, float volumeScale = 1f)
{
PlayMusic(channelName, clipName, volumeScale, true);
}
/// <summary>
/// 停止指定通道的音乐
/// </summary>
public void StopMusic(string channelName)
{
if (musicChannels.TryGetValue(channelName, out AudioSource source))
{
source.Stop();
}
}
/// <summary>
/// 暂停指定通道的音乐
/// </summary>
public void PauseMusic(string channelName)
{
if (musicChannels.TryGetValue(channelName, out AudioSource source))
{
source.Pause();
}
}
/// <summary>
/// 恢复指定通道的音乐
/// </summary>
public void ResumeMusic(string channelName)
{
if (musicChannels.TryGetValue(channelName, out AudioSource source))
{
source.UnPause();
}
}
/// <summary>
/// 设置通道音量
/// </summary>
public void SetChannelVolume(string channelName, float volume)
{
if (musicChannels.TryGetValue(channelName, out AudioSource source))
{
source.volume = Mathf.Clamp01(volume);
}
}
/// <summary>
/// 获取通道音量
/// </summary>
public float GetChannelVolume(string channelName)
{
if (musicChannels.TryGetValue(channelName, out AudioSource source))
{
return source.volume;
}
return 0f;
}
#endregion
#region 音量控制系统
/// <summary>
/// 设置主音量
/// </summary>
public void SetMasterVolume(float volume)
{
masterVolume = Mathf.Clamp01(volume);
UpdateAllVolumes();
}
/// <summary>
/// 设置背景音乐音量
/// </summary>
public void SetMusicVolume(float volume)
{
musicVolume = Mathf.Clamp01(volume);
UpdateMusicVolumes();
}
/// <summary>
/// 设置音效音量
/// </summary>
public void SetSFXVolume(float volume)
{
sfxVolume = Mathf.Clamp01(volume);
UpdateSFXVolumes();
}
/// <summary>
/// 更新所有音量
/// </summary>
private void UpdateAllVolumes()
{
UpdateMusicVolumes();
UpdateSFXVolumes();
}
/// <summary>
/// 更新所有音乐通道音量
/// </summary>
private void UpdateMusicVolumes()
{
foreach (var channel in musicChannels.Values)
{
if (channel.clip != null)
{
channel.volume = GetFinalMusicVolume();
}
}
}
/// <summary>
/// 更新所有音效源音量
/// </summary>
private void UpdateSFXVolumes()
{
foreach (AudioSource source in sfxSourcePool)
{
if (!source.isPlaying)
{
source.volume = GetFinalSFXVolume();
}
}
}
#endregion
#region 全局控制
/// <summary>
/// 停止所有音频
/// </summary>
public void StopAllAudio(bool immediate = true)
{
if (immediate)
{
StopAllSFX();
foreach (var channel in musicChannels.Values)
{
channel.Stop();
}
}
else
{
StartCoroutine(DelayedStopAll());
}
}
private IEnumerator DelayedStopAll()
{
yield return null;
StopAllSFX();
foreach (var channel in musicChannels.Values)
{
channel.Stop();
}
}
/// <summary>
/// 获取所有音乐通道名称
/// </summary>
public List<string> GetMusicChannels()
{
return new List<string>(musicChannels.Keys);
}
/// <summary>
/// 淡入淡出主音量
/// </summary>
public IEnumerator FadeMasterVolume(float targetVolume, float duration, System.Action onComplete = null)
{
float startVolume = masterVolume;
float timer = 0f;
while (timer < duration)
{
masterVolume = Mathf.Lerp(startVolume, targetVolume, timer / duration);
UpdateAllVolumes();
timer += Time.deltaTime;
yield return null;
}
masterVolume = targetVolume;
UpdateAllVolumes();
onComplete?.Invoke();
}
/// <summary>
/// 淡入淡出音乐音量
/// </summary>
public IEnumerator FadeMusicVolume(float targetVolume, float duration, System.Action onComplete = null)
{
float startVolume = musicVolume;
float timer = 0f;
while (timer < duration)
{
musicVolume = Mathf.Lerp(startVolume, targetVolume, timer / duration);
UpdateMusicVolumes();
timer += Time.deltaTime;
yield return null;
}
musicVolume = targetVolume;
UpdateMusicVolumes();
onComplete?.Invoke();
}
/// <summary>
/// 淡入淡出音效音量
/// </summary>
public IEnumerator FadeSFXVolume(float targetVolume, float duration, System.Action onComplete = null)
{
float startVolume = sfxVolume;
float timer = 0f;
while (timer < duration)
{
sfxVolume = Mathf.Lerp(startVolume, targetVolume, timer / duration);
UpdateSFXVolumes();
timer += Time.deltaTime;
yield return null;
}
sfxVolume = targetVolume;
UpdateSFXVolumes();
onComplete?.Invoke();
}
#endregion
#region 扩展方法
/// <summary>
/// 淡入淡出指定通道的音量
/// </summary>
public IEnumerator FadeChannel(string channelName, float targetVolume, float duration)
{
if (!musicChannels.TryGetValue(channelName, out AudioSource source))
yield break;
float startVolume = source.volume;
float timer = 0f;
while (timer < duration)
{
source.volume = Mathf.Lerp(startVolume, targetVolume, timer / duration);
timer += Time.deltaTime;
yield return null;
}
source.volume = targetVolume;
}
#endregion
}
2.具体用法
提前说明一下,音频文件是放在Resources/AudioClips/
目录下,可以将一开始的LoadAudioResources函数公开就能传入深层路径。
1. 基本功能使用
// 播放普通音效
AudioManager.Instance.PlaySFX("ButtonClick");
// 播放全局音效
AudioManager.Instance.PlayGlobalSFX("Notification");
// 播放背景音乐(自动创建Main通道)
AudioManager.Instance.PlayMusic("Main", "BackgroundMusic");
// 播放战斗音乐(自动创建Battle通道)
AudioManager.Instance.PlayMusic("Battle", "BattleTheme", 0.8f);
// 暂停和恢复音乐
AudioManager.Instance.PauseMusic("Main");
AudioManager.Instance.ResumeMusic("Main");
// 停止所有音频
AudioManager.Instance.StopAllAudio();
2. 音量控制
我直接用了一个AudioPanel来控制
还是挺好理解的,控制啥直接新建一个Slider就行了,Slider啥也不用改
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AudioPanel : MonoBehaviour
{
[Header("Volume Sliders")]
public Slider allMusicVolumeSlider; // 总音量滑块
public Slider backgroundMusicVolumeSlider; // 背景音乐音量滑块
public Slider sfxMusicVolumeSlider; // 音效音量滑块
[Header("Volume Display (Optional)")]
public Text allVolumeText; // 总音量显示文本
public Text musicVolumeText; // 音乐音量显示文本
public Text sfxVolumeText; // 音效音量显示文本
private void Start()
{
InitializeSliders();
SetupSliderCallbacks();
}
/// <summary>
/// 初始化滑块值
/// </summary>
private void InitializeSliders()
{
if (AudioManager.Instance != null)
{
// 设置滑块初始值为AudioManager中的当前音量
if (allMusicVolumeSlider != null)
{
allMusicVolumeSlider.value = AudioManager.Instance.MasterVolume;
UpdateVolumeText(allVolumeText, AudioManager.Instance.MasterVolume);
}
if (backgroundMusicVolumeSlider != null)
{
backgroundMusicVolumeSlider.value = AudioManager.Instance.MusicVolume;
UpdateVolumeText(musicVolumeText, AudioManager.Instance.MusicVolume);
}
if (sfxMusicVolumeSlider != null)
{
sfxMusicVolumeSlider.value = AudioManager.Instance.SFXVolume;
UpdateVolumeText(sfxVolumeText, AudioManager.Instance.SFXVolume);
}
}
}
/// <summary>
/// 设置滑块回调函数
/// </summary>
private void SetupSliderCallbacks()
{
// 总音量滑块回调
if (allMusicVolumeSlider != null)
{
allMusicVolumeSlider.onValueChanged.AddListener(SetMasterVolume);
}
// 背景音乐音量滑块回调
if (backgroundMusicVolumeSlider != null)
{
backgroundMusicVolumeSlider.onValueChanged.AddListener(SetMusicVolume);
}
// 音效音量滑块回调
if (sfxMusicVolumeSlider != null)
{
sfxMusicVolumeSlider.onValueChanged.AddListener(SetSFXVolume);
}
}
/// <summary>
/// 设置主音量
/// </summary>
/// <param name="volume">音量值(0-1)</param>
private void SetMasterVolume(float volume)
{
if (AudioManager.Instance != null)
{
AudioManager.Instance.SetMasterVolume(volume);
UpdateVolumeText(allVolumeText, volume);
}
}
/// <summary>
/// 设置背景音乐音量
/// </summary>
/// <param name="volume">音量值(0-1)</param>
private void SetMusicVolume(float volume)
{
if (AudioManager.Instance != null)
{
AudioManager.Instance.SetMusicVolume(volume);
UpdateVolumeText(musicVolumeText, volume);
}
}
/// <summary>
/// 设置音效音量
/// </summary>
/// <param name="volume">音量值(0-1)</param>
private void SetSFXVolume(float volume)
{
if (AudioManager.Instance != null)
{
AudioManager.Instance.SetSFXVolume(volume);
UpdateVolumeText(sfxVolumeText, volume);
}
}
/// <summary>
/// 更新音量显示文本
/// </summary>
/// <param name="textComponent">文本组件</param>
/// <param name="volume">音量值</param>
private void UpdateVolumeText(Text textComponent, float volume)
{
if (textComponent != null)
{
textComponent.text = $"{Mathf.RoundToInt(volume * 100)}%";
}
}
#region 公共方法 - 供外部调用
/// <summary>
/// 重置所有音量到默认值
/// </summary>
public void ResetAllVolumes()
{
SetMasterVolume(1f);
SetMusicVolume(1f);
SetSFXVolume(1f);
// 更新滑块显示
if (allMusicVolumeSlider != null) allMusicVolumeSlider.value = 1f;
if (backgroundMusicVolumeSlider != null) backgroundMusicVolumeSlider.value = 1f;
if (sfxMusicVolumeSlider != null) sfxMusicVolumeSlider.value = 1f;
}
/// <summary>
/// 静音所有音频
/// </summary>
public void MuteAll()
{
SetMasterVolume(0f);
if (allMusicVolumeSlider != null) allMusicVolumeSlider.value = 0f;
}
/// <summary>
/// 静音背景音乐
/// </summary>
public void MuteMusic()
{
SetMusicVolume(0f);
if (backgroundMusicVolumeSlider != null) backgroundMusicVolumeSlider.value = 0f;
}
/// <summary>
/// 静音音效
/// </summary>
public void MuteSFX()
{
SetSFXVolume(0f);
if (sfxMusicVolumeSlider != null) sfxMusicVolumeSlider.value = 0f;
}
/// <summary>
/// 测试音效播放
/// </summary>
public void TestSFX()
{
// 这里可以播放一个测试音效,需要确保有对应的音频文件
if (AudioManager.Instance != null)
{
// 示例:播放一个名为"test"的音效
// AudioManager.Instance.PlaySFX("test");
Debug.Log("测试音效播放 - 请确保有测试音频文件");
}
}
/// <summary>
/// 测试背景音乐播放
/// </summary>
public void TestMusic()
{
// 这里可以播放一个测试背景音乐
if (AudioManager.Instance != null)
{
// 示例:在Main通道播放一个名为"test_music"的音乐
// AudioManager.Instance.PlayMusic("Main", "test_music");
Debug.Log("测试背景音乐播放 - 请确保有测试音频文件");
}
}
#endregion
#region Unity生命周期
private void OnEnable()
{
// 面板激活时刷新滑块值
RefreshSliderValues();
}
/// <summary>
/// 刷新滑块显示值
/// </summary>
private void RefreshSliderValues()
{
if (AudioManager.Instance != null)
{
if (allMusicVolumeSlider != null)
{
allMusicVolumeSlider.value = AudioManager.Instance.MasterVolume;
UpdateVolumeText(allVolumeText, AudioManager.Instance.MasterVolume);
}
if (backgroundMusicVolumeSlider != null)
{
backgroundMusicVolumeSlider.value = AudioManager.Instance.MusicVolume;
UpdateVolumeText(musicVolumeText, AudioManager.Instance.MusicVolume);
}
if (sfxMusicVolumeSlider != null)
{
sfxMusicVolumeSlider.value = AudioManager.Instance.SFXVolume;
UpdateVolumeText(sfxVolumeText, AudioManager.Instance.SFXVolume);
}
}
}
#endregion
}
没啥好多的了,直接用就行了。
下面随便写点吧
普通音频资源放在
Resources/AudioClips/
目录下全局音频资源放在
Resources/GlobalAudioClips/
目录下
主音量控制所有声音
通道音量相对于主音量
最终音量 = 主音量 × 通道音量 × 播放音量缩放
由于使用
DontDestroyOnLoad
,AudioManager 会在场景切换时保留使用
StopAllAudio()
在场景切换时清理不需要的声音