提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、Resource文件夹下的音效片段目录
- 二、采用单例模式作为全局访问方式
- 三、音效类型分类
- 四、播放音效的组件池的数据对象
- 五、播放时的参数封装
- 六、音效播放管理器
- 七、测试功能脚本
- 八、测试场景截图
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
一个基于对象池优化思想的音频管理系统,实现了游戏中各种2d和3d不同音效类型播放的需求,包括音效的播放,暂停,继续,停止,播放完成的回调执行等,同时也实现了对音效的开关控制,音量大小控制,以及静音状态的保存。另外采用对象池的优化思想,能够及时对不用的音效组件进行回收利用。
提示:以下是本篇文章正文内容,下面案例可供参考
一、Resource文件夹下的音效片段目录
二、采用单例模式作为全局访问方式
using UnityEngine;/// <summary>
/// 依赖于Unity Mono的单例,其需要和Unity的一些物体做交互。例如对象池模块和音频管理模块。
/// </summary>
public class MonoSingleton<T> : MonoBehaviour where T : Component
{private static T _instance = null;public static T Instance{get{if (_instance == null){_instance = FindObjectOfType<T>();if (_instance == null){GameObject obj = new GameObject(typeof(T).Name, new[] { typeof(T) });DontDestroyOnLoad(obj);_instance = obj.GetComponent<T>();(_instance as IInitable)?.Init();}else{Debug.LogWarning("Instance is already exist!");}}return _instance;}}/// <summary>/// 继承Mono单例的类如果写了Awake方法,需要在Awake方法最开始的地方调用一次base.Awake(),来给_instance赋值/// </summary>public void Awake(){_instance = this as T;DontDestroyOnLoad(this);}
}
三、音效类型分类
/// <summary>
/// 音效类型
/// </summary>
public enum SoundType
{BGM, //背景音乐UI, //背景音乐Effect,//效果音Talk //剧情对话
}
四、播放音效的组件池的数据对象
/// <summary>
/// 音效组件池数据
/// </summary>
[Serializable]
public class ASPoolData
{[SerializeField] private SoundType _type_;[SerializeField] private AudioSource _audioSource_;[SerializeField] private bool _isUseing; //是否在使用中(暂停中属于使用中,因为考虑到还可以继续播放)[SerializeField] private bool _isInPause;//是否在暂停中public SoundType Type_ { get => _type_; set => _type_ = value; }public AudioSource AudioSource_ { get => _audioSource_; set => _audioSource_ = value; }public bool IsUseing { get => _isUseing; set => _isUseing = value; }public bool IsInPause { get => _isInPause; set => _isInPause = value; }public ASPoolData(SoundType _type, AudioSource _AS, bool _isUsed, bool _isInPause){this.Type_ = _type;this.AudioSource_ = _AS;this.IsUseing = _isUsed;this.IsInPause = _isInPause;}
}
五、播放时的参数封装
/// <summary>
/// 播放时的参数封装
/// </summary>
public class PlayParams
{public SoundType type;public string clipName;public bool isLoop;public Action endCallback;//播完的回调//3d音效public bool is3d = false;public Vector3 pos = Vector3.zero;//2d音效public PlayParams(SoundType type, string name, bool isLoop = false, Action cb = null){this.type = type;this.clipName = name;this.isLoop = isLoop;this.endCallback = cb;}//3d音效public PlayParams(SoundType type, string name, bool is3d, Vector3 pos, bool isLoop = false, Action cb = null){this.type = type;this.clipName = name;this.isLoop = isLoop;this.endCallback = cb;this.is3d = is3d;this.pos = pos;}
}
六、音效播放管理器
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Collections;/// <summary>
/// 音效播放管理器
/// BGM不会同时播放多个,不需要使用Audiosource对象池
/// </summary>
public class SoundManager : MonoSingleton<SoundManager>
{/// <summary>/// 音效资源路径/// </summary>private static readonly Dictionary<SoundType, string> soundPathDict = new Dictionary<SoundType, string>();/// <summary>/// 音效播放音量大小/// </summary>private static readonly Dictionary<SoundType, float> soundVolumeDict = new Dictionary<SoundType, float>();/// <summary>/// 音效的静音状态/// </summary>private static readonly Dictionary<SoundType, bool> soundMuteStateDict = new Dictionary<SoundType, bool>();/// <summary>/// 记录声音效片段/// </summary>private static Dictionary<string, AudioClip> soundClipDict = new Dictionary<string, AudioClip>();private AudioSource _BGMAuidoSource; //背景音乐播放组件单独使用(不需要入池)public int poolCount_ = 10; //AudioSource对象池数量public List<ASPoolData> _ASPoolList = new List<ASPoolData>(); //AudioSource对象池private AudioListener audioListener;public AudioListener AudioListener{get{if (audioListener != null)return audioListener;audioListener = this.GetComponent<AudioListener>();if (audioListener != null)return audioListener;audioListener = this.gameObject.AddComponent<AudioListener>();return audioListener;}}private new void Awake(){base.Awake();Init();}/// <summary>/// 初始化/// </summary>public void Init(){soundPathDict.Add(SoundType.BGM, "Sounds/BGM");soundPathDict.Add(SoundType.UI, "Sounds/UI");soundPathDict.Add(SoundType.Effect, "Sounds/Effect");soundPathDict.Add(SoundType.Talk, "Sounds/Talk");soundVolumeDict.Add(SoundType.BGM, 1);soundVolumeDict.Add(SoundType.UI, 1);soundVolumeDict.Add(SoundType.Effect, 1);soundVolumeDict.Add(SoundType.Talk, 1);_BGMAuidoSource = gameObject.AddComponent<AudioSource>();Init_MuteState();Init_VolumeState();}/// <summary>/// 初始化静音状态/// </summary>private void Init_MuteState(){bool _BGMIsMute = false;bool _EffectIsMute = false;bool _UIIsMute = false;bool _TalkIsMute = false;if (PlayerPrefs.HasKey("BGMIsMute")){int v = PlayerPrefs.GetInt("BGMIsMute");_BGMIsMute = v == 1;}if (PlayerPrefs.HasKey("EffectIsMute")){int v = PlayerPrefs.GetInt("EffectIsMute");_EffectIsMute = v == 1;}if (PlayerPrefs.HasKey("UIIsMute")){int v = PlayerPrefs.GetInt("UIIsMute");_UIIsMute = v == 1;}if (PlayerPrefs.HasKey("TalkIsMute")){int v = PlayerPrefs.GetInt("TalkIsMute");_TalkIsMute = v == 1;}soundMuteStateDict[SoundType.BGM] = _BGMIsMute;soundMuteStateDict[SoundType.Effect] = _EffectIsMute;soundMuteStateDict[SoundType.UI] = _UIIsMute;soundMuteStateDict[SoundType.Talk] = _TalkIsMute;if(_BGMAuidoSource != null)_BGMAuidoSource.mute = soundMuteStateDict[SoundType.BGM];foreach (var data in _ASPoolList){if (data.Type_ == SoundType.Effect)data.AudioSource_.mute = soundMuteStateDict[SoundType.Effect];else if(data.Type_ == SoundType.Talk)data.AudioSource_.mute = soundMuteStateDict[SoundType.Talk];else if (data.Type_ == SoundType.UI)data.AudioSource_.mute = soundMuteStateDict[SoundType.UI];}}/// <summary>/// 初始化音效的音量状态/// </summary>private void Init_VolumeState(){float _BGMVolume = 1;float _EffectVolume = 1;float _UIVolume = 1;float _TalkVolume = 1;if (PlayerPrefs.HasKey("BGMVolume"))_BGMVolume = PlayerPrefs.GetFloat("BGMVolume");if (PlayerPrefs.HasKey("EffectVolume"))_EffectVolume = PlayerPrefs.GetInt("EffectVolume");if (PlayerPrefs.HasKey("UIVolume"))_UIVolume = PlayerPrefs.GetInt("UIVolume");if (PlayerPrefs.HasKey("TalkVolume"))_TalkVolume = PlayerPrefs.GetInt("TalkVolume");soundVolumeDict[SoundType.BGM] = _BGMVolume;soundVolumeDict[SoundType.Effect] = _EffectVolume;soundVolumeDict[SoundType.UI] = _UIVolume;soundVolumeDict[SoundType.Talk] = _TalkVolume;if (_BGMAuidoSource != null)_BGMAuidoSource.volume = soundVolumeDict[SoundType.BGM];foreach (var data in _ASPoolList){if (data.Type_ == SoundType.Effect)data.AudioSource_.volume = soundVolumeDict[SoundType.Effect];else if (data.Type_ == SoundType.Talk)data.AudioSource_.volume = soundVolumeDict[SoundType.Talk];else if (data.Type_ == SoundType.UI)data.AudioSource_.volume = soundVolumeDict[SoundType.UI];}}/// <summary>/// 播放音效/// </summary>public void Play(PlayParams playParams){AudioClip clip = GetAudioClip(playParams.type, playParams.clipName);if (clip == null) return;AudioSource audioSource = GetAudioSource(playParams.type, out ASPoolData asDate);if (audioSource == null){Debug.LogError(playParams.type.ToString() + " 类型音频播放组件没找到");return;}audioSource.clip = clip;audioSource.clip.LoadAudioData();audioSource.loop = playParams.isLoop;soundMuteStateDict.TryGetValue(playParams.type, out bool isMute);audioSource.mute = isMute;soundVolumeDict.TryGetValue(playParams.type, out float volume);audioSource.volume = volume;audioSource.Play();if (playParams.type != SoundType.BGM)StartCoroutine(WaitPlayEnd(asDate, playParams.endCallback));if (!playParams.is3d) return;AudioSource.PlayClipAtPoint(clip, playParams.pos);}/// <summary>/// 切换音效静音开关/// </summary>public void SetSwitch(SoundType type){bool isMute = false;int value = 1;if (type == SoundType.BGM){isMute = soundMuteStateDict[SoundType.BGM];soundMuteStateDict[SoundType.BGM] = !isMute;_BGMAuidoSource.mute = soundMuteStateDict[SoundType.BGM];value = soundMuteStateDict[SoundType.BGM] ? 1 : 0;PlayerPrefs.SetInt(type.ToString() + "IsMute", value);return;}isMute = soundMuteStateDict[type];soundMuteStateDict[type] = !isMute;value = soundMuteStateDict[type] ? 1 : 0;foreach (var data in _ASPoolList){if (data.Type_ == type)data.AudioSource_.mute = soundMuteStateDict[type];}PlayerPrefs.SetInt(type.ToString() + "IsMute", value);}/// <summary>/// 更改音效音量/// </summary>public void SetVolume(SoundType type, float volume){if (type == SoundType.BGM){_BGMAuidoSource.volume = volume;PlayerPrefs.SetFloat(type.ToString() + "Volume", volume);return;}else{foreach (var data in _ASPoolList){if (data.Type_ == type)data.AudioSource_.volume = volume;}}soundVolumeDict[type] = volume;PlayerPrefs.SetFloat(type.ToString() + "Volume", volume);}/// <summary>/// 停止播放某个音效(会回收),/// 所有同名的音效都会被停止/// </summary>/// <param name="clipName"></param>public void Stop(SoundType type, string clipName = ""){if (type == SoundType.BGM){if (!_BGMAuidoSource.isPlaying) return;_BGMAuidoSource.Stop();return;}foreach (var data in _ASPoolList){if (data.Type_ != type) break;if (!data.IsUseing) break;if (data.AudioSource_.clip.name != clipName) break;data.AudioSource_.Stop();if (_ASPoolList.Contains(data))_ASPoolList.Find((date) => date == data).IsUseing = false;}}/// <summary>/// 暂停播放某个音效/// 所有同名的音效都会被暂停/// </summary>/// <param name="clipName"></param>public void Pause(SoundType type, string clipName = ""){if (type == SoundType.BGM){if (!_BGMAuidoSource.isPlaying) return;_BGMAuidoSource.Pause();return;}foreach (var data in _ASPoolList){if (data.Type_ != type) break;if (!data.IsUseing) break;if (data.AudioSource_.clip.name != clipName) break;data.IsInPause = true;data.AudioSource_.Pause();}}/// <summary>/// 继续播放某个音效,/// 所有同名的音效都会被继续播放/// </summary>/// <param name="clipName"></param>public void Resume(SoundType type, string clipName = ""){if (type == SoundType.BGM){_BGMAuidoSource.UnPause();return;}foreach (var data in _ASPoolList){if (data.Type_ != type) break;if (!data.IsUseing) break;if (data.AudioSource_.clip.name != clipName) break;data.IsInPause = false;data.AudioSource_.UnPause();}}/// <summary>/// 获取音频文件/// </summary>private AudioClip GetAudioClip(SoundType type, string clipName){if (!soundClipDict.ContainsKey(clipName)){soundPathDict.TryGetValue(type, out string path);if (path == null){Debug.LogError(type.ToString() + " 音频资源目录不存在,请检查方法参数type");return null;}AudioClip ac = Resources.Load(path + "/" + clipName) as AudioClip;if (ac == null){Debug.LogError("Resource中不存在该资源,请检查音频资源---" + path + "/" + clipName);return null;}soundClipDict.Add(clipName, ac);}return soundClipDict[clipName];}/// <summary>/// 获取音频组件/// </summary>/// <returns></returns>private AudioSource GetAudioSource(SoundType type, out ASPoolData poolData){if (type == SoundType.BGM){poolData = null;return _BGMAuidoSource;}poolData = _ASPoolList.Find(data => data.IsUseing == false);if(poolData != null)poolData.IsUseing = true;return poolData != null ? poolData.AudioSource_ : AddAudioSource(type, out poolData);}/// <summary>/// 添加音频组件/// </summary>private AudioSource AddAudioSource(SoundType type, out ASPoolData poolData){AudioSource audioSource = gameObject.AddComponent<AudioSource>();poolData = new ASPoolData(type, audioSource, true, false);_ASPoolList.Add(poolData);return audioSource;}/// <summary>/// 回收音频播放组件对象/// </summary>private void RecyclePool(ASPoolData aSPoolData){if (_ASPoolList.Contains(aSPoolData))_ASPoolList.Find((date) => date == aSPoolData).IsUseing = false;//控制下对象池的大小if (_ASPoolList.Count > poolCount_){_ASPoolList.Remove(aSPoolData);Destroy(aSPoolData.AudioSource_);}}/// <summary>/// 待音效播放完成后 将其移至未使用集合中,/// 同时执行播放完毕的回调/// </summary>private IEnumerator WaitPlayEnd(ASPoolData aSPoolData, Action action){if (aSPoolData == null) yield break;//如果没有在使用中的,或者没有正在播放且没有处于暂停状态的就要进行回收yield return new WaitUntil(() => !aSPoolData.IsUseing || (!aSPoolData.AudioSource_.isPlaying && !aSPoolData.IsInPause));RecyclePool(aSPoolData);if (action != null)action.Invoke();yield break;}
}/// <summary>
/// 音效类型
/// </summary>
public enum SoundType
{BGM, //背景音乐UI, //背景音乐Effect,//效果音Talk //剧情对话
}/// <summary>
/// 音效组件池数据
/// </summary>
[Serializable]
public class ASPoolData
{[SerializeField] private SoundType _type_;[SerializeField] private AudioSource _audioSource_;[SerializeField] private bool _isUseing; //是否在使用中(暂停中属于使用中,因为考虑到还可以继续播放)[SerializeField] private bool _isInPause;//是否在暂停中public SoundType Type_ { get => _type_; set => _type_ = value; }public AudioSource AudioSource_ { get => _audioSource_; set => _audioSource_ = value; }public bool IsUseing { get => _isUseing; set => _isUseing = value; }public bool IsInPause { get => _isInPause; set => _isInPause = value; }public ASPoolData(SoundType _type, AudioSource _AS, bool _isUsed, bool _isInPause){this.Type_ = _type;this.AudioSource_ = _AS;this.IsUseing = _isUsed;this.IsInPause = _isInPause;}
}/// <summary>
/// 播放时的参数封装
/// </summary>
public class PlayParams
{public SoundType type;public string clipName;public bool isLoop;public Action endCallback;//播完的回调//3d音效public bool is3d = false;public Vector3 pos = Vector3.zero;//2d音效public PlayParams(SoundType type, string name, bool isLoop = false, Action cb = null){this.type = type;this.clipName = name;this.isLoop = isLoop;this.endCallback = cb;}//3d音效public PlayParams(SoundType type, string name, bool is3d, Vector3 pos, bool isLoop = false, Action cb = null){this.type = type;this.clipName = name;this.isLoop = isLoop;this.endCallback = cb;this.is3d = is3d;this.pos = pos;}
}
七、测试功能脚本
using UnityEngine;
using UnityEngine.UI;public class SoundMgrTest : MonoBehaviour
{public Slider volumeSlider_BGM;public Slider volumeSlider_UI;public Slider volumeSlider_Talk;public Slider volumeSlider_Effect;//测试音效播放,暂停,继续,停止public void OnPlayBGMBtnClick(){SoundManager.Instance.Play(new PlayParams(SoundType.BGM, "BGM01"));}public void OnPlayUIBtnClick(){SoundManager.Instance.Play(new PlayParams(SoundType.UI, "Bell_High"));}public void OnPlayTalkBtnClick(){SoundManager.Instance.Play(new PlayParams(SoundType.Talk, "Buzz"));}public void OnPlayEffectBtnClick(){SoundManager.Instance.Play(new PlayParams(SoundType.Effect, "Bell_Low"));}//测试音量调节public void OnBGMVolumeChange(){SoundManager.Instance.SetVolume(SoundType.BGM, volumeSlider_BGM.value);}public void OnUIVolumeChange(){SoundManager.Instance.SetVolume(SoundType.UI, volumeSlider_UI.value);}public void OnTalkVolumeChange(){SoundManager.Instance.SetVolume(SoundType.Talk, volumeSlider_Talk.value);}public void OnEffectVolumeChange(){SoundManager.Instance.SetVolume(SoundType.Effect, volumeSlider_Effect.value);}//测试音效开关public void OnBGMSwitchClick(){SoundManager.Instance.SetSwitch(SoundType.BGM);}public void OnUISwitchClick(){SoundManager.Instance.SetSwitch(SoundType.UI);}public void OnTalkSwitchClick(){SoundManager.Instance.SetSwitch(SoundType.Talk);}public void OnEffectSwitchClick(){SoundManager.Instance.SetSwitch(SoundType.Effect);}
八、测试场景截图
总结
本文介绍了一个基于对象池优化思想的音频管理系统,采用对象池的优化思想,能够及时对不用的音效组件进行回收利用。