音频系统

Fink Framework 的音频系统基于 Unity AudioMixer 实现集中式音频管理, 提供 背景音乐(Music)音效(SFX) 两大类音频的完整控制能力。

系统通过 ResManager 异步加载音频资源、通过对象池创建与回收 AudioSource, 并支持句柄(AudioOperation)获取进度与回调。
这是一个高度模块化、可扩展的音频系统。


1. 系统概述

AudioManager 包含以下核心组件:

1.1. 全局 AudioMixer

位于 Resources/Audio/MasterMixer

内部包含两个分组:

  • Music(背景音乐)
  • SFX(音效)

系统通过 FindMatchingGroups 自动获取分组。

1.2 背景音乐(Music)

  • 使用专属持久化的 MusicPlayer(AudioSource)
  • 永不销毁(DontDestroyOnLoad)
  • 输出到 Music MixerGroup
  • 支持同步 / 异步 / 句柄方式播放

用途:循环播放背景音乐。

1.3 音效(Sound Effects / SFX)

  • 每次播放使用对象池创建 AudioSource
  • 输出到 SFX MixerGroup
  • 支持父对象挂载(用于跟随 3D 声源)
  • 播放结束后自动清理(FixedUpdate → CleanAudioSource)
  • 支持同步 / 异步 / 回调 / 句柄方式播放

所有音效节点默认挂在:SoundPlayers

会在第一次播放音效时自动创建。

2. Mixer 初始化流程

AudioManager 构造函数中执行:InitMixer()

流程:

    1. 从 ResManager 加载 res://Audio/MasterMixer
    1. 查找 Music 组
    1. 查找 SFX 组
    1. 若加载失败会打印错误日志

之后所有 AudioSource 会自动绑定到对应 MixerGroup。


3. 背景音乐(Music)

背景音乐由一个全局唯一的 MusicPlayer 播放。

3.1 播放音乐(同步加载)

AudioManager.Instance.PlayMusic("res://Audio/Music/BGM_Main");

行为说明:

  • 若 MusicPlayer 不存在则创建
  • 挂到 DontDestroyOnLoad
  • 使用 Music MixerGroup
  • 同步加载 AudioClip
  • 自动循环播放

3.2 播放音乐(异步 await)

await AudioManager.Instance.PlayMusicAsync("res://Audio/Music/BGM_Main");

返回 AudioSource。

内部行为:

  • 异步加载音频资源(LoadAsyncHandle)
  • 播放完成后设定循环
  • 返回播放器引用

3.3 播放音乐(异步 回调)

AudioManager.Instance.PlayMusicAsync(
    "res://Audio/Music/BGM_Main",
    source =>
    {
        // 播放成功后的回调
    }
);

3.4 播放音乐(异步 句柄)

var op = AudioManager.Instance.PlayMusicHandle("res://Audio/Music/BGM_Main");
op.Completed += o => Debug.Log("Music Loaded");

AudioOperation 可用于:

  • Progress 进度条
  • IsDone
  • IsFailed
  • Completed 回调

3.5 停止音乐

AudioManager.Instance.StopMusic("anyName");

(名称不参与逻辑,仅作接口兼容)

3.6 暂停音乐

AudioManager.Instance.PauseMusic("anyName");

3.7 修改背景音乐音量

AudioManager.Instance.ChangeMusicValue(0.5f);

内部将 01 转换为 -800 dB 的真实音量范围。


4. 音效(SFX)

音效使用对象池管理,每次播放都会从池中取出 AudioSource,并在播放结束后自动回收。

4.1 播放音效(同步加载)

AudioManager.Instance.PlaySound("res://Audio/SFX/Click");

4.2 播放音效(异步 await)

var source = await AudioManager.Instance.PlaySoundAsync("res://Audio/SFX/Explosion");

4.3 播放音效(异步 回调)

AudioManager.Instance.PlaySoundAsyncCallback(
    "res://Audio/SFX/Explosion",
    source => { Debug.Log("Loaded"); }
);

4.4 播放音效(异步 句柄)

var op = AudioManager.Instance.PlaySoundHandle("res://Audio/SFX/Explosion");

op.Completed += o =>
{
    Debug.Log("Done: " + o.Progress);
};

句柄方式适合:

  • Loading UI
  • 多步骤流程
  • 动画触发
  • 播放前修改参数

4.5 停止某个音效

AudioManager.Instance.StopSound(source);

操作:

  • 停止播放
  • 清空 Clip
  • 回收至对象池
  • 从内部列表/集合移除记录

4.6 调整所有 SFX 音量

AudioManager.Instance.ChangeSoundValue(0.3f);

映射成 Mixer dB 范围 -80~0。

4.7 开关所有音效

AudioManager.Instance.ToggleAllSounds(true);   // 恢复播放
AudioManager.Instance.ToggleAllSounds(false);  // 全部停止

暂停后再次开启会重新播放非循环音效。

4.8 清空所有音效

AudioManager.Instance.ClearSound();

框架内置的场景切换流程会自动调用。


5. 内部机制说明

5.1 异步播放流程(核心)

所有异步音频最终走到:PlayAudioAsyncWrapper()

流程:

  • 创建或获取 AudioSource
  • 调用 ResManager.LoadAsyncHandle 加载 AudioClip
  • 将进度同步给 AudioOperation.Progress
  • 加载失败 → op.SetFailed()
  • 加载成功 → 绑定 Clip 并播放
  • op.SetResult(clip) → 触发 Completed

5.2 自动清理音效

FixedUpdate 中执行:CleanAudioSource()

倒序遍历 soundList:

若 AudioSource 不存在或未播放(!isPlaying)

清空 clip

回收至池

从集合与列表中移除

保证音效不会泄漏。

5.3 音效挂载结构

如果未传父对象:

SoundPlayers ↳ (pool instances)

若已传父对象:

Player ↳ ExplosionSource

用于 3D 声源跟随。

5.4 避免重复记录播放源

音效播放使用:

HashSet<AudioSource> soundSet
List<AudioSource> soundList

好处:

  • soundSet → O(1) 判断是否重复
  • soundList → 有序遍历并倒序删除

这是避免重复回收、重复检测的关键结构。


6. 音频操作句柄(AudioOperation)

异步播放会返回一个 AudioOperation。

包含:

属性说明
IsDone是否完成
IsFailed是否失败
Progress0~1 进度(来自资源加载)
Clip加载成功的 AudioClip
Source播放的 AudioSource
Completed完成事件回调

等待句柄完成: await op.WaitUntilDone();


7. 最佳实践(建议)

背景音乐必须放在 res://Audio/Music/

音效必须放在 res://Audio/SFX/

所有音效尽量使用异步加载,避免同步卡顿

切换场景前调用 ClearSound()(框架场景系统已自动处理)

需要淡入淡出可在 MusicPlayer 上自行扩展 Tween


8. 总结

本音频系统具备:

  • 完整的同步/异步/句柄式播放能力
  • Mixer 级别精确音量控制
  • 对象池优化的音效管理
  • 自动清理减少泄漏风险
  • 适用于 2D/3D 游戏、UI、VR 项目