using UnityEngine;
|
using System.Collections.Generic;
|
|
/// <summary>
|
/// 战斗音效管理器
|
/// 职责:播放战斗中的短促技能动作和特效音效
|
/// </summary>
|
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<AudioSource> audioSourcePool = new Queue<AudioSource>();
|
private List<AudioSource> activeAudioSources = new List<AudioSource>();
|
|
// 同一音效同时播放的最大数量
|
private const int MAX_SAME_AUDIO_COUNT = 3;
|
// 记录每个音效ID当前播放的AudioSource
|
private Dictionary<int, List<AudioSource>> audioIdToSources = new Dictionary<int, List<AudioSource>>();
|
|
// 音频剪辑缓存
|
private Dictionary<int, AudioClip> audioClipCache = new Dictionary<int, AudioClip>();
|
|
// 当前播放速度
|
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();
|
}
|
}
|
|
/// <summary>
|
/// 初始化音频源池
|
/// </summary>
|
private void InitializeAudioSources()
|
{
|
// 使用战场根节点作为 AudioSource 的载体
|
audioSourceObject = battleField.battleRootNode.gameObject;
|
|
// 创建初始音频源池
|
for (int i = 0; i < INITIAL_AUDIO_POOL; i++)
|
{
|
var source = audioSourceObject.AddComponent<AudioSource>();
|
source.playOnAwake = false;
|
source.loop = false;
|
source.spatialBlend = 0f; // 2D音效
|
audioSourcePool.Enqueue(source);
|
}
|
}
|
|
/// <summary>
|
/// 每帧运行,清理已完成的音频源
|
/// </summary>
|
public void Run()
|
{
|
// 每帧清理,性能开销很小
|
CleanupFinishedAudioSources();
|
}
|
|
|
/// <summary>
|
/// 播放特效音效
|
/// </summary>
|
/// <param name="audioId">音效ID</param>
|
public void PlayEffectSound(int audioId, bool pitchControl = true)
|
{
|
if (audioId <= 0)
|
{
|
return;
|
}
|
|
PlaySound(audioId, pitchControl);
|
}
|
|
/// <summary>
|
/// 核心播放方法
|
/// </summary>
|
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<AudioSource>();
|
}
|
audioIdToSources[audioId].Add(source);
|
}
|
|
/// <summary>
|
/// 获取音频剪辑(优先从缓存获取)
|
/// </summary>
|
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;
|
}
|
|
/// <summary>
|
/// 检查是否可以播放该音效
|
/// </summary>
|
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;
|
}
|
|
/// <summary>
|
/// 加载音频剪辑
|
/// </summary>
|
private AudioClip LoadAudioClip(int audioId)
|
{
|
var config = AudioConfig.Get(audioId);
|
if (config != null)
|
{
|
return AudioLoader.LoadAudio(config.Folder, config.Audio);
|
}
|
return null;
|
}
|
|
/// <summary>
|
/// 获取可用的音频源
|
/// </summary>
|
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<AudioSource>().Length;
|
|
if (totalAudioSources >= MAX_AUDIO_SOURCES)
|
{
|
// 达到上限,不再创建,丢弃这次播放请求
|
Debug.LogWarning($"BattleSoundManager: AudioSource 数量已达上限 {MAX_AUDIO_SOURCES},无法播放新音效");
|
return null;
|
}
|
|
// 在 battleRootNode 上动态创建新的 AudioSource
|
var source = audioSourceObject.AddComponent<AudioSource>();
|
source.playOnAwake = false;
|
source.loop = false;
|
source.spatialBlend = 0f; // 2D音效
|
|
return source;
|
}
|
|
/// <summary>
|
/// 清理已播放完成的音频源
|
/// </summary>
|
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);
|
}
|
}
|
|
/// <summary>
|
/// 战场速度变化回调
|
/// </summary>
|
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;
|
}
|
}
|
}
|
|
/// <summary>
|
/// 焦点变化回调
|
/// </summary>
|
private void OnFocusChanged(bool isFocus)
|
{
|
hasFocus = isFocus;
|
|
// 失去焦点时,停止所有正在播放的音效
|
if (!hasFocus)
|
{
|
StopAllSounds();
|
}
|
}
|
|
/// <summary>
|
/// 停止所有音效
|
/// </summary>
|
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();
|
}
|
|
/// <summary>
|
/// 设置音量
|
/// </summary>
|
public void SetVolume(float volume)
|
{
|
volume = Mathf.Clamp01(volume);
|
foreach (var source in activeAudioSources)
|
{
|
if (source != null)
|
{
|
source.volume = volume;
|
}
|
}
|
}
|
|
/// <summary>
|
/// 释放资源
|
/// </summary>
|
public void Release()
|
{
|
if (battleField != null)
|
{
|
battleField.OnSpeedRatioChange -= OnSpeedRatioChanged;
|
battleField.OnFocusChange -= OnFocusChanged;
|
}
|
|
StopAllSounds();
|
|
// 销毁所有 AudioSource 组件
|
if (audioSourceObject != null)
|
{
|
var sources = audioSourceObject.GetComponents<AudioSource>();
|
foreach (var source in sources)
|
{
|
if (source != null)
|
{
|
GameObject.Destroy(source);
|
}
|
}
|
audioSourceObject = null;
|
}
|
|
audioSourcePool.Clear();
|
activeAudioSources.Clear();
|
audioIdToSources.Clear();
|
audioClipCache.Clear();
|
}
|
}
|