基于ET框架的Unity音效管理组件 Plus (转)
介绍
本组件是基于强大的ET框架之上的,此处附上et框架链接:https://github.com/egametang/ET
当然如果不使用et的话,也可自行修改为单例模式进行使用,非常简单易用
本音效组件主要分三个文件:
SoundComponent.cs——游戏音效管理组件
SoundData.cs——每个音效单元类
EditorCreateAudio.cs——创建音效预设(Editor类)
特点:支持控制全局音量变化和单独控制音量和音效,音乐静音及音效静音
流程:.mp3等资源–> . prefab预制体并附上SoundData单元类–> SoundComponent中通过ab加载. prefab并实例化–> SoundComponent调用SoundData去控制每一个音效
上面都是抄的渐渐大佬,哈哈,因为不想码字,我只是做了一点简单的拓展
SoundData(音效资源类)
请看代码,一目了然,简单明了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
using System; using System.Collections; using System.Collections.Generic; using JetBrains.Annotations; using UnityEngine; namespace ETModel { [RequireComponent(typeof(AudioSource))] public class SoundData:MonoBehaviour { //音频源控件 public new AudioSource audio; /// <summary> /// 是否强制重新播放 /// </summary> //[HideInInspector] public bool isForceReplay = false; /// <summary> /// 是否循环播放 /// </summary> //[HideInInspector] public bool isLoop = false; /// <summary> /// 音量 /// </summary> //[HideInInspector] public float volume = 1; /// <summary> /// 延迟 /// </summary> //[HideInInspector] public ulong delay = 0; public AudioSource GetAudio() { return audio; } public bool IsPlaying { get { return audio != null && audio.isPlaying; } } public bool IsPause { get; set; } public void Dispose() { StopAllCoroutines(); UnityEngine.Object.Destroy(gameObject); } /// <summary> /// 音效类型 /// </summary> public SoundType Sound { get; set; } public bool Mute { get { return audio.mute; } set { audio.mute = value; } } public float Volume { get { return audio.volume; } set { audio.volume = value; } } public delegate void AudioCallBack(); private AudioCallBack AudioCallback; private Coroutine func; /// <summary> /// 播放音效 /// </summary> /// <param name="callback"></param> public void PlayClipData(AudioCallBack callback=null) { audio.Play(this.delay); AudioCallback = callback; if (callback != null) func=StartCoroutine(DelayedCallBack(audio.clip.length)); } /// <summary> /// 重新开始播放音效 /// </summary> /// <param name="callback"></param> public void ResumeData(AudioCallBack callback = null) { this.audio.PlayDelayed(this.delay); this.audio.PlayScheduled(0); AudioCallback = callback; if (callback != null) func=StartCoroutine(DelayedCallBack(audio.clip.length)); } /// <summary> /// 音效事件的回调 /// </summary> /// <param name="time">音效的时间</param> /// <param name="callback">音效的回调事件</param> /// <returns></returns> private IEnumerator DelayedCallBack(float time) { yield return new WaitForSeconds(time); if (this.AudioCallback != null) AudioCallback(); } /// <summary> /// 音效事件的回调 /// </summary> /// <param name="time">音效的时间</param> /// <param name="callback">音效的回调事件</param> /// <returns></returns> public void StopCallBackIEnumerator() { if (func != null) { AudioCallback = null; StopCoroutine(func); } } } } |
这里我给音效添加了一个回调的方法,在音乐播放完毕时执行一个事件
EditorCreateAudio(工具类)
自动将目录下的音效资源文件创建成Prefab预制体,方便加载和管理,这里我给音效Prefab自动加上了ab标识
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.IO; namespace ETModel { public class EditorCreateAudio:MonoBehaviour { //音效资源路径 private static string audiosDir = "assets/Res/Sounds"; //导出预制体路径 private static string prefabDir = "assets/Bundles/SoundsPre/sound_"; [MenuItem("SoundManage / GreateSoundPre", priority = 1004)] static void CreateAudioPrefab() { Debug.Log("正在创建音效..."); string[] _patterns = new string[] { "*.mp3","*.wav", "*.ogg" };//识别不同的后缀名 List _allFilePaths = new List(); foreach (var item in _patterns) { string[] _temp = Directory.GetFiles(audiosDir, item, SearchOption.AllDirectories); _allFilePaths.AddRange(_temp); } foreach (var item in _allFilePaths) { System.IO.FileInfo _fi = new System.IO.FileInfo(item); var _tempName = _fi.Name.Replace(_fi.Extension, "").ToLower(); AudioClip _clip = AssetDatabase.LoadAssetAtPath(item); if (null != _clip) { GameObject _go = new GameObject(); _go.name = _tempName; AudioSource _as = _go.AddComponent(); _as.playOnAwake = false; SoundData _data = _go.AddComponent(); _data.audio = _as; _data.audio.clip = _clip; var temp = PrefabUtility.CreatePrefab(prefabDir + _tempName + ".prefab", _go); GameObject.DestroyImmediate(_go); EditorUtility.SetDirty(temp); AssetImporter ai = AssetImporter.GetAtPath(prefabDir + _tempName+".prefab"); ai.assetBundleName = "sound_"+ _tempName+".unity3d"; Resources.UnloadAsset(_clip); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("创建音效完成..."); } } } |
SoundComponent(音效管理组件)
新增加了基于主音量的的长音乐和短音效的独立音量控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; namespace ETModel { /// <summary> /// 音效类型 /// </summary> public enum SoundType { Music,//长音乐 Sound,//短音乐 } [ObjectSystem] public class SoundComponentEvent : AwakeSystem<SoundComponent> { public override void Awake(SoundComponent self) { self.Awake(); } } /// <summary> /// 游戏音效管理组件 /// </summary> public class SoundComponent: Component { public static SoundComponent Instance; /// <summary> /// 控制游戏全局音量 /// </summary> public float SoundVolume { get { return _soundVolume; } set { _soundVolume = Mathf.Clamp(value, 0, 1); foreach (SoundData clip in m_clips.Values) { clip.Volume = _soundVolume * clip.volume; } } } private float _soundVolume = 1f; //所有的短音效 private float _soundEffectsVolume = 0.5f; //所有的长音效 private float _soundMusicBGMVolume = 0.5f; /// <summary> /// 控制游戏音效的音量 /// </summary> public float SoundEffectsVolume { get { return _soundEffectsVolume; } set { _soundEffectsVolume = Mathf.Clamp(value, 0, 1); foreach (SoundData clip in _allClips[SoundType.Sound].Values) { clip.Volume = _soundEffectsVolume; } PlayerPrefs.SetFloat("SoundEffect", _soundEffectsVolume); } } /// <summary> /// 控制游戏音量的音量 /// </summary> public float SoundMusicBGMVolume { get { return _soundMusicBGMVolume; } set { _soundMusicBGMVolume = Mathf.Clamp(value, 0, 1); foreach (SoundData clip in _allClips[SoundType.Music].Values) { clip.Volume = _soundMusicBGMVolume; } PlayerPrefs.SetFloat("SoundMusic", _soundMusicBGMVolume); } } //所有音效 private Dictionary<string, SoundData> m_clips = new Dictionary<string, SoundData>(); //根据类型分类所有音效 private Dictionary<SoundType, Dictionary<string, SoundData>> _allClips = new Dictionary<SoundType, Dictionary<string, SoundData>>() { { SoundType.Music, new Dictionary<string, SoundData>() }, { SoundType.Sound, new Dictionary<string, SoundData>() } }; //catch ab资源 private static Dictionary<string, SoundData> abSounds = new Dictionary<string, SoundData>(); //根物体 private Transform root; /// <summary> /// 音乐静音 /// </summary> public bool MusicMute { get { return _musicMute; } set { _musicMute = value; foreach (var soundData in _allClips[SoundType.Music].Values) { soundData.Mute = _musicMute; } PlayerPrefs.SetInt("MusicMute", value? 1 : 0); } } private bool _musicMute = false; /// <summary> /// 音效静音 /// </summary> public bool SoundMute { get { return _soundMute; } set { _soundMute = value; foreach (var soundData in _allClips[SoundType.Sound].Values) { soundData.Mute = _soundMute; } PlayerPrefs.SetInt("SoundMute", value? 1 : 0); } } private bool _soundMute = false; public void Awake() { Instance = this; _musicMute = PlayerPrefs.GetInt("MusicMute", 0) == 1; _soundMute = PlayerPrefs.GetInt("SoundMute", 0) == 1; root = new GameObject("SoundDatas").transform; if (PlayerPrefs.HasKey("SoundMusic")) SoundMusicBGMVolume = PlayerPrefs.GetFloat("SoundMusic"); if (PlayerPrefs.HasKey("SoundEffect")) SoundEffectsVolume = PlayerPrefs.GetFloat("SoundEffect"); GameObject.DontDestroyOnLoad(root.gameObject); //开局播放背景音乐 this.PlayBgBGM(); } private bool IsContainClip(string clipName) { lock (m_clips) { if (m_clips.ContainsKey(clipName)) return true; return false; } } private SoundData GetAudioSource(string clipName) { if (IsContainClip(clipName)) return m_clips[clipName]; return null; } private void AddClip(string clipName, SoundData data, SoundType type) { lock (m_clips) { data.IsPause = false; data.transform.transform.SetParent(root); data.Sound = type; if (IsContainClip(clipName)) { m_clips[clipName] = data; _allClips[type][clipName] = data; } else { m_clips.Add(clipName, data); _allClips[type].Add(clipName, data); } } } /// <summary> /// 短暂的声音和特效 /// 无法暂停 /// 异步加载音效 /// </summary> public async void PlayClip(string clipName,Action action = null) { SoundData sd = await LoadSound(clipName); if (sd != null) { sd.volume = Mathf.Clamp(_soundVolume*_soundEffectsVolume, 0, 1); sd.Mute = SoundMute; if (!IsContainClip(clipName)) { AddClip(clipName, sd, SoundType.Sound); } PlayMusic(clipName, sd, action); } else { Log.Error($"没有此音效 ={clipName}"); } } /// <summary> /// 播放长音乐 背景音乐等 /// 可以暂停 继续播放 /// 异步加载音效 /// </summary> /// <param name=”clipName”>声音的预设名字(不包括前缀路径名)</param> /// <param name=”delay”>延迟播放 单位秒</param> /// <param name=”volume”>音量</param> /// <param name=”isloop”>是否循环播放</param> /// /// <param name=”forceReplay”>是否强制重头播放</param> public async void PlayBgmMusic(string clipName, ulong delay = 0, bool isloop = false, bool forceReplay = false, Action action = null) { SoundData sd = await LoadSound(clipName); if (sd != null) { sd.isForceReplay = forceReplay; sd.isLoop = isloop; sd.delay = delay; sd.volume = Mathf.Clamp(_soundVolume * _soundMusicBGMVolume, 0, 1); sd.Mute = MusicMute; if (!IsContainClip(clipName)) { AddClip(clipName, sd, SoundType.Music); } PlayMusic(clipName, sd, action); } else { Log.Error($"没有此音效 ={clipName}"); } } //加载声音 private async Task<SoundData> LoadSound(string soundName) { ResourcesComponent resourcesComponent = Game.Scene.GetComponent<ResourcesComponent>(); if (!abSounds.ContainsKey(soundName) || abSounds[soundName] == null) { await resourcesComponent.LoadBundleAsync($"{soundName}.unity3d"); abSounds.Add(soundName,GameObject.Instantiate((GameObject) resourcesComponent.GetAsset($"{soundName}.unity3d", soundName)) .GetComponent<SoundData>()); resourcesComponent.UnloadBundle($"{soundName}.unity3d"); } return abSounds[soundName]; } //播放SoundData private void PlayMusic(string clipName, SoundData asource, Action action) { if (null == asource) return; bool forceReplay = asource.isForceReplay; asource.audio.volume = asource.volume; asource.audio.loop = asource.isLoop; //判断是否强制重新播放 if (!forceReplay) { //判断是否正在播放 if (!asource.IsPlaying) { //如果是短音效直接就play if (asource.Sound == SoundType.Sound) { asource.audio.Play(asource.delay); return; } //判断是否暂停 if (!asource.IsPause) { asource.PlayClipData(() => { action(); }); } else { Resume(clipName); } } } else { asource.ResumeData(()=> { actions(); }); } } /// <summary> /// 停止并销毁声音 /// </summary> /// <param name=”clipName”></param> public void Stop(string clipName) { SoundData data = GetAudioSource(clipName); if (null != data) { if (_allClips[data.Sound].ContainsKey(clipName)) { _allClips[data.Sound].Remove(clipName); } m_clips.Remove(clipName); abSounds.Remove(clipName); data.Dispose(); } } /// <summary> /// 暂停声音 /// </summary> /// <param name=”clipName”></param> public void Pause(string clipName) { SoundData data = GetAudioSource(clipName); if (null != data) { data.IsPause = true; data.audio.Pause(); } } /// <summary> /// 继续播放 /// </summary> /// <param name=”clipName”></param> public void Resume(string clipName) { SoundData data = GetAudioSource(clipName); if (null != data) { data.IsPause = false; data.audio.UnPause(); } } /// <summary> /// 销毁所有声音 /// </summary> public void DisposeAll() { foreach (var allClip in _allClips.Values) { allClip.Clear(); } foreach (var item in m_clips) { item.Value.Dispose(); } m_clips.Clear(); } /// <summary> /// 播放背景音乐,顺序播放 /// </summary> private int BgmIndex = 0; public void PlayBgBGM() { if (this.BgmIndex == SoundsName.BgBGM.Count) { this.BgmIndex = 0; } string clipName = SoundsName.BgBGM[this.BgmIndex]; PlayBgmMusic(clipName, 0, false, true,()=> { return this.PlayBgBGM(); }); BgmIndex++; } public async void StopBgBgm() { this.BgmIndex--; string clipName = SoundsName.BgBGM[this.BgmIndex]; SoundData sd = await LoadSound(clipName); sd.StopCallBackIEnumerator(); this.Pause(clipName); } } } |
SoundName(音效资源组件)
这里就是一个简单的资源组件啦,把所有需要加载的东西放在一个类里面管理,游戏的背景音乐我就直接放在一个集合种了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System; using System.Collections.Generic; namespace ETModel { public static class SoundsName { //背景音效 public static List BgBGM = new List() { "bgm1", "bgm2" }; public static string sound1 = "音效1"; public static string sound2 = "音效2"; } } |
Over
希望能对大家有点帮助.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)