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; InitializeAudioSources(); // 监听战场速度变化 if (battleField != null) { battleField.OnSpeedRatioChange += OnSpeedRatioChanged; 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); } } /// /// 每帧运行,清理已完成的音频源 /// 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) { 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; } // 播放音效 source.PlayOneShot(audioClip); // 标记为活跃 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; // ===== 修复:添加 Audio/ 前缀 ===== AudioClip audioClip = BattleResManager.Instance.GetAudioResource( "Audio/" + config.Folder, // ← 修复:添加 Audio/ 前缀 config.Audio, battleField.guid ); return audioClip; // ================================ } /// /// 获取可用的音频源 /// private AudioSource GetAvailableAudioSource() { // 尝试从池中获取 if (audioSourcePool.Count > 0) { return audioSourcePool.Dequeue(); } // 如果池为空,检查是否可以动态创建 if (audioSourceObject == null) { Debug.LogError("BattleSoundManager: audioSourceObject 为空,无法创建 AudioSource"); return null; } // 计算当前总的 AudioSource 数量 int totalAudioSources = audioSourceObject.GetComponents().Length; if (totalAudioSources >= MAX_AUDIO_SOURCES) { // 达到上限,不再创建,丢弃这次播放请求 Debug.LogWarning($"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) { audioSourcePool.Enqueue(source); } } } // 清理 audioIdToSources 中已停止播放的 AudioSource foreach (var kvp in audioIdToSources) { kvp.Value.RemoveAll(s => s == null || !s.isPlaying); } } /// /// 战场速度变化回调 /// 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) { hasFocus = isFocus; // 失去焦点时,停止所有正在播放的音效 if (!hasFocus) { StopAllSounds(); } } /// /// 停止所有音效 /// public void StopAllSounds() { foreach (var source in activeAudioSources) { if (source != null && source.isPlaying) { source.Stop(); } } // 清理并回收所有音频源 foreach (var source in activeAudioSources) { if (source != null) { audioSourcePool.Enqueue(source); } } activeAudioSources.Clear(); audioIdToSources.Clear(); } /// /// 设置音量 /// public void SetVolume(float volume) { volume = Mathf.Clamp01(volume); foreach (var source in activeAudioSources) { if (source != null) { source.volume = volume; } } } /// /// 释放资源 /// public void Release() { if (battleField != null) { battleField.OnSpeedRatioChange -= OnSpeedRatioChanged; battleField.OnFocusChange -= OnFocusChanged; } StopAllSounds(); // 销毁所有 AudioSource 组件 if (audioSourceObject != null) { var sources = audioSourceObject.GetComponents(); foreach (var source in sources) { if (source != null) { GameObject.Destroy(source); } } audioSourceObject = null; } audioSourcePool.Clear(); activeAudioSources.Clear(); audioIdToSources.Clear(); audioClipCache.Clear(); } }