• Unity 音频插件 - MasterAudio 实现音频管理系统


    插件介绍:

    Master Audio的是一个整体解决方案,所有的丰富的游戏音频需求。内置的音乐闪避,手动和自动的声音触发真正的随机声音变化,音频汇集全3D声音功能。支持所有出口的手机游戏平台,具有一流的性能。

    主音频在线帮助网站可在此处找到:

    Table of Contents

    完整的主音频 API 文档可在此处找到:

    Master Audio - AAA Sound Solution!: Main Page

    实现功能:

    1. 音效要能与场景物件绑定,要能单独配置音量,支持相关配置方式
    2. 检查多个音效同时出现时的声音混合是否正常
    3. 提供动态调整某一大类音效音量的接口
    4. 脚步声配置

    代码实现

            这里不细说实现中使用MasterAudio插件的API了(可以直接去翻一下插件文档)。

    1. 动态创建音频组,提供音频的播放、停止接口
    2. 提供调节一组音频的音量大小
    3. 音效与场景物件绑定工具
    4. 脚步声配置工具

    AudioSystem:

    1. using System;
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using DarkTonic.MasterAudio;
    6. using System.Linq;
    7. using CheapUtil;
    8. struct PlayAudioArgs
    9. {
    10. public MasterAudioGroup group;
    11. public string path;
    12. public bool isLoop;
    13. public string callBack;
    14. public Vector3 position;
    15. public float volume;
    16. public float speed;
    17. public float startTime;
    18. }
    19. public class AudioSystem : MonoBehaviour
    20. {
    21. struct MasterAudioGroupConfig
    22. {
    23. public AudioType audioType;
    24. public string name;
    25. public int audioLimit;
    26. public bool is2DSound;
    27. public MasterAudioGroupConfig(AudioType audioType, string name, int audioLimit, bool is2DSound = true)
    28. {
    29. this.audioType = audioType;
    30. this.name = name;
    31. this.audioLimit = audioLimit;
    32. this.is2DSound = is2DSound;
    33. }
    34. }
    35. #region 属性
    36. private static AudioSystem instance = null;
    37. public static AudioSystem Instance => instance;
    38. private float unloadTime = 5f;
    39. private MasterAudio masterAudio;
    40. private static readonly string groupTemplate = "Audio/DefaultSingle";
    41. private static readonly string audioSourceTemplate = "Audio/TemplatesDistance10";
    42. private Dictionary
    43. _masterAudioGroupDic = new Dictionary();
    44. private MasterAudioGroupConfig[] _masterAudioGroupConfig = new[]
    45. {
    46. new MasterAudioGroupConfig(AudioType.BGM, "BGM_SoundGroup", 0),
    47. new MasterAudioGroupConfig(AudioType.SE_2D, "2D_SoundGroup", 9),
    48. new MasterAudioGroupConfig(AudioType.SE_3D, "3D_SoundGroup", 9, false),
    49. new MasterAudioGroupConfig(AudioType.Voice, "Voice_SoundGroup", 0),
    50. new MasterAudioGroupConfig(AudioType.Footstep, "Footstep_SoundGroup", 9),
    51. };
    52. // 淡出携程,仅支持BGM和Voice(同时唯一播放)
    53. private Coroutine audioFadeOutCorBGM = null;
    54. private static PlayAudioArgs waitingBGM = new PlayAudioArgs();
    55. private Coroutine audioFadeOutCorVoice = null;
    56. private static PlayAudioArgs waitingVoice = new PlayAudioArgs();
    57. // 正在播放的音效数据
    58. private static List sgvList = new List();
    59. #endregion
    60. #region 初始化相关
    61. //初始化使用到的MasterAudio音乐组件
    62. public void Awake()
    63. {
    64. GameObject Masterad = new GameObject("MasterAudio");
    65. if (Masterad != null)
    66. {
    67. DontDestroyOnLoad(Masterad);
    68. } //加载时不销毁此GameObject
    69. masterAudio = Masterad.AddComponent();
    70. //设置MasterAudio的参数值
    71. masterAudio.useGroupTemplates = true;
    72. GameObject groupTemplateObj = GameAssetProxy.Load(GameAssetType.DataPrefab, groupTemplate, false) as GameObject;
    73. masterAudio.groupTemplates.Add(groupTemplateObj);
    74. masterAudio.soundGroupTemplate = groupTemplateObj.transform;
    75. GameObject audioSourceTemplateObj =
    76. GameAssetProxy.Load(GameAssetType.DataPrefab, audioSourceTemplate, false) as GameObject;
    77. masterAudio.audioSourceTemplates.Add(audioSourceTemplateObj);
    78. masterAudio.soundGroupVariationTemplate = audioSourceTemplateObj.transform;
    79. masterAudio.musicSpatialBlendType = MasterAudio.AllMusicSpatialBlendType.AllowDifferentPerController;
    80. masterAudio.mixerSpatialBlendType = MasterAudio.AllMixerSpatialBlendType.AllowDifferentPerGroup;
    81. masterAudio.newGroupSpatialType = MasterAudio.ItemSpatialBlendType.UseCurveFromAudioSource;
    82. }
    83. private void Start()
    84. {
    85. InitAudioGroups();
    86. instance = this;
    87. }
    88. private void InitAudioGroups()
    89. {
    90. for (int i = 0; i < _masterAudioGroupConfig.Length; i++)
    91. {
    92. var cfg = _masterAudioGroupConfig[i];
    93. MasterAudioGroup group = CreateSoundGroup(cfg.audioType, cfg.name, cfg.audioLimit, cfg.is2DSound);
    94. if (_masterAudioGroupDic.ContainsKey(cfg.audioType))
    95. {
    96. Debug.LogError("配置了相同类型的Group,请检查配置");
    97. }
    98. else
    99. {
    100. _masterAudioGroupDic.Add(cfg.audioType, group);
    101. }
    102. }
    103. }
    104. private MasterAudioGroup CreateSoundGroup(AudioType audioType, string rootName, int variationNum, bool is2DSound)
    105. {
    106. // string rootName = GetAudioGroupName(audioType);
    107. Transform trans =
    108. MasterAudio.CreateSoundGroup(rootName, MasterAudio.Instance.gameObject, variationNum, is2DSound);
    109. MasterAudioGroup audioGroup = trans.GetComponent();
    110. if (audioGroup == null)
    111. {
    112. audioGroup = trans.gameObject.AddComponent();
    113. }
    114. audioGroup.groupMasterVolume = GetAudioGroupVolume(audioType);
    115. return audioGroup;
    116. }
    117. float deltaTime = 0f;
    118. public void LateUpdate()
    119. {
    120. deltaTime += Time.deltaTime;
    121. if (deltaTime >= unloadTime)
    122. {
    123. deltaTime = 0f;
    124. TryUnloadUselessClips();
    125. }
    126. }
    127. #endregion
    128. #region 开放方法
    129. ///
    130. /// 播放音效
    131. ///
    132. /// 音效组类型
    133. /// 音效路径
    134. /// 音量[0, 1] (仅该音效的音量,不指音效组总音量)
    135. /// 是否循环播放
    136. /// 播放完毕回调
    137. /// 播放速度
    138. /// 是否淡出
    139. /// 淡出时长
    140. /// 3D音效的位置
    141. /// 3D音效的位置
    142. /// 3D音效的位置
    143. public void PlayAudio(AudioType audioType, string path, float volume = 1f, bool isLoop = false,
    144. bool isFadeOut = false, float fadeOutTime = 1f,
    145. string callBack = null, float speed = 1f, float startTime = 0, float posX = 0f, float posY = 0f,
    146. float posZ = 0f)
    147. {
    148. MasterAudioGroup group = GetAudioGroup(audioType);
    149. if (!TryLoadAudio(group, path)) return;
    150. if (volume > 1f) volume = 1f;
    151. if (volume < 0f) volume = 0f;
    152. bool canPlay = true;
    153. Coroutine curCor = GetAudioFadeOutCor(audioType);
    154. if (curCor != null)
    155. {
    156. canPlay = false;
    157. }
    158. else
    159. {
    160. if (isFadeOut && AllowFadeOut(audioType))
    161. {
    162. SetAudioFadeOutCor(audioType, StartCoroutine(FadeOutAndPlayNext(fadeOutTime, audioType)));
    163. canPlay = false;
    164. }
    165. }
    166. if (canPlay)
    167. {
    168. RealPlayAudio(group, path, isLoop, callBack, new Vector3(posX, posY, posZ), volume, speed, startTime);
    169. }
    170. else
    171. {
    172. if (audioType == AudioType.BGM)
    173. {
    174. waitingBGM.group = group;
    175. waitingBGM.path = path;
    176. waitingBGM.isLoop = isLoop;
    177. waitingBGM.callBack = callBack;
    178. waitingBGM.position = new Vector3(posX, posY, posZ);
    179. waitingBGM.volume = volume;
    180. waitingBGM.speed = speed;
    181. waitingBGM.startTime = startTime;
    182. }
    183. else if (audioType == AudioType.Voice)
    184. {
    185. waitingVoice.group = group;
    186. waitingVoice.path = path;
    187. waitingVoice.isLoop = isLoop;
    188. waitingVoice.callBack = callBack;
    189. waitingVoice.position = new Vector3(posX, posY, posZ);
    190. waitingVoice.volume = volume;
    191. waitingVoice.speed = speed;
    192. waitingVoice.startTime = startTime;
    193. }
    194. }
    195. }
    196. public void PlaySound3DAtVector3(AudioType audioType, string path, Vector3 pos, float volume = 1f,
    197. bool isLoop = false,float pitch = 1,float delayTime = 0, string callBack = null, float startTime = 0)
    198. {
    199. MasterAudioGroup group = GetAudioGroup(audioType);
    200. if (!TryLoadAudio(group, path)) return;
    201. if (volume > 1f) volume = 1f;
    202. if (volume < 0f) volume = 0f;
    203. PlaySoundResult result = MasterAudio.PlaySound3DAtVector3(group.name,path,pos, volume, pitch, delayTime);
    204. SetPlaySoundResult(result,path,isLoop,callBack,startTime);
    205. }
    206. public void PlaySound3DAtTransform(AudioType audioType, string path, Transform trans, float volume = 1f,
    207. bool isLoop = false,float pitch = 1,float delayTime = 0, string callBack = null, float startTime = 0)
    208. {
    209. MasterAudioGroup group = GetAudioGroup(audioType);
    210. if (!TryLoadAudio(group, path)) return;
    211. if (volume > 1f) volume = 1f;
    212. if (volume < 0f) volume = 0f;
    213. PlaySoundResult result = MasterAudio.PlaySound3DAtTransform(group.name,path, trans, volume, pitch, delayTime);
    214. SetPlaySoundResult(result,path,isLoop,callBack,startTime);
    215. }
    216. public void PlaySound3DFollowTransform(AudioType audioType, string path, Transform trans, float volume = 1f,
    217. bool isLoop = false,float pitch = 1,float delayTime = 0, string callBack = null, float startTime = 0)
    218. {
    219. MasterAudioGroup group = GetAudioGroup(audioType);
    220. if (!TryLoadAudio(group, path)) return;
    221. if (volume > 1f) volume = 1f;
    222. if (volume < 0f) volume = 0f;
    223. PlaySoundResult result = MasterAudio.PlaySound3DFollowTransform(group.name,path, trans, volume, pitch, delayTime);
    224. SetPlaySoundResult(result,path,isLoop,callBack,startTime);
    225. }
    226. public void StopAudio(AudioType audioType, string path)
    227. {
    228. MasterAudioGroup group = GetAudioGroup(audioType);
    229. // 无法真正停止播放
    230. // foreach(SoundGroupVariation var in group.groupVariations)
    231. // {
    232. // if (var.IsPlaying && var.resourceFileName == path)
    233. // {
    234. // MasterAudio.StopVariationInSoundGroup(group.GameObjectName, var.name);
    235. // }
    236. // }
    237. for (int i = 0; i < sgvList.Count; i++)
    238. {
    239. var sgv = sgvList[i];
    240. if (sgv.IsPlaying)
    241. MasterAudio.StopVariationInSoundGroup(group.GameObjectName, sgv.name);
    242. }
    243. sgvList.RemoveAll(item => (item.IsPlaying == false || item.VarAudio.clip == null));
    244. }
    245. public void StopAudioGroup(AudioType audioType, bool isFadeOut = false, float fadeOutTime = 1f)
    246. {
    247. string curPath = GetPlayingPaths(audioType);
    248. if (curPath.Equals(""))
    249. return;
    250. MasterAudioGroup group = GetAudioGroup(audioType);
    251. if (audioType == AudioType.BGM)
    252. {
    253. waitingBGM.path = null;
    254. }
    255. else if (audioType == AudioType.Voice)
    256. {
    257. waitingVoice.path = null;
    258. }
    259. if (isFadeOut)
    260. {
    261. Coroutine curCor = GetAudioFadeOutCor(audioType);
    262. if (curCor == null)
    263. {
    264. SoundGroupVariation curSGV = GetAudioCurVariation(audioType);
    265. SetAudioFadeOutCor(audioType, StartCoroutine(FadeOutAndPlayNext(fadeOutTime, audioType)));
    266. }
    267. else
    268. {
    269. RealStopAudioGroup(audioType);
    270. }
    271. }
    272. else
    273. {
    274. RealStopAudioGroup(audioType);
    275. }
    276. }
    277. // todo
    278. public void PauseAudio(AudioType audioType, string path)
    279. {
    280. }
    281. public void PauseAudioGroup(AudioType audioType)
    282. {
    283. }
    284. public void ResumeAudio(AudioType audioType, string path)
    285. {
    286. }
    287. public void ResumeAudioGroup(AudioType audioType)
    288. {
    289. }
    290. public void SilentAudioGroup(AudioType audioType, bool bMute)
    291. {
    292. MasterAudioGroup group = GetAudioGroup(audioType);
    293. if (bMute)
    294. MasterAudio.MuteGroup(group.GameObjectName);
    295. else
    296. MasterAudio.UnmuteGroup(group.GameObjectName);
    297. }
    298. public void SetAudioGroupVolume(AudioType audioType, float volume, bool isGradual = false, float time = 1f)
    299. {
    300. if (volume > 1f) volume = 1f;
    301. if (volume < 0f) volume = 0f;
    302. MasterAudioGroup group = GetAudioGroup(audioType);
    303. if (!isGradual)
    304. MasterAudio.SetGroupVolume(group.GameObjectName, volume);
    305. else
    306. MasterAudio.FadeSoundGroupToVolume(group.GameObjectName, volume, time);
    307. SaveAudioGroupVolume(audioType,volume);
    308. }
    309. public float GetAudioPlayTime(string path)
    310. {
    311. foreach (SoundGroupVariation sgv in sgvList)
    312. {
    313. if (sgv.GetCurClipName() == path)
    314. return sgv.VarAudio.time;
    315. }
    316. return -1;
    317. }
    318. public float GetAudioLength(string path)
    319. {
    320. foreach (SoundGroupVariation sgv in sgvList)
    321. {
    322. if (sgv.GetCurClipName() == path)
    323. return sgv.VarAudio.clip.length;
    324. }
    325. return -1;
    326. }
    327. /// 如有多个,以逗号分隔
    328. public string GetPlayingPaths(AudioType audioType)
    329. {
    330. string paths = "";
    331. foreach (SoundGroupVariation sgv in sgvList)
    332. {
    333. if (sgv.ParentGroup == GetAudioGroup(audioType))
    334. {
    335. if (!paths.Equals(""))
    336. paths += ",";
    337. paths += sgv.GetCurClipName();
    338. }
    339. }
    340. return paths;
    341. }
    342. #endregion
    343. #region private
    344. private bool TryLoadAudio(MasterAudioGroup group, string path)
    345. {
    346. if (path == null)
    347. {
    348. Debug.LogError("传入的文件路径为空");
    349. return false;
    350. }
    351. if (group == null)
    352. {
    353. Debug.LogError("当前没有创建相关的MasterAudioGroup");
    354. return false;
    355. }
    356. if (!LoadAudio(group, path))
    357. {
    358. Debug.LogError("加载 " + path + " 路径下的资源失败");
    359. return false;
    360. }
    361. return true;
    362. }
    363. private bool LoadAudio(MasterAudioGroup audioGroup, string clipPath)
    364. {
    365. if (!audioGroup.hasReadyClip(clipPath))
    366. {
    367. AudioClip cp = GameAssetProxy.Load(GameAssetType.Audio, clipPath);
    368. cp.name = clipPath; // 音频这边以path为key
    369. if (cp == null)
    370. return false;
    371. audioGroup.ReadyAudioClips.Add(clipPath, cp);
    372. }
    373. return true;
    374. }
    375. private void RealPlayAudio(MasterAudioGroup group, string path, bool isloop, string callBack, Vector3 position,
    376. float volume = 1f, float speed = 1f, float startTime = 0)
    377. {
    378. PlaySoundResult result;
    379. if (position != null && !position.Equals(Vector3.zero))
    380. // 对Z轴距离无效
    381. result = MasterAudio.PlaySound3DAtVector3(group.GameObjectName, path, position, volume, speed);
    382. else
    383. result = MasterAudio.PlaySound(group.GameObjectName, path, volume, speed);
    384. if (result != null)
    385. {
    386. SoundGroupVariation sgv = result.ActingVariation;
    387. sgv.VarAudio.loop = isloop;
    388. sgv.JumpToTime(startTime);
    389. sgvList.Add(sgv);
    390. if (callBack != null)
    391. StartCoroutine(PlayEndCallBack(sgv, path, callBack));
    392. }
    393. else
    394. {
    395. Debug.LogError("播放音效失败!");
    396. return;
    397. }
    398. }
    399. private void RealPlayAudio(MasterAudioGroup group, string path, bool isloop, string callBack, Transform trans,
    400. float volume = 1f, float speed = 1f, float startTime = 0)
    401. {
    402. PlaySoundResult result;
    403. if (trans != null)
    404. // 对Z轴有效
    405. result = MasterAudio.PlaySound3DFollowTransform(group.GameObjectName, path, trans, volume, 1, 0);
    406. else
    407. result = MasterAudio.PlaySound(group.GameObjectName, path, volume, speed);
    408. if (result != null)
    409. {
    410. SetPlaySoundResult(result,path,isloop,callBack,startTime);
    411. }
    412. else
    413. {
    414. Debug.LogError("播放音效失败!");
    415. return;
    416. }
    417. }
    418. private void SetPlaySoundResult(PlaySoundResult result,string path,bool isloop,string callBack,float startTime = 0)
    419. {
    420. if (result != null)
    421. {
    422. SoundGroupVariation sgv = result.ActingVariation;
    423. sgv.VarAudio.loop = isloop;
    424. sgv.JumpToTime(startTime);
    425. sgvList.Add(sgv);
    426. if (callBack != null)
    427. StartCoroutine(PlayEndCallBack(sgv, path, callBack));
    428. }
    429. }
    430. public MasterAudioGroup GetAudioGroup(AudioType audioType)
    431. {
    432. MasterAudioGroup group = null;
    433. if (!_masterAudioGroupDic.TryGetValue(audioType, out group))
    434. {
    435. Debug.LogError($"not find group,audioType:{audioType.ToString()}");
    436. }
    437. return group;
    438. }
    439. private SoundGroupVariation GetAudioCurVariation(AudioType audioType)
    440. {
    441. foreach (SoundGroupVariation sgv in sgvList)
    442. {
    443. if (sgv.ParentGroup.name == GetAudioGroup(audioType).name)
    444. return sgv;
    445. }
    446. return null;
    447. }
    448. private Coroutine GetAudioFadeOutCor(AudioType audioType)
    449. {
    450. switch (audioType)
    451. {
    452. case AudioType.BGM:
    453. return audioFadeOutCorBGM;
    454. case AudioType.Voice:
    455. return audioFadeOutCorVoice;
    456. }
    457. return null;
    458. }
    459. private void SetAudioFadeOutCor(AudioType audioType, Coroutine cor)
    460. {
    461. switch (audioType)
    462. {
    463. case AudioType.BGM:
    464. audioFadeOutCorBGM = cor;
    465. break;
    466. case AudioType.Voice:
    467. audioFadeOutCorVoice = cor;
    468. break;
    469. }
    470. }
    471. private void RealStopAudioGroup(AudioType audioType)
    472. {
    473. MasterAudioGroup group = GetAudioGroup(audioType);
    474. MasterAudio.StopAllOfSound(group.GameObjectName);
    475. }
    476. private PlayAudioArgs GetWaitingArgs(AudioType audioType)
    477. {
    478. if (audioType == AudioType.BGM)
    479. return waitingBGM;
    480. else if (audioType == AudioType.Voice)
    481. return waitingVoice;
    482. return new PlayAudioArgs();
    483. }
    484. private bool AllowFadeOut(AudioType audioType)
    485. {
    486. if (audioType == AudioType.BGM || audioType == AudioType.Voice)
    487. return true;
    488. return false;
    489. }
    490. private IEnumerator FadeOutAndPlayNext(float fadeOutTime, AudioType audioType)
    491. {
    492. SoundGroupVariation curSGV = GetAudioCurVariation(audioType);
    493. if (curSGV != null)
    494. {
    495. curSGV.FadeOutNow(fadeOutTime);
    496. yield return new WaitForSeconds(fadeOutTime);
    497. }
    498. else
    499. {
    500. yield return new WaitForSeconds(0.01f); // 此行仅仅是为了代码方便好写
    501. }
    502. SetAudioFadeOutCor(audioType, null);
    503. RealStopAudioGroup(audioType);
    504. if (AllowFadeOut(audioType)){
    505. PlayAudioArgs waitingArgs = GetWaitingArgs(audioType);
    506. if (waitingArgs.path != null){
    507. RealPlayAudio(waitingArgs.group,
    508. waitingArgs.path,
    509. waitingArgs.isLoop,
    510. waitingArgs.callBack,
    511. waitingArgs.position,
    512. waitingArgs.volume,
    513. waitingArgs.speed,
    514. waitingArgs.startTime);
    515. }
    516. }
    517. }
    518. private IEnumerator PlayEndCallBack(SoundGroupVariation sgv, string path, string callBack)
    519. {
    520. while (sgv != null && sgv.IsPlaying)
    521. yield return null;
    522. LuaClient.GetMainState().CallLuaFunction(callBack, path);
    523. }
    524. private void TryUnloadUselessClips()
    525. {
    526. // todo: 怀疑这里会有"在卸载的同一帧播放音频时播不出来"的bug,但暂时没空自测了
    527. List<string> unloadClipNames = new List<string>();
    528. foreach (SoundGroupVariation sgv in sgvList)
    529. {
    530. if (!sgv.IsPlaying)
    531. {
    532. string name = sgv.VarAudio.clip.name;
    533. if (!unloadClipNames.Contains(name))
    534. unloadClipNames.Add(sgv.VarAudio.clip.name);
    535. }
    536. }
    537. foreach (string clipName in unloadClipNames)
    538. {
    539. bool canRemove = true;
    540. if (clipName == waitingBGM.path || clipName == waitingVoice.path)
    541. canRemove = false;
    542. if (canRemove)
    543. {
    544. foreach (SoundGroupVariation sgv in sgvList)
    545. {
    546. if (sgv.VarAudio.isPlaying && sgv.VarAudio.clip && sgv.VarAudio.clip.name == clipName)
    547. {
    548. canRemove = false;
    549. break;
    550. }
    551. }
    552. }
    553. if (canRemove)
    554. {
    555. List audioGroups = new List();
    556. foreach (SoundGroupVariation sgv in sgvList)
    557. {
    558. if (sgv.VarAudio.clip && sgv.VarAudio.clip.name == clipName)
    559. {
    560. sgv.VarAudio.clip = null;
    561. if (!audioGroups.Contains(sgv.ParentGroup))
    562. audioGroups.Add(sgv.ParentGroup);
    563. }
    564. }
    565. foreach (MasterAudioGroup audioGroup in audioGroups)
    566. {
    567. AudioClip clip = audioGroup.ReadyAudioClips[clipName];
    568. audioGroup.ReadyAudioClips.Remove(clipName);
    569. GameAssetProxy.Unload(GameAssetType.Audio, clip);
    570. clip = null;
    571. }
    572. }
    573. }
    574. sgvList.RemoveAll(item => (item.IsPlaying == false || item.VarAudio.clip == null));
    575. }
    576. #endregion
    577. #region 调整某一大类音效音量
    578. private void SaveAudioGroupVolume(AudioType audioType,float volume)
    579. {
    580. PlayerPrefs.SetFloat(audioType.ToString(), volume);
    581. }
    582. public float GetAudioGroupVolume(AudioType audioType)
    583. {
    584. if(PlayerPrefs.HasKey(audioType.ToString()))
    585. return PlayerPrefs.GetFloat(audioType.ToString());
    586. return 1;
    587. }
    588. #endregion
    589. }

    音频与场景物体绑定工具:

    1. /*
    2. * 物体与声音绑定配置表
    3. * 2022.11.9
    4. * linYiShan
    5. */
    6. using System;
    7. using System.Collections;
    8. using System.Collections.Generic;
    9. using DarkTonic.MasterAudio;
    10. using MainCameraSpace;
    11. using UnityEditor;
    12. using UnityEngine;
    13. [Serializable]
    14. public class AudioClipConfig
    15. {
    16. [HideInInspector]
    17. public string name = "";
    18. public AudioClip clip;
    19. public AudioType audioType = AudioType.SE_3D;
    20. [Tooltip("路径")]
    21. public string path = "";
    22. [Tooltip("音量"),Range(0,1)]
    23. public float volume = 1;
    24. [Tooltip("音高"),Range(0,3)]
    25. public float pitch = 1;
    26. [Tooltip("播放速度")]
    27. public float speed = 1;
    28. [Tooltip("是否循环")]
    29. public bool loop = false;
    30. [Tooltip("显示时播放")]
    31. public bool playOnLoad = false;
    32. [Tooltip("范围")]
    33. public float range = 30;
    34. [HideInInspector] public bool isPlaying = false;
    35. [HideInInspector] public SoundGroupVariation variation = null;
    36. public void Reset()
    37. {
    38. name = string.Empty;
    39. clip = null;
    40. audioType = AudioType.SE_3D;
    41. path = "";
    42. volume = 1;
    43. speed = 1;
    44. pitch = 1;
    45. playOnLoad = false;
    46. range = 30;
    47. }
    48. }
    49. public class GameObjectAudioConfig : MonoBehaviour
    50. {
    51. // 编辑器范围显示
    52. public static bool ShowRange = false;
    53. [Header("隐藏时停止播放")]
    54. public bool hideStopAudio = true;
    55. [Header("声音曲线")]
    56. public AnimationCurve audioVolumeCurve = AnimationCurve.Linear(0, 0, 1, 1);
    57. [Header("声音列表")]
    58. public List audioClipConfigList = new List();
    59. private void Start()
    60. {
    61. CreateRangeObj();
    62. }
    63. // Start is called before the first frame update
    64. void OnEnable()
    65. {
    66. StartCoroutine(_PlayOnLoad());
    67. }
    68. private void OnDisable()
    69. {
    70. if(hideStopAudio)
    71. Stop();
    72. }
    73. private void Update()
    74. {
    75. DetectionDistance();
    76. UpdateVolume();
    77. }
    78. public void Play()
    79. {
    80. if (AudioSystem.Instance == null)
    81. {
    82. Debug.LogError("AudioSystem not init");
    83. return;
    84. }
    85. for (int i = 0; i < audioClipConfigList.Count; i++)
    86. {
    87. AudioClipConfig config = audioClipConfigList[i];
    88. _Play(config);
    89. }
    90. }
    91. public void Stop()
    92. {
    93. if (AudioSystem.Instance == null)
    94. {
    95. return;
    96. }
    97. for (int i = 0; i < audioClipConfigList.Count; i++)
    98. {
    99. AudioClipConfig config = audioClipConfigList[i];
    100. _Stop(config);
    101. }
    102. }
    103. private void _Stop(AudioClipConfig config)
    104. {
    105. if (config.isPlaying)
    106. {
    107. if (config.variation != null)
    108. {
    109. AudioSystem.Instance.StopAudioBySgvName(config.audioType,config.variation.name);
    110. config.isPlaying = false;
    111. config.variation = null;
    112. }
    113. // else
    114. // {
    115. // AudioSystem.Instance.StopAudio(config.audioType,config.path);
    116. // Debug.Log("stop audio: " + config.path);
    117. // config.isPlaying = false;
    118. // }
    119. }
    120. }
    121. private IEnumerator _PlayOnLoad()
    122. {
    123. while (AudioSystem.Instance == null)
    124. {
    125. yield return null;
    126. }
    127. for (int i = 0; i < audioClipConfigList.Count; i++)
    128. {
    129. AudioClipConfig config = audioClipConfigList[i];
    130. if(config.audioType == AudioType.SE_3D) continue;
    131. if (config.playOnLoad)
    132. {
    133. _Play(config);
    134. }
    135. }
    136. }
    137. private void _Play(AudioClipConfig config)
    138. {
    139. if (config.isPlaying) return;
    140. // if (config.audioType == AudioType.SE_3D)
    141. // {
    142. config.variation = AudioSystem.Instance.PlayGameObjectConfigAudio(config.audioType, config.path,1, config.loop);
    143. if(config.variation == null) return;
    144. config.isPlaying = true;
    145. // }
    146. // else
    147. // {
    148. // AudioSystem.Instance.PlayAudio(config.audioType,config.path,config.volume,config.loop);
    149. // config.isPlaying = true;
    150. // Debug.Log("play audio: " + config.path);
    151. // }
    152. // AudioSystem.Instance.PlaySound3DFollowTransform(config.audioType, config.path, transform, config.volume, config.loop);
    153. }
    154. private bool CheckInRange(float range)
    155. {
    156. // var p = GetCameraLookAtGroundPos();
    157. var p = AudioSystem.Instance.Scene3DTriggerTrans.position;
    158. if (Vector3.Distance(p, transform.position) >= range) return false;
    159. return true;
    160. }
    161. private void DetectionDistance()
    162. {
    163. if (AudioSystem.Instance == null) return;
    164. if (AudioSystem.Instance.Scene3DTriggerTrans == null) return;
    165. if(audioClipConfigList == null) return;
    166. for (int i = 0; i < audioClipConfigList.Count; i++)
    167. {
    168. var cfg = audioClipConfigList[i];
    169. if(cfg.audioType != AudioType.SE_3D) continue;
    170. if (CheckInRange(cfg.range))
    171. {
    172. if (!cfg.isPlaying)
    173. {
    174. _Play(cfg);
    175. }
    176. }
    177. else
    178. {
    179. if (cfg.isPlaying)
    180. {
    181. _Stop(cfg);
    182. }
    183. }
    184. }
    185. }
    186. public Vector3 GetCameraLookAtGroundPos()
    187. {
    188. Transform camera = MainCameraController.GetInstance().GetMainCameraTransform();
    189. Vector3 p = GetIntersectWithLineAndPlane(camera.position,camera.forward,new Vector3(0,1,0), new Vector3(0,transform.position.y,9));
    190. return p;
    191. }
    192. ///
    193. /// 计算直线与平面的交点
    194. ///
    195. /// 直线上某一点
    196. /// 直线的方向
    197. /// 垂直于平面的的向量
    198. /// 平面上的任意一点
    199. ///
    200. private Vector3 GetIntersectWithLineAndPlane(Vector3 point, Vector3 direct, Vector3 planeNormal, Vector3 planePoint)
    201. {
    202. float d = Vector3.Dot(planePoint - point, planeNormal) / Vector3.Dot(direct.normalized, planeNormal);
    203. return d * direct.normalized + point;
    204. }
    205. private void UpdateVolume()
    206. {
    207. if (AudioSystem.Instance == null) return;
    208. if (AudioSystem.Instance.Scene3DTriggerTrans == null) return;
    209. for (int i = 0; i < audioClipConfigList.Count; i++)
    210. {
    211. AudioClipConfig config = audioClipConfigList[i];
    212. if(config.audioType != AudioType.SE_3D) continue;
    213. if (config.isPlaying)
    214. {
    215. float dis = Vector3.Distance(AudioSystem.Instance.Scene3DTriggerTrans.position, transform.position) / (config.range);
    216. dis = 1 - Math.Min(dis, 1);
    217. float volume = audioVolumeCurve.Evaluate(dis);
    218. volume = volume * config.volume;
    219. // MasterAudioGroup group = AudioSystem.Instance.GetAudioGroup(config.audioType);
    220. if (config.variation != null)
    221. {
    222. // MasterAudio.ChangeVariationVolume(group.GameObjectName,true,config.variation.name,volume);
    223. config.variation.VarAudio.volume = volume;
    224. }
    225. }
    226. }
    227. }
    228. public void CreateRangeObj()
    229. {
    230. #if UNITY_EDITOR
    231. if(!GameObjectAudioConfig.ShowRange) return;
    232. Transform audioTrans = transform.Find("AuidoRange");
    233. if (audioTrans == null)
    234. {
    235. GameObject obj = new GameObject("AuidoRange");
    236. obj.transform.SetParent(transform);
    237. audioTrans = obj.transform;
    238. audioTrans.localScale = new Vector3(1,1,1);
    239. audioTrans.localPosition = new Vector3();
    240. }
    241. var spherePrefab = EditorGUIUtility.LoadRequired("Assets/Resources/Editor/Audio/Sphere.prefab") as GameObject;
    242. for (int i = 0; i < audioClipConfigList.Count; i++)
    243. {
    244. var config = audioClipConfigList[i];
    245. if(config.clip == null) continue;
    246. Transform child = audioTrans.Find(config.name);
    247. if (child == null)
    248. {
    249. var obj = Instantiate(spherePrefab, audioTrans);
    250. obj.name = config.name;
    251. child = obj.transform;
    252. }
    253. float radius = config.range * 2;
    254. child.localScale = new Vector3(radius, radius, radius);
    255. child.localPosition = new Vector3();
    256. }
    257. #endif
    258. }
    259. }

    Editor文件夹下的编辑器脚本:

    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using UnityEditor;
    5. [CustomEditor(typeof(GameObjectAudioConfig))]
    6. public class GameObjectAudioConfigEditor:Editor
    7. {
    8. private List audioClipConfigList;
    9. private void Awake()
    10. {
    11. GameObjectAudioConfig config = target as GameObjectAudioConfig;
    12. audioClipConfigList = config.audioClipConfigList;
    13. }
    14. public override void OnInspectorGUI()
    15. {
    16. for (int i = 0; i < audioClipConfigList.Count; i++)
    17. {
    18. var config = audioClipConfigList[i];
    19. if (config.clip == null)
    20. {
    21. config.Reset();
    22. }
    23. string path = config.clip == null ? string.Empty : AssetDatabase.GetAssetPath(config.clip);
    24. config.path = path == string.Empty ? string.Empty : GetAudioPath(path);
    25. config.name = path == string.Empty ? string.Empty : Path.GetFileNameWithoutExtension(path);
    26. }
    27. base.OnInspectorGUI();
    28. }
    29. private string GetAudioPath(string path)
    30. {
    31. string _path = path.Replace(Path.GetExtension(path), String.Empty);
    32. return _path.Replace("Assets/Resources/", string.Empty);
    33. }
    34. }

    脚步声配置工具:这里使用不用的Layer播放不同的音效

    1. /*
    2. * 脚步声单元
    3. * 2022.11.9
    4. * linYiShan
    5. */
    6. using System;
    7. using System.Collections.Generic;
    8. using DarkTonic.MasterAudio;
    9. using UnityEngine;
    10. public class FootstepSoundsUnit:MonoBehaviour
    11. {
    12. public enum SoundSpawnLocationMode {
    13. MasterAudioLocation, // 主音频位置:任何声音组都将从主音频的位置发出
    14. CallerLocation, // 呼叫者位置:这将从该游戏对象的位置触发声音组
    15. AttachToCaller // 附加到呼叫者:默认值。这实际上不会重新设置Variation游戏对象,但它将遵循具有 Event Sounds 脚本的游戏对象的位置。这样,当物体消失或被场景更改破坏时,声音不会被切断或变化对象被破坏。
    16. }
    17. public enum FootstepTriggerMode {
    18. None,
    19. OnCollisionEnter,
    20. OnCollisionExit,
    21. OnTriggerEnter,
    22. OnTriggerExit,
    23. OnCollision2D,
    24. OnTriggerEnter2D
    25. }
    26. [Header("声音生成模式")]
    27. public SoundSpawnLocationMode soundSpawnMode = SoundSpawnLocationMode.CallerLocation;
    28. [Header("脚步声触发事件")]
    29. public FootstepTriggerMode footstepEvent = FootstepTriggerMode.OnTriggerEnter;
    30. [Header("限制触发间距模式")]
    31. public EventSounds.RetriggerLimMode retriggerLimitMode = EventSounds.RetriggerLimMode.FrameBased;
    32. [Header("显示帧数")]
    33. public int limitPerXFrm = 10000;
    34. [Header("限制秒数")]
    35. public float limitPerXSec = 0f;
    36. [HideInInspector]
    37. public int triggeredLastFrame = -100;
    38. [HideInInspector]
    39. public float triggeredLastTime = -100f;
    40. // ReSharper restore InconsistentNaming
    41. [HideInInspector,Header("使用层筛选")]
    42. public bool useLayerFilter = true;
    43. [HideInInspector,Header("使用标签筛选")] // 暂不使用
    44. public bool userTagFilter = false;
    45. [Header("脚步声音频")]
    46. public List footstepAudioInfoList = new List();
    47. private Transform _trans;
    48. private void Start()
    49. {
    50. }
    51. private double t = 0;
    52. // ReSharper disable once UnusedMember.Local
    53. private void OnTriggerEnter(Collider other) {
    54. if (footstepEvent != FootstepTriggerMode.OnTriggerEnter) {
    55. return;
    56. }
    57. PlaySoundsIfMatch(other.gameObject);
    58. }
    59. private void OnTriggerExit(Collider other)
    60. {
    61. if (footstepEvent != FootstepTriggerMode.OnTriggerExit) {
    62. return;
    63. }
    64. PlaySoundsIfMatch(other.gameObject);
    65. }
    66. // ReSharper disable once UnusedMember.Local
    67. private void OnCollisionEnter(Collision collision) {
    68. if (footstepEvent != FootstepTriggerMode.OnCollisionEnter) {
    69. return;
    70. }
    71. PlaySoundsIfMatch(collision.gameObject);
    72. }
    73. private void OnCollisionExit(Collision collision)
    74. {
    75. if (footstepEvent != FootstepTriggerMode.OnCollisionExit) {
    76. return;
    77. }
    78. PlaySoundsIfMatch(collision.gameObject);
    79. }
    80. // ReSharper disable once UnusedMember.Local
    81. private void OnCollisionEnter2D(Collision2D collision) {
    82. if (footstepEvent != FootstepTriggerMode.OnCollision2D) {
    83. return;
    84. }
    85. PlaySoundsIfMatch(collision.gameObject);
    86. }
    87. // ReSharper disable once UnusedMember.Local
    88. private void OnTriggerEnter2D(Collider2D other) {
    89. if (footstepEvent != FootstepTriggerMode.OnTriggerEnter2D) {
    90. return;
    91. }
    92. PlaySoundsIfMatch(other.gameObject);
    93. }
    94. private bool CheckForRetriggerLimit() {
    95. // check for limiting restraints
    96. switch (retriggerLimitMode) {
    97. case EventSounds.RetriggerLimMode.FrameBased:
    98. if (triggeredLastFrame > 0 && AudioUtil.FrameCount - triggeredLastFrame < limitPerXFrm) {
    99. return false;
    100. }
    101. break;
    102. case EventSounds.RetriggerLimMode.TimeBased:
    103. if (triggeredLastTime > 0 && AudioUtil.Time - triggeredLastTime < limitPerXSec) {
    104. return false;
    105. }
    106. break;
    107. }
    108. return true;
    109. }
    110. private void PlaySoundsIfMatch(GameObject go) {
    111. if (!CheckForRetriggerLimit()) {
    112. return;
    113. }
    114. // set the last triggered time or frame
    115. switch (retriggerLimitMode) {
    116. case EventSounds.RetriggerLimMode.FrameBased:
    117. triggeredLastFrame = AudioUtil.FrameCount;
    118. break;
    119. case EventSounds.RetriggerLimMode.TimeBased:
    120. triggeredLastTime = AudioUtil.Time;
    121. break;
    122. }
    123. // ReSharper disable once ForCanBeConvertedToForeach
    124. for (var i = 0; i < footstepAudioInfoList.Count; i++) {
    125. var aGroup = footstepAudioInfoList[i];
    126. // check filters for matches if turned on
    127. if (useLayerFilter && !aGroup.layerMatchs.Contains(go.layer)) {
    128. continue;
    129. }
    130. if (userTagFilter && !aGroup.tagMatchs.Contains(go.tag)) {
    131. continue;
    132. }
    133. var volume = aGroup.volume;
    134. float pitch = aGroup.pitch;
    135. switch (soundSpawnMode) {
    136. case SoundSpawnLocationMode.CallerLocation:
    137. AudioSystem.Instance.PlaySound3DAtTransform(AudioType.Footstep,aGroup.path,Trans,volume,false,pitch);
    138. break;
    139. case SoundSpawnLocationMode.AttachToCaller:
    140. AudioSystem.Instance.PlaySound3DFollowTransform(AudioType.Footstep,aGroup.path,Trans,volume,false,pitch);
    141. break;
    142. case SoundSpawnLocationMode.MasterAudioLocation:
    143. AudioSystem.Instance.PlayAudio(AudioType.Footstep,aGroup.path,volume,false);
    144. break;
    145. }
    146. }
    147. }
    148. private Transform Trans {
    149. get {
    150. if (_trans != null) {
    151. return _trans;
    152. }
    153. _trans = transform;
    154. return _trans;
    155. }
    156. }
    157. }
    158. [Serializable]
    159. public class FootStepAudioInfo
    160. {
    161. [HideInInspector]
    162. public string name = "";
    163. public AudioClip clip = null;
    164. [Tooltip("路径")]
    165. public string path = "";
    166. [Tooltip("音量"),Range(0,1)]
    167. public float volume = 1;
    168. [Tooltip("音高"),Range(0,3)]
    169. public float pitch = 1;
    170. [Header("层筛选"), LayerPropertyAttribute]
    171. public List<int> layerMatchs = null;
    172. [HideInInspector,Header("标签筛选"), TagPropertyAttribute]
    173. public List<string> tagMatchs = null;
    174. public void Reset()
    175. {
    176. name = string.Empty;
    177. clip = null;
    178. path = "";
    179. volume = 1;
    180. pitch = 1;
    181. layerMatchs = null;
    182. tagMatchs = null;
    183. }
    184. }
    185. public class LayerPropertyAttribute:PropertyAttribute {}
    186. public class TagPropertyAttribute:PropertyAttribute {}

    Editor文件夹下的编辑器脚本:

    FootstepSoundsUnitEditor:
    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using DarkTonic.MasterAudio;
    5. using UnityEditor;
    6. [CustomEditor(typeof(FootstepSoundsUnit))]
    7. public class FootstepSoundsUnitEditor:Editor
    8. {
    9. private List footStepAudioInfoList;
    10. private void Awake()
    11. {
    12. FootstepSoundsUnit config = target as FootstepSoundsUnit;
    13. footStepAudioInfoList = config.footstepAudioInfoList;
    14. }
    15. public override void OnInspectorGUI()
    16. {
    17. for (int i = 0; i < footStepAudioInfoList.Count; i++)
    18. {
    19. var config = footStepAudioInfoList[i];
    20. if (config.clip == null)
    21. {
    22. config.Reset();
    23. }
    24. string path = config.clip == null ? string.Empty : AssetDatabase.GetAssetPath(config.clip);
    25. config.path = path == string.Empty ? string.Empty : GetAudioPath(path);
    26. config.name = path == string.Empty ? string.Empty : Path.GetFileNameWithoutExtension(path);
    27. }
    28. base.OnInspectorGUI();
    29. }
    30. private string GetAudioPath(string path)
    31. {
    32. string _path = path.Replace(Path.GetExtension(path), String.Empty);
    33. return _path.Replace("Assets/Resources/", string.Empty);
    34. }
    35. }
    1. using UnityEditor;
    2. using UnityEngine;
    3. [CustomPropertyDrawer(typeof(LayerPropertyAttribute))]
    4. public class LayerPropertyDrawer : PropertyDrawer
    5. {
    6. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    7. {
    8. // LayerPropertyAttribute info = base.attribute as LayerPropertyAttribute;
    9. property.intValue = EditorGUI.LayerField(position,"layer Match ", property.intValue);
    10. }
    11. }
    12. [CustomPropertyDrawer(typeof(TagPropertyAttribute))]
    13. public class TagPropertyDrawer : PropertyDrawer
    14. {
    15. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    16. {
    17. // TagPropertyAttribute info = base.attribute as TagPropertyAttribute;
    18. property.stringValue = EditorGUI.TagField(position,"tag Match ", property.stringValue);
    19. }
    20. }

  • 相关阅读:
    【vue2 vuex】store state mutations actions状态管理(草稿一留存)
    实战PyQt5: 141-QChart图表之箱形图
    LeetCode 705. Design HashSet
    【QT】Windows 编译并使用 QT 5.12.7源码
    Latex 安装与配置
    【Python之正则表达式与JSON】
    在乡村沃土上,架起一座直播间
    【算法练习Day12】树的递归遍历&&非递归遍历
    django configparser.NoSectionError: No section: ‘Samples
    移动端布局之flex布局3:案例-携程网首页案例制作(曾经的版本)2
  • 原文地址:https://blog.csdn.net/weixin_41316824/article/details/127805050