feat(audio): 实现音频管理系统基础功能

- 添加音频管理器抽象基类和接口定义
- 支持背景音乐、音效和3D音效播放
- 实现音量控制和音频淡入淡出效果
- 提供音频播放器池化管理机制
- 支持通过资源ID或路径播放音频
- 实现主音量、音乐音量和音效音量独立控制
- 添加音频播放状态检测功能
- 支持低通滤波器和混响效果设置
- 实现系统资源自动清理机制
This commit is contained in:
GeWuYou 2025-12-17 12:49:28 +08:00
parent 7da12c05ce
commit 506c26f945
2 changed files with 548 additions and 0 deletions

View File

@ -0,0 +1,447 @@
using GFramework.Core.extensions;
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 音频管理器抽象基类,提供音频播放的基础实现
/// </summary>
public abstract class AbstractAudioManagerSystem : AbstractSystem, IAudioManagerSystem
{
/// <summary>
/// 音频资源加载系统依赖
/// </summary>
protected IResourceLoadSystem? ResourceLoadSystem;
/// <summary>
/// 资源目录系统依赖
/// </summary>
protected IAssetCatalogSystem? AssetCatalogSystem;
/// <summary>
/// 背景音乐播放器
/// </summary>
protected AudioStreamPlayer? MusicPlayer;
/// <summary>
/// 音效播放器列表
/// </summary>
protected readonly List<AudioStreamPlayer> SoundPlayers = [];
/// <summary>
/// 可用音效播放器队列
/// </summary>
protected readonly Queue<AudioStreamPlayer> AvailableSoundPlayers = new();
/// <summary>
/// 3D音效播放器列表
/// </summary>
protected readonly List<AudioStreamPlayer3D> Sound3DPlayers = [];
/// <summary>
/// 可用3D音效播放器队列
/// </summary>
protected readonly Queue<AudioStreamPlayer3D> AvailableSound3DPlayers = new();
/// <summary>
/// 资源工厂系统依赖
/// </summary>
protected IResourceFactorySystem? ResourceFactorySystem;
/// <summary>
/// 背景音乐音量
/// </summary>
protected float MusicVolume = 1.0f;
/// <summary>
/// 音效音量
/// </summary>
protected float SoundVolume = 1.0f;
/// <summary>
/// 主音量
/// </summary>
protected float MasterVolume = 1.0f;
/// <summary>
/// 特效音量
/// </summary>
protected float SfxVolume = 1.0f;
/// <summary>
/// 语音音量
/// </summary>
protected float VoiceVolume = 1.0f;
/// <summary>
/// 环境音量
/// </summary>
protected float AmbientVolume = 1.0f;
/// <summary>
/// 音乐淡入淡出动画
/// </summary>
protected Tween? MusicFadeTween;
/// <summary>
/// 最大同时播放的音效数量
/// </summary>
protected const int MaxSoundPlayers = 10;
/// <summary>
/// 最大同时播放的3D音效数量
/// </summary>
protected const int MaxSound3DPlayers = 5;
/// <summary>
/// 所有者节点的抽象属性
/// </summary>
protected abstract Node Owner { get; }
/// <summary>
/// 系统初始化方法
/// </summary>
protected override void OnInit()
{
// 获取依赖的系统
ResourceLoadSystem = this.GetSystem<IResourceLoadSystem>();
AssetCatalogSystem = this.GetSystem<IAssetCatalogSystem>();
ResourceFactorySystem = this.GetSystem<IResourceFactorySystem>();
// 初始化背景音乐播放器
MusicPlayer = new AudioStreamPlayer();
Owner.AddChild(MusicPlayer);
// 预创建音效播放器池
for (var i = 0; i < MaxSoundPlayers; i++)
{
var soundPlayer = new AudioStreamPlayer();
Owner.AddChild(soundPlayer);
soundPlayer.Finished += () => OnSoundFinished(soundPlayer);
SoundPlayers.Add(soundPlayer);
AvailableSoundPlayers.Enqueue(soundPlayer);
}
// 预创建3D音效播放器池
for (var i = 0; i < MaxSound3DPlayers; i++)
{
var sound3DPlayer = new AudioStreamPlayer3D();
Owner.AddChild(sound3DPlayer);
sound3DPlayer.Finished += () => OnSound3DFinished(sound3DPlayer);
Sound3DPlayers.Add(sound3DPlayer);
AvailableSound3DPlayers.Enqueue(sound3DPlayer);
}
}
/// <summary>
/// 当音效播放完成时的回调
/// </summary>
/// <param name="player">完成播放的音频播放器</param>
private void OnSoundFinished(AudioStreamPlayer player)
{
// 将播放器放回可用队列
AvailableSoundPlayers.Enqueue(player);
}
/// <summary>
/// 当3D音效播放完成时的回调
/// </summary>
/// <param name="player">完成播放的3D音频播放器</param>
private void OnSound3DFinished(AudioStreamPlayer3D player)
{
// 将播放器放回可用队列
AvailableSound3DPlayers.Enqueue(player);
}
/// <summary>
/// 播放背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="loop">是否循环播放</param>
public virtual void PlayMusic(string audioPath, float volume = 1.0f, bool loop = true)
{
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null || MusicPlayer == null) return;
// 停止当前正在进行的淡入淡出效果
MusicFadeTween?.Kill();
MusicPlayer.Stream = audioStream;
MusicPlayer.VolumeDb = LinearToDb(volume * MusicVolume * MasterVolume);
MusicPlayer.Play();
}
/// <summary>
/// 通过资源ID播放背景音乐
/// </summary>
/// <param name="musicId">音乐资源ID</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="loop">是否循环播放</param>
public virtual void PlayMusic(AssetCatalog.ResourceId musicId, float volume = 1.0f, bool loop = true)
{
PlayMusic(musicId.Path, volume, loop);
}
/// <summary>
/// 播放音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlaySound(string audioPath, float volume = 1.0f, float pitch = 1.0f)
{
if (AvailableSoundPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSoundPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * SoundVolume * MasterVolume);
player.Play();
}
/// <summary>
/// 通过资源ID播放音效
/// </summary>
/// <param name="soundId">音效资源ID</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlaySound(AssetCatalog.ResourceId soundId, float volume = 1.0f, float pitch = 1.0f)
{
PlaySound(soundId.Path, volume, pitch);
}
/// <summary>
/// 播放3D音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="position">3D空间中的位置</param>
/// <param name="volume">音量大小范围0-1</param>
public virtual void PlaySound3D(string audioPath, Vector3 position, float volume = 1.0f)
{
if (AvailableSound3DPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSound3DPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * SoundVolume * MasterVolume);
player.Position = position;
player.Play();
}
/// <summary>
/// 停止背景音乐
/// </summary>
public virtual void StopMusic()
{
MusicFadeTween?.Kill();
MusicPlayer?.Stop();
}
/// <summary>
/// 暂停背景音乐
/// </summary>
public virtual void PauseMusic()
{
MusicFadeTween?.Kill();
// todo 需要记录音乐播放位置,以便恢复播放时从正确位置开始
}
/// <summary>
/// 恢复背景音乐播放
/// </summary>
public virtual void ResumeMusic()
{
MusicPlayer?.Play();
}
/// <summary>
/// 设置背景音乐音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetMusicVolume(float volume)
{
MusicVolume = volume;
if (MusicPlayer != null)
{
MusicPlayer.VolumeDb = LinearToDb(MusicVolume * MasterVolume);
}
}
/// <summary>
/// 设置音效音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetSoundVolume(float volume)
{
SoundVolume = volume;
}
/// <summary>
/// 设置主音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetMasterVolume(float volume)
{
MasterVolume = volume;
// 更新音乐音量
if (MusicPlayer != null)
{
MusicPlayer.VolumeDb = LinearToDb(MusicVolume * MasterVolume);
}
}
/// <summary>
/// 设置SFX音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetSfxVolume(float volume)
{
SfxVolume = volume;
}
/// <summary>
/// 设置语音音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetVoiceVolume(float volume)
{
VoiceVolume = volume;
}
/// <summary>
/// 设置环境音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetAmbientVolume(float volume)
{
AmbientVolume = volume;
}
/// <summary>
/// 检查背景音乐是否正在播放
/// </summary>
/// <returns>正在播放返回true否则返回false</returns>
public virtual bool IsMusicPlaying()
{
return MusicPlayer?.Playing ?? false;
}
/// <summary>
/// 淡入背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="duration">淡入持续时间(秒)</param>
/// <param name="volume">目标音量</param>
public virtual void FadeInMusic(string audioPath, float duration, float volume = 1.0f)
{
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null || MusicPlayer == null) return;
// 停止当前正在进行的淡入淡出效果
MusicFadeTween?.Kill();
MusicPlayer.Stream = audioStream;
MusicPlayer.VolumeDb = LinearToDb(0.0f); // 初始音量为0
MusicPlayer.Play();
// 创建淡入动画
MusicFadeTween = Owner.CreateTween();
MusicFadeTween.TweenProperty(MusicPlayer, "volume_db", LinearToDb(volume * MusicVolume * MasterVolume),
duration);
}
/// <summary>
/// 淡出背景音乐
/// </summary>
/// <param name="duration">淡出持续时间(秒)</param>
public virtual void FadeOutMusic(float duration)
{
if (MusicPlayer == null) return;
// 停止当前正在进行的淡入淡出效果
MusicFadeTween?.Kill();
// 创建淡出动画
MusicFadeTween = Owner.CreateTween();
MusicFadeTween.TweenProperty(MusicPlayer, "volume_db", LinearToDb(0.0f), duration);
MusicFadeTween.TweenCallback(Callable.From(() => MusicPlayer.Stop()));
}
/// <summary>
/// 设置低通滤波器强度
/// </summary>
/// <param name="amount">滤波器强度范围0-1</param>
public virtual void SetLowPassFilter(float amount)
{
// TODO: 实现低通滤波器效果
// 可以通过AudioEffectLowPassFilter实现
}
/// <summary>
/// 设置音频混响效果
/// </summary>
/// <param name="roomSize">房间大小</param>
/// <param name="damping">阻尼</param>
/// <param name="wetLevel">湿声级别</param>
public virtual void SetReverb(float roomSize, float damping, float wetLevel)
{
// TODO: 实现音频混响效果
// 可以通过AudioEffectReverb实现
}
/// <summary>
/// 将线性音量值转换为分贝值
/// </summary>
/// <param name="linear">线性音量值0-1</param>
/// <returns>分贝值</returns>
protected static float LinearToDb(float linear)
{
return linear > 0 ? 20 * Mathf.Log(linear) : -100;
}
/// <summary>
/// 将分贝值转换为线性音量值
/// </summary>
/// <param name="db">分贝值</param>
/// <returns>线性音量值0-1</returns>
protected static float DbToLinear(float db)
{
return db > -100 ? Mathf.Exp(db / 20) : 0;
}
/// <summary>
/// 系统销毁时清理资源
/// </summary>
protected void OnDestroy()
{
// 停止并清理淡入淡出动画
MusicFadeTween?.Kill();
// 清理音乐播放器
MusicPlayer?.QueueFree();
// 清理音效播放器池
foreach (var player in SoundPlayers)
{
player.QueueFree();
}
// 清理3D音效播放器池
foreach (var player in Sound3DPlayers)
{
player.QueueFree();
}
SoundPlayers.Clear();
AvailableSoundPlayers.Clear();
Sound3DPlayers.Clear();
AvailableSound3DPlayers.Clear();
}
}

View File

@ -0,0 +1,101 @@
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 音频管理器系统接口,用于统一管理背景音乐和音效的播放
/// </summary>
public interface IAudioManagerSystem : ISystem
{
/// <summary>
/// 播放背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="loop">是否循环播放</param>
void PlayMusic(string audioPath, float volume = 1.0f, bool loop = true);
/// <summary>
/// 播放音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
void PlaySound(string audioPath, float volume = 1.0f, float pitch = 1.0f);
/// <summary>
/// 停止背景音乐
/// </summary>
void StopMusic();
/// <summary>
/// 暂停背景音乐
/// </summary>
void PauseMusic();
/// <summary>
/// 恢复背景音乐播放
/// </summary>
void ResumeMusic();
/// <summary>
/// 设置背景音乐音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetMusicVolume(float volume);
/// <summary>
/// 设置音效音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetSoundVolume(float volume);
/// <summary>
/// 设置主音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetMasterVolume(float volume);
/// <summary>
/// 检查背景音乐是否正在播放
/// </summary>
/// <returns>正在播放返回true否则返回false</returns>
bool IsMusicPlaying();
/// <summary>
/// 淡入背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="duration">淡入持续时间(秒)</param>
/// <param name="volume">目标音量</param>
void FadeInMusic(string audioPath, float duration, float volume = 1.0f);
/// <summary>
/// 淡出背景音乐
/// </summary>
/// <param name="duration">淡出持续时间(秒)</param>
void FadeOutMusic(float duration);
/// <summary>
/// 播放3D音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="position">3D空间中的位置</param>
/// <param name="volume">音量大小范围0-1</param>
void PlaySound3D(string audioPath, Vector3 position, float volume = 1.0f);
/// <summary>
/// 设置低通滤波器强度
/// </summary>
/// <param name="amount">滤波器强度范围0-1</param>
void SetLowPassFilter(float amount);
/// <summary>
/// 设置音频混响效果
/// </summary>
/// <param name="roomSize">房间大小</param>
/// <param name="damping">阻尼</param>
/// <param name="wetLevel">湿声级别</param>
void SetReverb(float roomSize, float damping, float wetLevel);
}