using UnityEngine; using System.Collections.Generic; using Cysharp.Threading.Tasks; /// /// 战斗音效管理器 /// 职责:播放战斗中的短促技能动作和特效音效 /// public class BattleSoundManager { private BattleField battleField; private GameObject audioSourceObject; // 音效池配置 private const int INITIAL_AUDIO_POOL = 15; // 初始池大小 private const int MAX_AUDIO_SOURCES = 30; // AudioSource 总数上限 private Queue audioSourcePool = new Queue(); private List activeAudioSources = new List(); // 同一音效同时播放的最大数量 private const int MAX_SAME_AUDIO_COUNT = 3; // 记录每个音效ID当前播放的AudioSource private Dictionary> audioIdToSources = new Dictionary>(); // 清理用缓存列表,避免每帧分配 private List _keysToRemoveCache = new List(); // 音频剪辑缓存 private Dictionary audioClipCache = new Dictionary(); // 当前播放速度 private float currentSpeedRatio = 1f; // 跟踪AudioSource总数,避免GetComponents分配 private int audioSourceCount = 0; // 是否有焦点 private bool hasFocus = true; public BattleSoundManager(BattleField _battleField) { battleField = _battleField; Debug.Log($"BattleSoundManager [{battleField.guid}]: 构造函数 - 初始焦点状态={battleField.IsFocus()}"); InitializeAudioSources(); // 监听战场速度变化 if (battleField != null) { battleField.OnSpeedRatioChange -= OnSpeedRatioChanged; battleField.OnSpeedRatioChange += OnSpeedRatioChanged; battleField.OnFocusChange -= OnFocusChanged; battleField.OnFocusChange += OnFocusChanged; currentSpeedRatio = battleField.speedRatio; hasFocus = battleField.IsFocus(); } #if UNITY_EDITOR // 监听编辑器暂停事件 UnityEditor.EditorApplication.pauseStateChanged -= OnEditorPauseStateChanged; UnityEditor.EditorApplication.pauseStateChanged += OnEditorPauseStateChanged; #endif } public void Init() { EventBroadcast.Instance.AddListener(EventName.SOUND_EFFECT_MUTE_CHANGE, OnSoundEffectMuteChange); EventBroadcast.Instance.AddListener(EventName.SOUND_EFFECT_VOLUME_CHANGE, OnSoundEffectVolumeChange); } #if UNITY_EDITOR /// /// 编辑器暂停状态变化回调 /// private void OnEditorPauseStateChanged(UnityEditor.PauseState pauseState) { if (pauseState == UnityEditor.PauseState.Paused) { Debug.Log($"BattleSoundManager [{battleField.guid}]: 编辑器暂停,停止所有音效"); for (int i = activeAudioSources.Count - 1; i >= 0; i--) { var source = activeAudioSources[i]; if (source != null) { source.Stop(); } } } else if (pauseState == UnityEditor.PauseState.Unpaused) { Debug.Log($"BattleSoundManager [{battleField.guid}]: 编辑器恢复"); for (int i = activeAudioSources.Count - 1; i >= 0; i--) { var source = activeAudioSources[i]; if (source != null) { source.Play(); } } } } #endif /// /// 初始化音频源池 /// private void InitializeAudioSources() { // 使用战场根节点作为 AudioSource 的载体 audioSourceObject = battleField.battleRootNode.gameObject; // 创建初始音频源池 for (int i = 0; i < INITIAL_AUDIO_POOL; i++) { var source = audioSourceObject.AddComponent(); source.playOnAwake = false; source.loop = false; source.spatialBlend = 0f; // 2D音效 audioSourcePool.Enqueue(source); } audioSourceCount = INITIAL_AUDIO_POOL; Debug.Log($"BattleSoundManager [{battleField.guid}]: 初始化了 {INITIAL_AUDIO_POOL} 个 AudioSource"); } /// /// 每帧运行,清理已完成的音频源 /// public void Run() { // 每帧清理,性能开销很小 CleanupFinishedAudioSources(); } /// /// 播放特效音效 /// /// 音效ID public async UniTask PlayEffectSound(int audioId, bool pitchControl = true) { if (audioId <= 0) { return; } await PlaySound(audioId, pitchControl); } /// /// 核心播放方法 /// private async UniTask PlaySound(int audioId, bool pitchControl) { // 检查是否有焦点,无焦点时不播放 if (!hasFocus) { #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 无焦点,拒绝播放音效 {audioId}"); #endif return; } // 检查该音效是否已达到播放上限 if (!CanPlayAudio(audioId)) { #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 音效 {audioId} 达到播放上限,拒绝播放"); #endif return; } var audioClip = await GetAudioClip(audioId); if (audioClip == null) { #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 无法加载音效 {audioId}"); #endif return; } // 从池中获取可用的音频源 AudioSource source = GetAvailableAudioSource(); if (source == null) { #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 无法获取AudioSource,池数量={audioSourcePool.Count},活跃数量={activeAudioSources.Count}"); #endif return; } // 设置音量(使用音效音量设置) source.volume = SystemSetting.Instance.GetSoundEffect(); source.mute = SystemSetting.Instance.GetMuteSoundEffect(); // 设置播放速度,使用pitch来控制 // pitch范围建议在0.5-2.0之间以避免失真 if (pitchControl) { float pitch = Mathf.Clamp(currentSpeedRatio, 0.5f, 2.0f); source.pitch = pitch; } else { source.pitch = 1.0f; } // 设置音频剪辑并播放(使用 Play() 而不是 PlayOneShot(),以便 isPlaying 状态正确) source.clip = audioClip; source.Play(); #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 播放音效 {audioId} - {audioClip.name}"); #endif // 标记为活跃 if (!activeAudioSources.Contains(source)) { activeAudioSources.Add(source); } // 记录该音效ID与AudioSource的关联 if (!audioIdToSources.ContainsKey(audioId)) { audioIdToSources[audioId] = new List(); } audioIdToSources[audioId].Add(source); } /// /// 获取音频剪辑(每次从ResManager加载,依赖其内部缓存) /// private async UniTask GetAudioClip(int audioId) { // 不在此处缓存,直接从 ResManager 加载 // ResManager 内部会处理缓存,这样可以避免 AudioClip 失效问题 return await LoadAudioClipAsync(audioId); } /// /// 检查是否可以播放该音效 /// private bool CanPlayAudio(int audioId) { if (!audioIdToSources.ContainsKey(audioId)) { return true; } // 过滤掉已经停止播放的AudioSource var sources = audioIdToSources[audioId]; sources.RemoveAll(s => s == null || !s.isPlaying); // 检查同时播放的数量 return sources.Count < MAX_SAME_AUDIO_COUNT; } /// /// US2: Async audio clip loading. /// private async UniTask LoadAudioClipAsync(int audioId) { var config = AudioConfig.Get(audioId); if (config == null) return null; return await ResManager.Instance.LoadAssetAsync( "Audio/" + config.Folder, config.Audio, false ); } /// /// 获取可用的音频源 /// private AudioSource GetAvailableAudioSource() { // 检查 audioSourceObject 是否仍然有效(Unity 对象可能被销毁但引用不为 null) if (audioSourceObject == null || !audioSourceObject) { Debug.Log($"BattleSoundManager [{battleField.guid}]: audioSourceObject 已被销毁或无效!尝试重新获取"); // 尝试重新获取 if (battleField != null && battleField.battleRootNode != null) { audioSourceObject = battleField.battleRootNode.gameObject; Debug.Log($"BattleSoundManager [{battleField.guid}]: 重新获取 audioSourceObject 成功"); } else { Debug.Log("BattleSoundManager: 无法重新获取 audioSourceObject"); return null; } } // 尝试从池中获取 while (audioSourcePool.Count > 0) { var source = audioSourcePool.Dequeue(); // 检查 AudioSource 是否仍然有效(真机上可能被销毁) if (source != null) { return source; } #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 池中的AudioSource已被销毁,跳过"); #endif } // 计算当前总的 AudioSource 数量 if (audioSourceCount >= MAX_AUDIO_SOURCES) { // 达到上限,不再创建,丢弃这次播放请求 #if UNITY_EDITOR Debug.Log($"BattleSoundManager: AudioSource 数量已达上限 {MAX_AUDIO_SOURCES},无法播放新音效"); #endif return null; } // 在 battleRootNode 上动态创建新的 AudioSource var newSource = audioSourceObject.AddComponent(); newSource.playOnAwake = false; newSource.loop = false; newSource.spatialBlend = 0f; // 2D音效 audioSourceCount++; #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: 动态创建新AudioSource,当前总数={audioSourceCount}"); #endif return newSource; } /// /// 清理已播放完成的音频源 /// private void CleanupFinishedAudioSources() { // 清理 activeAudioSources 列表 for (int i = activeAudioSources.Count - 1; i >= 0; i--) { var source = activeAudioSources[i]; if (source == null || !source.isPlaying) { activeAudioSources.RemoveAt(i); if (source != null) { // 清空 clip 引用,释放内存 source.Stop(); source.clip = null; audioSourcePool.Enqueue(source); } } } // 清理 audioIdToSources 中已停止播放的 AudioSource _keysToRemoveCache.Clear(); foreach (var kvp in audioIdToSources) { kvp.Value.RemoveAll(s => s == null || !s.isPlaying); if (kvp.Value.Count == 0) { _keysToRemoveCache.Add(kvp.Key); } } // 移除空的音效ID记录 foreach (var key in _keysToRemoveCache) { audioIdToSources.Remove(key); } } /// /// 战场速度变化回调 /// private void OnSpeedRatioChanged(float newSpeedRatio) { currentSpeedRatio = newSpeedRatio; // 更新所有正在播放的音效的速度 foreach (var source in activeAudioSources) { if (source != null && source.isPlaying) { float pitch = Mathf.Clamp(newSpeedRatio, 0.5f, 2.0f); source.pitch = pitch; } } } private void OnSoundEffectMuteChange(bool isMute) { // Debug.Log($"BattleSoundManager [{battleField.guid}]: OnSoundEffectMuteChange({isMute}) - 当前活跃音效数={activeAudioSources.Count}"); // 更新所有正在播放的音效的静音状态 foreach (var source in activeAudioSources) { if (source != null) { source.mute = isMute; } } } private void OnSoundEffectVolumeChange(float volume) { // Debug.Log($"BattleSoundManager [{battleField.guid}]: OnSoundEffectVolumeChange({volume}) - 当前活跃音效数={activeAudioSources.Count}"); // 更新所有正在播放的音效的音量 foreach (var source in activeAudioSources) { if (source != null) { source.volume = volume; } } } /// /// 焦点变化回调 /// private void OnFocusChanged(bool isFocus) { Debug.Log($"BattleSoundManager [{battleField.guid}]: OnFocusChanged({isFocus}) - 当前活跃音效数={activeAudioSources.Count}"); hasFocus = isFocus; // 失去焦点时,停止所有正在播放的音效 if (!hasFocus) { Debug.Log($"BattleSoundManager [{battleField.guid}]: 失去焦点,准备停止所有音效"); StopAllSounds(); } else { Debug.Log($"BattleSoundManager [{battleField.guid}]: 获得焦点"); } } /// /// 停止所有音效 /// public void StopAllSounds() { int activeCount = activeAudioSources.Count; int totalStopped = 0; AudioSource[] allSources = null; // 不依赖列表,直接停止 GameObject 上的所有 AudioSource if (audioSourceObject != null) { allSources = audioSourceObject.GetComponents(); #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: StopAllSounds - GameObject上共有 {allSources.Length} 个AudioSource"); #endif foreach (var source in allSources) { if (source != null) { if (source.isPlaying) { #if UNITY_EDITOR Debug.Log($" 停止正在播放的: {source.clip?.name}"); #endif totalStopped++; } source.Stop(); source.clip = null; } } } #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: StopAllSounds - 活跃列表={activeCount}, 实际停止={totalStopped}"); #endif // 清空所有列表 activeAudioSources.Clear(); audioIdToSources.Clear(); // 重建对象池(复用上面已获取的数组) audioSourcePool.Clear(); if (allSources != null) { foreach (var source in allSources) { if (source != null) { audioSourcePool.Enqueue(source); } } } #if UNITY_EDITOR Debug.Log($"BattleSoundManager [{battleField.guid}]: StopAllSounds 完成"); #endif } /// /// 设置音量 /// public void SetVolume(float volume) { volume = Mathf.Clamp01(volume); foreach (var source in activeAudioSources) { if (source != null) { source.volume = volume; } } } /// /// 释放资源 /// public void Release() { Debug.Log($"BattleSoundManager [{battleField.guid}]: Release 开始"); if (battleField != null) { battleField.OnSpeedRatioChange -= OnSpeedRatioChanged; battleField.OnFocusChange -= OnFocusChanged; } EventBroadcast.Instance.RemoveListener(EventName.SOUND_EFFECT_MUTE_CHANGE, OnSoundEffectMuteChange); EventBroadcast.Instance.RemoveListener(EventName.SOUND_EFFECT_VOLUME_CHANGE, OnSoundEffectVolumeChange); #if UNITY_EDITOR // 取消订阅编辑器暂停事件 UnityEditor.EditorApplication.pauseStateChanged -= OnEditorPauseStateChanged; #endif StopAllSounds(); // 销毁所有 AudioSource 组件 if (audioSourceObject != null) { var sources = audioSourceObject.GetComponents(); Debug.Log($"BattleSoundManager [{battleField.guid}]: 销毁 {sources.Length} 个 AudioSource 组件"); foreach (var source in sources) { if (source != null) { GameObject.Destroy(source); } } audioSourceObject = null; } audioSourcePool.Clear(); activeAudioSources.Clear(); audioIdToSources.Clear(); audioClipCache.Clear(); Debug.Log($"BattleSoundManager [{battleField.guid}]: Release 完成"); } }