• 自定义Unity组件——AudioManager(音频管理器)


    需求描述

            在游戏开发中,音频资源是不可或缺的,通常情况下音频资源随机分布,各个音频的操作和管理都是各自负责,同时对于音频的很多操作逻辑都是大同小异的,这就造成了许多冗余代码的堆叠,除此之外在获取各类音频资源的时候也会造成不必要的开销。所以解决资源分散的问题最直接的方式就是集中管理和分配,通过统一的渠道和特有标识即可获取或操作对应的音频资源。所以本篇文章将围绕这个方案进行尝试。

    功能描述

            在Unity中我们导入的音频资源都会转换为AudioClip,音频的设置和管理则由AudioSource负责,AudioListener负责监听音频。我们可以在此基础上去封装,从而打造一个音频管理器。

            音频管理器负责管理音频信息以及操作音频,比如音频信息的增加和删除,音频的播放和暂停等;

            音频信息可以使用一个单独的实体类来记录,用于记录AudioSource组件中的信息,之所以单独用一个实体类来记录音频信息而不直接采用AudioSource,主要是可以通过业务需求去动态调整所要记录的音频信息,我们的实际开发中并非需要AudioSource中所有的信息,有时候仅仅需要其中的一部分,同时音频信息可能涉及存储,直接采用AudioSource可能无法与已经开发好的存储系统相互兼容,而实体类可以为其添加接口或继承来兼容存储系统;

            AudioSource组件的管理可以通过一个组件池进行管理,我们知道AudioSource也是等同于一个音频实体类,但是其同时也是一种组件,组件同样作为一种资源,通常情况下相比简单的实体类而言会带来更大的开销,例如一个场景中有十个游戏对象有播放音频的需求,那么按照传统情况就需要每个游戏对象挂载一个AudioSource组件,但是实际运行中十个游戏对象并不一定需要同时播放音频,它们或许都存在各自触发音频播放的条件,我们只需要在游戏对象需要播放音频时为其分配一个AudioSource组件即可,组件池则负责维护AudioSource组件的生产、获取和归还,进而减少资源开销。

            本质上,AudioSource组件承担的工作就是记录音频信息和操作音频,我们现在将记录工作分担给音频信息实体类,而操作音频的工作则分担给音频管理器,例如音频管理器的播放依旧是调用AudioSource的播放方法,在播放之前由音频管理器去获取AudioSource组件并且为之配置音频信息,其它的音频操作逻辑同理。

    代码展示(C#)

    AudioManager.cs

    1. using System.Linq;
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. namespace Tools.AudioManagerAPI
    6. {
    7. ///
    8. /// 音频管理器
    9. ///
    10. [DisallowMultipleComponent]
    11. public class AudioManager : MonoBehaviour
    12. {
    13. [Header("必要属性")]
    14. [Tooltip("音频总音量,默认为1(值属于[0,1]),该值将影响所有音频的音量,例如该值为0则所有音频音量变为原有音量的0%,若为1则所有音频音量保持不变,即该值将基于所有音频的当前音量进行影响,而不是直接统一所有音频的音量为该值")]
    15. [Range(0, 1), SerializeField] private float TotalVolume = 1;
    16. ///
    17. /// 音频总音量,默认为1(值属于[0,1])
    18. /// 声明:该值将影响所有音频的音量,例如该值为0则所有音频音量变为原有音量的0%,若为1则所有音频音量保持不变,即该值将基于所有音频的当前音量进行影响,而不是直接统一所有音频的音量为该值
    19. ///
    20. public float mTotalVolume
    21. {
    22. get { return TotalVolume; }
    23. set
    24. {
    25. if (value >= 0 && value <= 1 && TotalVolume != value)
    26. {
    27. TotalVolume = value;
    28. mTotalVolumeChangedEvents?.Invoke(value);
    29. }
    30. }
    31. }
    32. ///
    33. /// 是否启用音频信息覆盖,默认为true
    34. ///
    35. public bool mIsOverWrite { get => isOverWrite; set => isOverWrite = value; }
    36. ///
    37. /// 音频管理器中所存储的音频数量
    38. ///
    39. public int mCount { get => audioInfos.Count; }
    40. ///
    41. /// 总音量更改事件回调
    42. ///
    43. public event Action<float> mTotalVolumeChangedEvents;
    44. ///
    45. /// 音频信息名称合集
    46. ///
    47. public string[] mAudioInfoNames { get => audioInfos.Keys.ToArray(); }
    48. private Dictionary<string, AudioInfo> audioInfos;//音频信息集合
    49. private bool isInit;//是否完成了初始化
    50. private bool isOverWrite;//是否启用音频信息覆盖
    51. private static AudioSourcePool audioSourcePool = AudioSourcePool.GetInstance();//AudioSource组件池
    52. ///
    53. /// 播放指定名称的音频
    54. /// p_audioName:音频名称
    55. ///
    56. public void Play(string p_audioName)
    57. {
    58. if (isInit)
    59. {
    60. if (audioInfos.ContainsKey(p_audioName))
    61. {
    62. AudioInfo ai = audioInfos[p_audioName];
    63. AudioSource v_audioSource = audioSourcePool.Get(ai);
    64. ai.mAudioSource = v_audioSource;
    65. ai.Play();
    66. }
    67. }
    68. }
    69. ///
    70. /// 播放指定名称的音频并开启立体声过渡
    71. /// p_audioName:音频名称
    72. /// 声明:该方法要求已启用立体声过渡且已设置好立体声过渡的相关属性
    73. ///
    74. public void PlayWithStereoTransition(string p_audioName)
    75. {
    76. if (isInit)
    77. {
    78. if (audioInfos.ContainsKey(p_audioName))
    79. {
    80. AudioInfo ai = audioInfos[p_audioName];
    81. AudioSource v_audioSource = audioSourcePool.Get(ai);
    82. ai.mAudioSource = v_audioSource;
    83. StartCoroutine(ai.mStereoPanTransitionCoroutine);
    84. ai.Play();
    85. }
    86. }
    87. }
    88. ///
    89. /// 播放指定名称的音频并开启立体声过渡
    90. /// p_audioName:音频名称
    91. /// p_stereoTransitionValues:立体声过渡值集合
    92. /// p_stereoTimeSpan:立体声过渡每帧时间间隔
    93. ///
    94. public void PlayWithStereoTransition(string p_audioName, float[] p_stereoTransitionValues, float p_stereoTimeSpan)
    95. {
    96. if (isInit)
    97. {
    98. if (audioInfos.ContainsKey(p_audioName))
    99. {
    100. AudioInfo ai = audioInfos[p_audioName];
    101. AudioSource v_audioSource = audioSourcePool.Get(ai);
    102. ai.mAudioSource = v_audioSource;
    103. ai.mStereoTransition = true;
    104. ai.mStereoTransitionValues = p_stereoTransitionValues;
    105. ai.mStereoTransitionTimeSpan = p_stereoTimeSpan;
    106. StartCoroutine(ai.mStereoPanTransitionCoroutine);
    107. ai.Play();
    108. }
    109. }
    110. }
    111. ///
    112. /// 暂停播放指定名称的音频
    113. /// p_audioName:音频名称
    114. ///
    115. public void Pause(string p_audioName)
    116. {
    117. if (isInit)
    118. {
    119. if (audioInfos.ContainsKey(p_audioName))
    120. {
    121. AudioInfo ai = audioInfos[p_audioName];
    122. StopCoroutine(ai.mStereoPanTransitionCoroutine);
    123. ai.Pause();
    124. audioSourcePool.Return(ai.mAudioSource);
    125. }
    126. }
    127. }
    128. ///
    129. /// 添加音频信息
    130. /// p_audioInfo:音频信息
    131. /// 声明1:若启用了音频信息覆盖,当存在相同名称的音频时,新的音频信息将覆盖旧的音频信息
    132. /// 声明2:默认启用了音频信息覆盖,可通过mIsOverWrite属性设置禁用
    133. ///
    134. public void AddAudioInfo(AudioInfo p_audioInfo)
    135. {
    136. if (isInit) DoAddAudioInfo(p_audioInfo);
    137. }
    138. ///
    139. /// 删除音频信息
    140. /// p_audioName:音频名称
    141. /// 返回值:若删除成功则返回true,否则返回false
    142. ///
    143. public bool DeleteAudioInfo(string p_audioName)
    144. {
    145. if (isInit) return DoDeleteAudioInfo(p_audioName);
    146. return false;
    147. }
    148. private void Awake()
    149. {
    150. isInit = false;
    151. if (InitParameters()) isInit = true;
    152. }
    153. //添加音频信息的执行逻辑
    154. private void DoAddAudioInfo(AudioInfo p_audioInfo)
    155. {
    156. if (p_audioInfo != null && !String.IsNullOrEmpty(p_audioInfo.mAudioName))
    157. {
    158. string v_audioName = p_audioInfo.mAudioName;
    159. p_audioInfo.BindAudioManager(this);
    160. if (isOverWrite) audioInfos[v_audioName] = p_audioInfo;
    161. else if (!audioInfos.ContainsKey(v_audioName)) audioInfos.Add(v_audioName, p_audioInfo);
    162. }
    163. }
    164. //删除音频信息的执行逻辑
    165. private bool DoDeleteAudioInfo(string p_audioName)
    166. {
    167. if (!String.IsNullOrEmpty(p_audioName) && audioInfos.ContainsKey(p_audioName))
    168. {
    169. audioInfos[p_audioName].BindAudioManager(null);
    170. return audioInfos.Remove(p_audioName);
    171. }
    172. return false;
    173. }
    174. //对音频管理器相关参数进行初始化
    175. private bool InitParameters()
    176. {
    177. audioInfos = new Dictionary<string, AudioInfo>();
    178. isOverWrite = true;
    179. audioSourcePool.BindAudioManager(this);
    180. #if UNITY_EDITOR
    181. foreach (AudioInfo ai in AudioInfos)
    182. DoAddAudioInfo(ai);
    183. #endif
    184. return true;
    185. }
    186. #if UNITY_EDITOR
    187. ///
    188. /// 在当前Inspector面板中的AudioInfos中的元素数量
    189. ///
    190. public int mAudioInfoCount { get => AudioInfos?.Length > 0 ? AudioInfos.Length : 0; }
    191. [SerializeField] private AudioInfo[] AudioInfos;//存储Inspector面板中
    192. ///
    193. /// 在当前Inspector面板中的AudioInfos中添加一个元素
    194. ///
    195. public void Add()
    196. {
    197. AudioInfo v_audioInfo = new AudioInfo();
    198. if (AudioInfos?.Length > 0)
    199. {
    200. AudioInfo[] v_audioInfos = new AudioInfo[AudioInfos.Length + 1];
    201. AudioInfos.CopyTo(v_audioInfos, 0);
    202. v_audioInfos[v_audioInfos.Length - 1] = v_audioInfo;
    203. AudioInfos = new AudioInfo[v_audioInfos.Length];
    204. v_audioInfos.CopyTo(AudioInfos, 0);
    205. }
    206. else AudioInfos = new AudioInfo[] { v_audioInfo };
    207. v_audioInfo.ValidateCheck();
    208. }
    209. ///
    210. /// 在当前Inspector面板中的AudioInfos中删除一个元素
    211. ///
    212. public void Delete(int p_index)
    213. {
    214. if (AudioInfos?.Length == 1) AudioInfos = Array.Empty();
    215. else if (AudioInfos?.Length > 1)
    216. {
    217. AudioInfo[] v_audioInfos = new AudioInfo[AudioInfos.Length - 1];
    218. int v_index = 0;
    219. for (int i = 0; i < AudioInfos.Length; i++)
    220. {
    221. if (i != p_index) v_audioInfos[v_index++] = AudioInfos[i];
    222. }
    223. AudioInfos = new AudioInfo[v_audioInfos.Length];
    224. v_audioInfos.CopyTo(AudioInfos, 0);
    225. }
    226. }
    227. private void OnValidate()
    228. {
    229. foreach (AudioInfo audioInfo in AudioInfos)
    230. {
    231. audioInfo?.ValidateCheck();
    232. }
    233. }
    234. #endif
    235. }
    236. }

    AudioInfo.cs

    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Linq;
    4. using System;
    5. namespace Tools.AudioManagerAPI
    6. {
    7. ///
    8. /// 音频信息
    9. ///
    10. [System.Serializable]
    11. public class AudioInfo
    12. {
    13. [Header("必要组件")]
    14. [Tooltip("AudioClip组件"), SerializeField] private AudioClip TheAudioClip;
    15. [Header("必要属性")]
    16. [Tooltip("音频名称"), SerializeField] private string AudioName;
    17. [Tooltip("音频音量,默认为1(值属于[0,1])"), Range(0, 1), SerializeField] private float Volume = 1;
    18. [Tooltip("音频播放速度,默认为1(值属于[-3,3])"), Range(-3, 3), SerializeField] private float Pitch = 1;
    19. [Tooltip("立体声位置,默认为0(值属于[-1,1]),若为-1则完全为左声道,若为1则完全为右声道"), Range(-1, 1), SerializeField] private float StereoPan = 0;
    20. [Tooltip("音频优先级,默认为128(值属于[0,256])"), Range(0, 256), SerializeField] private int Priority = 128;
    21. [Tooltip("是否在场景启动时进行播放,默认为true"), SerializeField] private bool PlayOnAwake = true;
    22. [Tooltip("是否循环播放,默认为false"), SerializeField] private bool Loop;
    23. [Tooltip("是否忽略总音量影响,默认为false"), SerializeField] private bool IgnoreTotalVolume;
    24. [Header("立体声过渡属性")]
    25. [Tooltip("是否启用立体声过渡,默认为false"), SerializeField] private bool StereoTransition;
    26. [Tooltip("立体声过渡的每帧时间间隔,默认为0.5(值属于[0.1,5])"), Range(.1f, 5), SerializeField] private float StereoTransitionTimeSpan = 0.5f;
    27. [Tooltip("立体声过渡值集合"), SerializeField] private float[] StereoTransitionValues;
    28. ///
    29. /// 音频名称
    30. ///
    31. public string mAudioName { get => AudioName; set => AudioName = value; }
    32. ///
    33. /// 音频音量,默认为1(值属于[0,1])
    34. ///
    35. public float mVolume
    36. {
    37. get { return Volume; }
    38. set { if (value >= 0 && value <= 1) Volume = value; }
    39. }
    40. ///
    41. /// 音频播放速度,默认为1(值属于[-3,3])
    42. ///
    43. public float mPitch
    44. {
    45. get { return Pitch; }
    46. set { if (value >= -3 && value <= 3) Pitch = value; }
    47. }
    48. ///
    49. /// 立体声位置,默认为0(值属于[-1,1]),若为-1则完全为左声道,若为1则完全为右声道
    50. ///
    51. public float mStereoPan
    52. {
    53. get { return StereoPan; }
    54. set { if (value >= -1 && value <= 1) StereoPan = value; }
    55. }
    56. ///
    57. /// 音频优先级,默认为128(值属于[0,256])
    58. ///
    59. public int mPriority
    60. {
    61. get { return Priority; }
    62. set { if (value >= 0 && value <= 256) Priority = value; }
    63. }
    64. ///
    65. /// 是否在场景启动时进行播放,默认为true
    66. ///
    67. public bool mPlayOnAwake { get => PlayOnAwake; set => PlayOnAwake = value; }
    68. ///
    69. /// 是否循环播放,默认为false
    70. ///
    71. public bool mLoop { get => Loop; set => Loop = value; }
    72. ///
    73. /// 是否启用立体声过渡,默认为false
    74. ///
    75. public bool mStereoTransition { get => StereoTransition; set => StereoTransition = value; }
    76. ///
    77. /// 立体声过渡的每帧时间间隔,默认为0.5(值属于[0.1,5])
    78. ///
    79. public float mStereoTransitionTimeSpan
    80. {
    81. get { return StereoTransitionTimeSpan; }
    82. set { if (value >= 0.1f && value <= 5) StereoTransitionTimeSpan = value; }
    83. }
    84. ///
    85. /// 立体声过渡值集合
    86. ///
    87. public float[] mStereoTransitionValues
    88. {
    89. get => StereoTransitionValues;
    90. set => StereoTransitionValues = value;
    91. }
    92. ///
    93. /// AudioSource组件
    94. ///
    95. public AudioSource mAudioSource { get => audioSource; set => audioSource = value; }
    96. ///
    97. /// 立体声过渡协程
    98. ///
    99. public IEnumerator mStereoPanTransitionCoroutine { get => stereoPanTransitionCoroutine; }
    100. ///
    101. /// 是否忽略总音量影响,默认为false
    102. ///
    103. public bool mIgnoreTotalVolume { get => IgnoreTotalVolume; set => IgnoreTotalVolume = value; }
    104. private AudioSource audioSource;//AudioSource组件
    105. private AudioManager audioManager;//音频管理器
    106. private bool isInit;//是否完成初始化
    107. private IEnumerator stereoPanTransitionCoroutine;//立体声过渡协程
    108. private float actualVolume;//实际音量
    109. private Action<float> totalVolumeChangedEvent;//总音量更改事件对象
    110. ///
    111. /// 将指定的AudioSource组件信息记录在新的AudioInfo实例中并返回它
    112. /// p_audioSource:指定的AudioSource组件
    113. /// 返回值:新的AudioInfo实例
    114. ///
    115. public static AudioInfo Record(AudioSource p_audioSource)
    116. {
    117. AudioInfo v_audioInfo = new AudioInfo();
    118. if (p_audioSource != null)
    119. {
    120. v_audioInfo.TheAudioClip = p_audioSource.clip;
    121. v_audioInfo.Volume = p_audioSource.volume;
    122. v_audioInfo.Pitch = p_audioSource.pitch;
    123. v_audioInfo.StereoPan = p_audioSource.panStereo;
    124. v_audioInfo.Priority = p_audioSource.priority;
    125. v_audioInfo.PlayOnAwake = p_audioSource.playOnAwake;
    126. v_audioInfo.Loop = p_audioSource.loop;
    127. v_audioInfo.audioSource = p_audioSource;
    128. }
    129. return v_audioInfo;
    130. }
    131. public AudioInfo()
    132. {
    133. isInit = false;
    134. InitToDefault();
    135. }
    136. ///
    137. /// 播放音频
    138. ///
    139. public void Play()
    140. {
    141. if (audioSource != null && !audioSource.isPlaying)
    142. {
    143. if (!IgnoreTotalVolume)
    144. {
    145. if (actualVolume < 0 || actualVolume > 1) actualVolume = Volume;
    146. audioSource.volume = actualVolume;
    147. }
    148. else actualVolume = -1;
    149. audioSource.Play();
    150. }
    151. }
    152. ///
    153. /// 暂停音频播放
    154. ///
    155. public void Pause()
    156. {
    157. if (audioSource != null && audioSource.isPlaying)
    158. {
    159. audioSource.Pause();
    160. audioSource.volume = Volume;
    161. }
    162. }
    163. ///
    164. /// 绑定音频管理器
    165. /// p_audioManager:音频管理器
    166. /// 声明1:若有需要可通过该方法将当前的AudioInfo与指定的音频管理器进行绑定
    167. /// 声明2:绑定后将自动向指定的音频管理器添加当前的AudioInfo
    168. ///
    169. public void BindAudioManager(AudioManager p_audioManager)
    170. {
    171. audioManager = p_audioManager;
    172. if (audioManager != null)
    173. {
    174. audioManager.mTotalVolumeChangedEvents -= totalVolumeChangedEvent;
    175. audioManager.mTotalVolumeChangedEvents += totalVolumeChangedEvent;
    176. }
    177. }
    178. ///
    179. /// 初始化为默认值
    180. ///
    181. public void InitToDefault()
    182. {
    183. if (!isInit)
    184. {
    185. TheAudioClip = null;
    186. AudioName = "Audio";
    187. Volume = 1;
    188. Pitch = 1;
    189. StereoPan = 0;
    190. StereoTransitionValues = null;
    191. StereoTransitionTimeSpan = 0.5f;
    192. Priority = 128;
    193. StereoTransition = false;
    194. PlayOnAwake = true;
    195. Loop = false;
    196. audioSource = null;
    197. stereoPanTransitionCoroutine = StereoPanTransition();
    198. totalVolumeChangedEvent = (val) => TotalVolumeChangedEvent(val);
    199. actualVolume = -1;
    200. }
    201. }
    202. ///
    203. /// 将当前AudioInfo实例中的信息配置给指定的AudioSource组件
    204. /// p_audioSource:指定的AudioSource组件
    205. ///
    206. public void ShareTo(AudioSource p_audioSource)
    207. {
    208. if (p_audioSource != null)
    209. {
    210. p_audioSource.clip = TheAudioClip;
    211. p_audioSource.volume = Volume;
    212. p_audioSource.pitch = Pitch;
    213. p_audioSource.panStereo = StereoPan;
    214. p_audioSource.priority = Priority;
    215. p_audioSource.playOnAwake = PlayOnAwake;
    216. p_audioSource.loop = Loop;
    217. }
    218. }
    219. ///
    220. /// 将指定的AudioSource组件信息存储在当前AudioInfo实例中
    221. /// p_audioSource:指定的AudioSource组件
    222. ///
    223. public void SelfRecord(AudioSource p_audioSource)
    224. {
    225. if (p_audioSource != null)
    226. {
    227. TheAudioClip = p_audioSource.clip;
    228. Volume = p_audioSource.volume;
    229. Pitch = p_audioSource.pitch;
    230. StereoPan = p_audioSource.panStereo;
    231. Priority = p_audioSource.priority;
    232. PlayOnAwake = p_audioSource.playOnAwake;
    233. Loop = p_audioSource.loop;
    234. audioSource = p_audioSource;
    235. }
    236. }
    237. // 总音量更改事件
    238. // p_totalVolume:总音量
    239. // 若不忽略总音量影响,通过调用该事件将基于总音量和当前音量换算实际音量数值
    240. // 当TotleVolume为0时,实际音量为0;
    241. // 当TotleVolume为1或不属于[0,1)时,实际音量为Volume;
    242. // 当TotleVolume属于(0,1)时,实际音量为Volume * TotalVolume
    243. private void TotalVolumeChangedEvent(float p_totalVolume)
    244. {
    245. if (!IgnoreTotalVolume)
    246. {
    247. if (p_totalVolume == 0) actualVolume = 0;
    248. else if (p_totalVolume > 0 && p_totalVolume < 1) actualVolume = Volume * p_totalVolume;
    249. else actualVolume = Volume;
    250. //运行时修改AudioSource音量
    251. if (audioSource != null) audioSource.volume = actualVolume;
    252. }
    253. else actualVolume = -1;
    254. }
    255. //立体声过渡协程
    256. private IEnumerator StereoPanTransition()
    257. {
    258. int currentIndex = 0;
    259. while (true)
    260. {
    261. if (audioSource == null || !StereoTransition || StereoTransitionValues == null || StereoTransitionValues.Length == 0)
    262. yield break;
    263. audioSource.panStereo = StereoTransitionValues[currentIndex];
    264. yield return new WaitForSeconds(StereoTransitionTimeSpan);
    265. currentIndex = (currentIndex + 1) % StereoTransitionValues.Length;
    266. if (currentIndex == 0) StereoTransitionValues = StereoTransitionValues.Reverse().ToArray<float>();
    267. }
    268. }
    269. #if UNITY_EDITOR
    270. [NonSerialized] private bool isAudioClipLog;
    271. private bool isAudioNameLog;
    272. ///
    273. /// Inspector面板的数据更改检测
    274. ///
    275. public void ValidateCheck()
    276. {
    277. AudioClipCheck();
    278. AudioNameCheck();
    279. }
    280. //AudioClip检测
    281. private void AudioClipCheck()
    282. {
    283. if (TheAudioClip == null)
    284. {
    285. if (!isAudioClipLog)
    286. {
    287. Debug.LogWarning("Component: TheAudioClip is null.");
    288. isAudioClipLog = true;
    289. }
    290. }
    291. else isAudioClipLog = false;
    292. }
    293. //AudioName检测
    294. private void AudioNameCheck()
    295. {
    296. if (String.IsNullOrEmpty(AudioName))
    297. {
    298. if (!isAudioNameLog)
    299. {
    300. Debug.LogWarning("Property: AudioName is empty.");
    301. isAudioNameLog = true;
    302. }
    303. }
    304. else isAudioNameLog = false;
    305. }
    306. #endif
    307. }
    308. }

    AudioSourcePool.cs 

    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. namespace Tools.AudioManagerAPI
    4. {
    5. ///
    6. /// AudioSource组件池
    7. ///
    8. public class AudioSourcePool
    9. {
    10. ///
    11. /// 空闲的AudioSource数量
    12. ///
    13. public int mFreeCount { get => audioSources.Count; }
    14. private Stack audioSources;//AudioSource组件集合
    15. private AudioManager audioManager;//AudioManager组件
    16. private AudioInfo defaultAudioInfo;//默认的AudioInfo
    17. ///
    18. /// 获取实例(单例模式)
    19. ///
    20. public static AudioSourcePool GetInstance()
    21. {
    22. return Handler.instance;
    23. }
    24. ///
    25. /// 绑定音频管理器
    26. /// p_audioManager:音频管理器
    27. ///
    28. public void BindAudioManager(AudioManager p_audioManager)
    29. {
    30. if (p_audioManager != null) audioManager = p_audioManager;
    31. }
    32. ///
    33. /// 获取AudioSource组件
    34. /// 返回值:AudioSource组件
    35. ///
    36. public AudioSource Get()
    37. {
    38. return DoGet();
    39. }
    40. ///
    41. /// 获取AudioSource组件并按照指定的AudioInfo为之配置属性
    42. /// p_audioInfo:指定的AudioInfo
    43. /// 返回值:AudioSource组件
    44. ///
    45. public AudioSource Get(AudioInfo p_audioInfo)
    46. {
    47. AudioSource v_audioSource = DoGet();
    48. p_audioInfo?.ShareTo(v_audioSource);
    49. return v_audioSource;
    50. }
    51. ///
    52. /// 归还指定的AudioSource组件
    53. /// p_audioSource:指定的AudioSource组件
    54. ///
    55. public void Return(AudioSource p_audioSource)
    56. {
    57. if (p_audioSource != null)
    58. {
    59. CleanAudioSource(p_audioSource);
    60. audioSources.Push(p_audioSource);
    61. }
    62. }
    63. class Handler
    64. {
    65. public static AudioSourcePool instance = new AudioSourcePool();
    66. }
    67. private AudioSourcePool()
    68. {
    69. audioSources = new Stack();
    70. defaultAudioInfo = new AudioInfo();
    71. }
    72. //获取AudioSource组件的执行逻辑
    73. private AudioSource DoGet()
    74. {
    75. if (audioManager == null) return null;
    76. AudioSource v_audioSource = null;
    77. while (v_audioSource == null)
    78. {
    79. if (audioSources.Count == 0) GenerateAudioSource();
    80. v_audioSource = audioSources.Pop();
    81. }
    82. return v_audioSource;
    83. }
    84. //生成AudioSource组件
    85. private void GenerateAudioSource()
    86. {
    87. if (audioManager?.gameObject != null)
    88. {
    89. AudioSource v_audioSource = audioManager.gameObject.AddComponent();
    90. audioSources.Push(v_audioSource);
    91. }
    92. }
    93. //清洗AudioSource组件
    94. private void CleanAudioSource(AudioSource p_audioSource)
    95. {
    96. defaultAudioInfo.ShareTo(p_audioSource);
    97. }
    98. }
    99. }

    NumberRange.cs

    1. using System.Collections.Generic;
    2. using System.Linq;
    3. namespace Tools.AudioManagerAPI
    4. {
    5. ///
    6. /// 数值范围数组工具类
    7. ///
    8. public static class NumberRange
    9. {
    10. ///
    11. /// 获取指定范围内指定步长的Float数值数组
    12. /// p_start:起始值
    13. /// p_end:终点值
    14. /// p_step:步长值
    15. /// [ContainsEnd]:是否包括终点值,默认为false
    16. /// 返回值:Float[]
    17. ///
    18. public static float[] FloatRange(float p_start, float p_end, float p_step, bool ContainsEnd = false)
    19. {
    20. if (!ContainsEnd) return DoFloatRange(p_start, p_end, p_step).ToArray();
    21. else
    22. {
    23. List<float> result = DoFloatRange(p_start, p_end, p_step).ToList();
    24. result.Add(p_end);
    25. return result.ToArray();
    26. }
    27. }
    28. //获取指定范围内指定步长的Float数值数组的执行逻辑
    29. static IEnumerable<float> DoFloatRange(float p_start, float p_end, float p_step)
    30. {
    31. for (float i = p_start; i <= p_end; i += p_step)
    32. {
    33. yield return i;
    34. }
    35. }
    36. }
    37. }

    界面展示

    演示效果

    自定义Unity组件AudioManager

    资源下载

    GitHub_AudioManager    百度网盘

    如果这篇文章对你有帮助,请给作者点个赞吧! 

  • 相关阅读:
    Java抽象类
    9.复杂的例子:模块的使用和自定义模块
    计算机毕业设计Python+django的高校学生信息管理系统(源码+系统+mysql数据库+Lw文档)
    实战回忆录从webshell开始突破边界
    MySQL高级_索引的数据结构
    actuator--基础--6.2--端点解析--metrics端点
    第15章_锁: (表级锁、页级锁、行锁、悲观锁、乐观锁、全局锁、死锁)
    JAVA技术设计模式
    企业信息化安全方案设计实战参考
    DAY25:逻辑漏洞
  • 原文地址:https://blog.csdn.net/hgf1037882434/article/details/133272525