Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / manager / SoundManager.cs
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Config;
  5. using Godot;
  6.  
  7. /// <summary>
  8. /// 音频总线 区分不同的声音 添加声音效果 目前只有背景音乐 和音效 两个bus
  9. /// </summary>
  10. public enum BUS
  11. {
  12. BGM = 0,
  13. SFX = 1
  14. }
  15.  
  16. /// <summary>
  17. /// 声音管理 背景音乐管理 音效
  18. /// </summary>
  19. public partial class SoundManager
  20. {
  21. /// <summary>
  22. /// 全局音效音量, 范围 0 - 1
  23. /// </summary>
  24. public static float SoundVolume { get; set; } = 0.4f;
  25. private static Stack<GameAudioPlayer2D> _streamPlayer2DStack = new Stack<GameAudioPlayer2D>();
  26. private static Stack<GameAudioPlayer> _streamPlayerStack = new Stack<GameAudioPlayer>();
  27. private static HashSet<string> _playingSoundResourceList = new HashSet<string>();
  28.  
  29. /// <summary>
  30. /// 2D音频播放节点
  31. /// </summary>
  32. public partial class GameAudioPlayer2D : AudioStreamPlayer2D
  33. {
  34. public override void _Ready()
  35. {
  36. Finished += OnPlayFinish;
  37. }
  38.  
  39. public void PlaySoundByResource(string path, float delayTime)
  40. {
  41. if (delayTime <= 0)
  42. {
  43. PlaySoundByResource(path);
  44. }
  45. else
  46. {
  47. GameApplication.Instance.StartCoroutine(DelayPlay(path, delayTime));
  48. }
  49. }
  50.  
  51. public void PlaySoundByResource(string path)
  52. {
  53. if (_playingSoundResourceList.Contains(path))
  54. {
  55. Debug.Log("重复播放: " + path);
  56. }
  57. else
  58. {
  59. _playingSoundResourceList.Add(path);
  60. var sound = ResourceManager.Load<AudioStream>(path);
  61. Stream = sound;
  62. Bus = Enum.GetName(typeof(BUS), 1);
  63. Play();
  64. }
  65. }
  66.  
  67. /// <summary>
  68. /// 停止播放, 并回收节点
  69. /// </summary>
  70. public void StopPlay()
  71. {
  72. Stop();
  73. OnPlayFinish();
  74. }
  75.  
  76. private void OnPlayFinish()
  77. {
  78. RecycleAudioPlayer2D(this);
  79. }
  80.  
  81. private IEnumerator DelayPlay(string path, float delayTime)
  82. {
  83. yield return new WaitForSeconds(delayTime);
  84. PlaySoundByResource(path);
  85. }
  86. }
  87.  
  88. /// <summary>
  89. /// 音频播放节点
  90. /// </summary>
  91. public partial class GameAudioPlayer : AudioStreamPlayer
  92. {
  93. public string SoundResourceName { get; set; }
  94. public override void _Ready()
  95. {
  96. Finished += OnPlayFinish;
  97. }
  98.  
  99. /// <summary>
  100. /// 停止播放, 并回收节点
  101. /// </summary>
  102. public void StopPlay()
  103. {
  104. Stop();
  105. OnPlayFinish();
  106. }
  107. private void OnPlayFinish()
  108. {
  109. GetParent().RemoveChild(this);
  110. Stream = null;
  111. Playing = false;
  112. RecycleAudioPlayer(this);
  113. }
  114. }
  115.  
  116. public static void Update(float delta)
  117. {
  118. _playingSoundResourceList.Clear();
  119. }
  120. /// <summary>
  121. /// 播放声音 用于bgm
  122. /// </summary>
  123. /// <param name="soundName">bgm路径</param>
  124. /// <param name="volume">音量</param>
  125. public static GameAudioPlayer PlayMusic(string soundName, float volume = 0.5f)
  126. {
  127. var sound = ResourceManager.Load<AudioStream>(soundName);
  128. var soundNode = GetAudioPlayerInstance();
  129. GameApplication.Instance.GlobalNodeRoot.AddChild(soundNode);
  130. soundNode.Stream = sound;
  131. soundNode.Bus = Enum.GetName(typeof(BUS), 0);
  132. soundNode.VolumeDb = volume;
  133. soundNode.Play();
  134. return soundNode;
  135. }
  136.  
  137. /// <summary>
  138. /// 添加并播放音效 用于音效
  139. /// </summary>
  140. /// <param name="soundName">音效文件路径</param>
  141. /// <param name="volume">音量 (0 - 1)</param>
  142. public static GameAudioPlayer PlaySoundEffect(string soundName, float volume = 1f)
  143. {
  144. var sound = ResourceManager.Load<AudioStream>(soundName);
  145. var soundNode = GetAudioPlayerInstance();
  146. GameApplication.Instance.GlobalNodeRoot.AddChild(soundNode);
  147. soundNode.Stream = sound;
  148. soundNode.Bus = Enum.GetName(typeof(BUS), 1);
  149. soundNode.VolumeDb = Mathf.LinearToDb(Mathf.Clamp(volume, 0, 1));
  150. soundNode.Play();
  151. return soundNode;
  152. }
  153.  
  154. /// <summary>
  155. /// 在指定的节点下播放音效 用于音效
  156. /// </summary>
  157. /// <param name="soundName">音效文件路径</param>
  158. /// <param name="pos">发声节点所在全局坐标</param>
  159. /// <param name="volume">音量 (0 - 1)</param>
  160. /// <param name="target">挂载节点, 为null则挂载到房间根节点下</param>
  161. public static GameAudioPlayer2D PlaySoundEffectPosition(string soundName, Vector2 pos, float volume = 1f, Node2D target = null)
  162. {
  163. return PlaySoundEffectPositionDelay(soundName, pos, 0, volume, target);
  164. }
  165.  
  166. /// <summary>
  167. /// 在指定的节点下延时播放音效 用于音效
  168. /// </summary>
  169. /// <param name="soundName">音效文件路径</param>
  170. /// <param name="pos">发声节点所在全局坐标</param>
  171. /// <param name="delayTime">延时时间</param>
  172. /// <param name="volume">音量 (0 - 1)</param>
  173. /// <param name="target">挂载节点, 为null则挂载到房间根节点下</param>
  174. public static GameAudioPlayer2D PlaySoundEffectPositionDelay(string soundName, Vector2 pos, float delayTime, float volume = 1f, Node2D target = null)
  175. {
  176. var soundNode = GetAudioPlayer2DInstance();
  177. if (target != null)
  178. {
  179. target.AddChild(soundNode);
  180. }
  181. else
  182. {
  183. GameApplication.Instance.GlobalNodeRoot.AddChild(soundNode);
  184. }
  185. soundNode.GlobalPosition = pos;
  186. soundNode.VolumeDb = Mathf.LinearToDb(Mathf.Clamp(volume * SoundVolume, 0, 1));
  187. soundNode.PlaySoundByResource(soundName, delayTime);
  188. return soundNode;
  189. }
  190. /// <summary>
  191. /// 根据音效配置表Id播放音效
  192. /// </summary>
  193. /// <param name="id">音效Id</param>
  194. /// <param name="viewPosition">播放音效的位置, 该位置为 SubViewport 下的坐标, 也就是 <see cref="ActivityObject"/> 使用的坐标</param>
  195. /// <param name="triggerRole">触发播放音效的角色, 因为 Npc 产生的音效声音更小, 可以传 null</param>
  196. public static GameAudioPlayer2D PlaySoundByConfig(string id, Vector2 viewPosition, Role triggerRole = null)
  197. {
  198. var sound = ExcelConfig.Sound_Map[id];
  199. return PlaySoundByConfig(sound, viewPosition, triggerRole);
  200. }
  201.  
  202. /// <summary>
  203. /// 根据音效配置播放音效
  204. /// </summary>
  205. /// <param name="sound">音效数据</param>
  206. /// <param name="viewPosition">播放音效的位置, 该位置为 SubViewport 下的坐标, 也就是 <see cref="ActivityObject"/> 使用的坐标</param>
  207. /// <param name="triggerRole">触发播放音效的角色, 因为 Npc 产生的音效声音更小, 可以传 null</param>
  208. public static GameAudioPlayer2D PlaySoundByConfig(ExcelConfig.Sound sound, Vector2 viewPosition, Role triggerRole = null)
  209. {
  210. return PlaySoundEffectPosition(
  211. sound.Path,
  212. GameApplication.Instance.ViewToGlobalPosition(viewPosition),
  213. CalcRoleVolume(sound.Volume, triggerRole)
  214. );
  215. }
  216.  
  217. /// <summary>
  218. /// 根据音效配置表Id延时播放音效
  219. /// </summary>
  220. /// <param name="id">音效Id</param>
  221. /// <param name="viewPosition">播放音效的位置, 该位置为 SubViewport 下的坐标, 也就是 <see cref="ActivityObject"/> 使用的坐标</param>
  222. /// <param name="delayTime">延时时间</param>
  223. /// <param name="triggerRole">触发播放音效的角色, 因为 Npc 产生的音效声音更小, 可以传 null</param>
  224. public static GameAudioPlayer2D PlaySoundByConfigDelay(string id, Vector2 viewPosition, float delayTime, Role triggerRole = null)
  225. {
  226. var sound = ExcelConfig.Sound_Map[id];
  227. return PlaySoundByConfigDelay(sound, viewPosition, delayTime, triggerRole);
  228. }
  229. /// <summary>
  230. /// 根据音效配置延时播放音效
  231. /// </summary>
  232. /// <param name="sound">音效数据</param>
  233. /// <param name="viewPosition">播放音效的位置, 该位置为 SubViewport 下的坐标, 也就是 <see cref="ActivityObject"/> 使用的坐标</param>
  234. /// <param name="delayTime">延时时间</param>
  235. /// <param name="triggerRole">触发播放音效的角色, 因为 Npc 产生的音效声音更小, 可以传 null</param>
  236. public static GameAudioPlayer2D PlaySoundByConfigDelay(ExcelConfig.Sound sound, Vector2 viewPosition, float delayTime, Role triggerRole = null)
  237. {
  238. return PlaySoundEffectPositionDelay(
  239. sound.Path,
  240. GameApplication.Instance.ViewToGlobalPosition(viewPosition),
  241. delayTime,
  242. CalcRoleVolume(sound.Volume, triggerRole)
  243. );
  244. }
  245.  
  246. /// <summary>
  247. /// 获取2D音频播放节点
  248. /// </summary>
  249. private static GameAudioPlayer2D GetAudioPlayer2DInstance()
  250. {
  251. if (_streamPlayer2DStack.Count > 0)
  252. {
  253. return _streamPlayer2DStack.Pop();
  254. }
  255.  
  256. var inst = new GameAudioPlayer2D();
  257. inst.AreaMask = 0;
  258. return inst;
  259. }
  260.  
  261. /// <summary>
  262. /// 获取音频播放节点
  263. /// </summary>
  264. private static GameAudioPlayer GetAudioPlayerInstance()
  265. {
  266. if (_streamPlayerStack.Count > 0)
  267. {
  268. return _streamPlayerStack.Pop();
  269. }
  270.  
  271. return new GameAudioPlayer();
  272. }
  273.  
  274. /// <summary>
  275. /// 回收2D音频播放节点
  276. /// </summary>
  277. private static void RecycleAudioPlayer2D(GameAudioPlayer2D inst)
  278. {
  279. var parent = inst.GetParent();
  280. if (parent != null)
  281. {
  282. parent.RemoveChild(inst);
  283. }
  284.  
  285. inst.Stream = null;
  286. _streamPlayer2DStack.Push(inst);
  287. }
  288.  
  289. /// <summary>
  290. /// 回收音频播放节点
  291. /// </summary>
  292. private static void RecycleAudioPlayer(GameAudioPlayer inst)
  293. {
  294. _streamPlayerStack.Push(inst);
  295. }
  296.  
  297. /// <summary>
  298. /// 计算指定角色播放音效使用的音量
  299. /// </summary>
  300. public static float CalcRoleVolume(float volume, Role role)
  301. {
  302. if (role is not Player)
  303. {
  304. return volume * 0.4f;
  305. }
  306.  
  307. return volume;
  308. }
  309. }