Unity3d--基于对象池优化思想的音频管理系统

news/2024/11/16 21:50:13/

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、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);}

八、测试场景截图

在这里插入图片描述

总结

本文介绍了一个基于对象池优化思想的音频管理系统,采用对象池的优化思想,能够及时对不用的音效组件进行回收利用。


http://www.ppmy.cn/news/578585.html

相关文章

游戏引擎基础(六)(声音系统,音频APIs)

第6部分: 声音系统&#xff0c;音频APIs声音系统  由于人们玩的游戏在种类和技术上的进步&#xff0c;声音和音乐近几年来在游戏中正逐渐变得重要起来&#xff08;声音是一个实际游戏的可玩特点&#xff0c;比如在Thief和其它同类游戏中的听觉提示&#xff09;。现在四声道环…

空间音频 2

13&#xff5c;如何利用HRTF实现听音辨位&#xff1f; 之前介绍了空间音频的基本概念&#xff0c;以及空间音频是如何采集和播放的。已经基本掌握了空间音频的基本原理。其实在游戏、社交、影视等场景中&#xff0c;空间音频被广泛地应用于构建虚拟的空间环境。 在空间音频的…

house的项目2天总结

uni-house-day01 项目功能点 首页 轮播图 ui-app的轮播组件ui框架的轮播组件【更简单】 图标&#xff08;ui框架布局&#xff09;猜你喜欢 触底加载 【重点】 城市选择页面 【重点】 渲染这个页面&#xff0c;你需要什么数据。把后端的数据转换成自己的数据【前端开发必备的…

Flink基础概念及常识

1.flink入门 官方定义&#xff1a;Apache Flink是一个框架和分布式处理引擎&#xff0c;用于在无边界和有边界数据流上进行有状态的计算&#xff0c;Flink能在所有常见集群环境中运行&#xff0c;并能以内存速度和任意规模进行计算。 简言之&#xff0c;Flink是一个分布式的计…

SpringSecurity3.2.5自定义角色及权限的教程

最近阴差阳错的搞上了SpringSecurity3&#xff0c;因为是自己做的小系统&#xff0c;中间遇到了很多坑&#xff0c;基本每个坑都踩过了&#xff0c;网上也查了不少资料&#xff0c;发现有不少错误的&#xff0c;更是让我绕了一圈又一圈&#xff0c;现在把一些基本的东西总结一下…

SAP用户权限相关操作

SE93 查询所有TCODE 或者 table: tstc SE16 display SUIM 查询用户信息的报表们 技术流 SAP常用的TCODE---BASIS 事务码 描述 ( 中英文 ) SBIT Menu 菜单 SBTA Test background processing 后台处理测试 SBTU Background processing for user 对用户的后台处理 SM36 Define Bac…

Ant Design of React从入门到开发教程

Ant Design Pro 是一个企业级中后台前端/设计解决方案。 目录: 一:开发前的准备 二:创建页面 三:创建组件并引用 四:封装网络请求和网络请求走向 五:登录流程以及路由权限设置 六:父组件和子组件相互传值和接收 七:for 循环渲染组件 Ant Design Pro 全家桶技术…

Linux命令十---Linux权限管理(读写执行权限详解)---chmod--chgrp--权限详解及权限修改---umask缺省权限

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载!! 欢迎访问:https://blog.csdn.net/qq_21439395/article/details/82119731 交流QQ: 824203453 权限管理 三种基本权限 r 读权限&#xff08;read&#xff09; w 写权限&#xff08;write&#xf…