using UnityEngine; using System.Collections.Generic; /// /// 战斗音效管理器 /// 职责:播放战斗中的短促技能动作和特效音效 /// 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 Dictionary audioClipCache = new Dictionary(); // 当前播放速度 private float currentSpeedRatio = 1f; // 是否有焦点 private bool hasFocus = true; public BattleSoundManager(BattleField _battleField) { battleField = _battleField; BattleDebug.LogError($"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(); } } /// /// 初始化音频源池 /// 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); } BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: 初始化了 {INITIAL_AUDIO_POOL} 个 AudioSource"); } /// /// 每帧运行,清理已完成的音频源 /// public void Run() { // 每帧清理,性能开销很小 CleanupFinishedAudioSources(); } /// /// 播放特效音效 /// /// 音效ID public void PlayEffectSound(int audioId, bool pitchControl = true) { if (audioId <= 0) { return; } PlaySound(audioId, pitchControl); } /// /// 核心播放方法 /// private void PlaySound(int audioId, bool pitchControl) { // 检查是否有焦点,无焦点时不播放 if (!hasFocus) { BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: 无焦点,拒绝播放音效 {audioId}"); return; } // 检查该音效是否已达到播放上限 if (!CanPlayAudio(audioId)) { return; } var audioClip = GetAudioClip(audioId); if (audioClip == null) { return; } // 从池中获取可用的音频源 AudioSource source = GetAvailableAudioSource(); if (source == null) { return; } // 设置音量(使用音效音量设置) source.volume = SystemSetting.Instance.GetSoundEffect(); // 设置播放速度,使用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(); BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: 播放音效 {audioId} - {audioClip.name}"); // 标记为活跃 if (!activeAudioSources.Contains(source)) { activeAudioSources.Add(source); } // 记录该音效ID与AudioSource的关联 if (!audioIdToSources.ContainsKey(audioId)) { audioIdToSources[audioId] = new List(); } audioIdToSources[audioId].Add(source); } /// /// 获取音频剪辑(优先从缓存获取) /// private AudioClip GetAudioClip(int audioId) { // 先从缓存中查找 if (audioClipCache.TryGetValue(audioId, out AudioClip cachedClip)) { return cachedClip; } // 缓存中没有,则加载并缓存 var audioClip = LoadAudioClip(audioId); if (audioClip != null) { audioClipCache[audioId] = audioClip; } return audioClip; } /// /// 检查是否可以播放该音效 /// 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; } /// /// 加载音频剪辑 /// private AudioClip LoadAudioClip(int audioId) { var config = AudioConfig.Get(audioId); if (config == null) return null; AudioClip audioClip = ResManager.Instance.LoadAsset( "Audio/" + config.Folder, config.Audio, false ); return audioClip; } /// /// 获取可用的音频源 /// private AudioSource GetAvailableAudioSource() { // 尝试从池中获取 if (audioSourcePool.Count > 0) { return audioSourcePool.Dequeue(); } // 如果池为空,检查是否可以动态创建 if (audioSourceObject == null) { BattleDebug.LogErrorError("BattleSoundManager: audioSourceObject 为空,无法创建 AudioSource"); return null; } // 计算当前总的 AudioSource 数量 int totalAudioSources = audioSourceObject.GetComponents().Length; if (totalAudioSources >= MAX_AUDIO_SOURCES) { // 达到上限,不再创建,丢弃这次播放请求 BattleDebug.LogErrorWarning($"BattleSoundManager: AudioSource 数量已达上限 {MAX_AUDIO_SOURCES},无法播放新音效"); return null; } // 在 battleRootNode 上动态创建新的 AudioSource var source = audioSourceObject.AddComponent(); source.playOnAwake = false; source.loop = false; source.spatialBlend = 0f; // 2D音效 return source; } /// /// 清理已播放完成的音频源 /// 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 var keysToRemove = new List(); foreach (var kvp in audioIdToSources) { kvp.Value.RemoveAll(s => s == null || !s.isPlaying); if (kvp.Value.Count == 0) { keysToRemove.Add(kvp.Key); } } // 移除空的音效ID记录 foreach (var key in keysToRemove) { 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 OnFocusChanged(bool isFocus) { BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: OnFocusChanged({isFocus}) - 当前活跃音效数={activeAudioSources.Count}"); hasFocus = isFocus; // 失去焦点时,停止所有正在播放的音效 if (!hasFocus) { BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: 失去焦点,准备停止所有音效"); StopAllSounds(); } else { BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: 获得焦点"); } } /// /// 停止所有音效 /// public void StopAllSounds() { int activeCount = activeAudioSources.Count; int totalStopped = 0; // 不依赖列表,直接停止 GameObject 上的所有 AudioSource if (audioSourceObject != null) { var allSources = audioSourceObject.GetComponents(); BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: StopAllSounds - GameObject上共有 {allSources.Length} 个AudioSource"); foreach (var source in allSources) { if (source != null) { if (source.isPlaying) { BattleDebug.LogError($" 停止正在播放的: {source.clip?.name}"); totalStopped++; } source.Stop(); source.clip = null; } } } BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: StopAllSounds - 活跃列表={activeCount}, 实际停止={totalStopped}"); // 清空所有列表 activeAudioSources.Clear(); audioIdToSources.Clear(); // 重建对象池 audioSourcePool.Clear(); if (audioSourceObject != null) { var allSources = audioSourceObject.GetComponents(); foreach (var source in allSources) { if (source != null) { audioSourcePool.Enqueue(source); } } } BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: StopAllSounds 完成"); } /// /// 设置音量 /// public void SetVolume(float volume) { volume = Mathf.Clamp01(volume); foreach (var source in activeAudioSources) { if (source != null) { source.volume = volume; } } } /// /// 释放资源 /// public void Release() { BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: Release 开始"); if (battleField != null) { battleField.OnSpeedRatioChange -= OnSpeedRatioChanged; battleField.OnFocusChange -= OnFocusChanged; } StopAllSounds(); // 销毁所有 AudioSource 组件 if (audioSourceObject != null) { var sources = audioSourceObject.GetComponents(); BattleDebug.LogError($"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(); BattleDebug.LogError($"BattleSoundManager [{battleField.guid}]: Release 完成"); } }