Unity中的AudioManager

发布于:2025-06-02 ⋅ 阅读:(24) ⋅ 点赞:(0)

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() 在场景切换时清理不需要的声音


网站公告

今日签到

点亮在社区的每一天
去签到