音频系统
OpenMon 的音频系统是一个基于 Unity AudioMixer 的池化 2D 音频引擎。它分为两层:Foundation 层(OpenMon.Foundation.Audio)提供通用播放引擎,Core 层(OpenMon.MCE.Audio)添加游戏特定的音量设置和持久化。

三 个相互独立的总线 —— 全局、音乐和音效 —— 全局总线是与另外两个相乘,而不是替换它们,这就是为什么把音乐静音并不会让战斗中的打击声消失。
架构
| 类 | 层 | 用途 |
|---|---|---|
AudioManager | Foundation | 拥有 AudioSource 池的单例,负责播放/停止/淡入淡出音频剪辑 |
AudioLibrary | Foundation | 将每个 AudioClip 映射到其 AudioMixerGroup 的 ScriptableObject |
AudioReference | Foundation | 按名称引用剪辑的轻量级可序列化 struct |
AudioSettingsManager | Core | 读写玩家音量偏好并应用到混音器 |
AudioConfiguration | Core | 持有 Master、Music 和 FX 音量级别的可序列化数据类 |
AudioInstaller | Foundation | 绑定 IAudioManager 和 IAudioLibrary 的 Zenject ScriptableObjectInstaller |
AudioSettingsInstaller | Core | 绑定 AudioSettingsManager 和 AudioMixer 的 Zenject 安装器 |
AudioManager
AudioManager 是一个实现 IAudioManager 的单例(Singleton<AudioManager>)。它在其自身的 GameObject 上管理一个动态 AudioSource 池。当请求播放时,管理器要么复用池中的空闲源,要么按需创建一个新的。
播放音频
// 简单的单次播放
AudioManager.Instance.PlayAudio(myAudioReference);
// 带淡入的循环 BGM
AudioManager.Instance.PlayAudio(bgmReference, loop: true, fadeTime: 1.5f);
// 自定义音高和音量
AudioManager.Instance.PlayAudio(sfxReference, pitch: 1.2f, volume: 0.8f);
每次调用通过 AudioLibrary 解析 AudioReference,获取剪辑及其混音器组,然后分配给池中的 AudioSource。当 fadeTime > 0 时,管理器运行一个协程,在指定持续时间内将音量从 0 线性插值到目标值。
停止音频
// 立即停止
AudioManager.Instance.StopAudio(bgmReference);
// 2秒淡出后停止
AudioManager.Instance.StopAudio(bgmReference, fadeTime: 2f);
// 紧急全部停止(如中断战斗)
AudioManager.Instance.StopAllAudios();
全局静音/取消静音
这些协程操作 AudioMixer 的 MasterVolume 参数,无需停止单个源即可静音整个游戏输出。适用于场景过渡和暂停画面。
StartCoroutine(AudioManager.Instance.MuteAllAudios(fadeTime: 1f));
StartCoroutine(AudioManager.Instance.UnmuteAllAudios(fadeTime: 1f));
池的生命周期
池是自管理的。每帧 CheckAudioSourceAvailability() 扫描所有源。任何已停止播放的源被标记为可用,其剪辑引用被清除。如果没有其他源仍在引用该剪辑,则通过 FreeAudioAsset() 通知库以便将来卸载。
AudioLibrary
AudioLibrary 是一个 ScriptableObject(通过 OpenMon > Audio > Audio Library 创建),作为中央音频注册表。它存储一个 SerializableDictionary<AudioClip, AudioMixerGroup>,将游戏中的每个剪辑映射到其应该路由 到的混音器组(如 BGM、SFX、ME)。
AudioLibrary (ScriptableObject)
+-- "battle_bgm_01" -> BGM MixerGroup
+-- "ui_confirm" -> SFX MixerGroup
+-- "level_up_jingle" -> ME MixerGroup
+-- "cry_001" -> SFX MixerGroup
...
在编辑时,库会缓存剪辑名称、剪辑引用和混音器组的平面列表,以便按名称进行 O(n) 快速查找。上下文菜单中的 Cache Audio References 命令可在添加或删除剪辑后重建这些缓存。
编辑器工具
导入器管道可以自动注册剪辑:
audioLibrary.AddAudioIfItsNotIn(clip, mixerGroup);
AudioReference
AudioReference 是一个简单的可序列化 struct,只有一个 string Audio 字段。它与 AudioLibrary 中注册的剪辑名称匹配。这使序列化的场景/预制体数据与直接剪辑引用解耦,允许在不破坏检查器引用的情况下重新组织音频文件。
[Serializable]
public struct AudioReference
{
public string Audio; // 必须与 AudioLibrary 中的名称匹配
}
在 MonoBehaviour、ScriptableObject 或 CommandGraph 节点上使用 AudioReference 字段,通过名称引用音频。
AudioSettingsManager
AudioSettingsManager 是游戏特定的音量控制器。它从持久配置系统读取 AudioConfiguration,并将三个音量级别应用到 AudioMixer:
- MasterVolume — 控制游戏整体音量
- MusicVolume — 控制 BGM 混音器组
- FXVolume — 控制音效和过场音乐
音量转换
人类对响度的感知是对数性的。系统使用 AudioUtils 在滑块的线性 0-1 范围和 Unity AudioMixer 所需的分贝刻度之间进行转换:
float db = AudioUtils.LinearVolumeToLogarithmicVolume(0.5f); // ~ -6 dB
float linear = AudioUtils.LogarithmicVolumeToLinearVolume(-6f); // ~ 0.5
持久化设置
音量偏好通过 IConfigurationManager 系统保存。当玩家在选项屏幕中调整滑块时,UI 调用:
audioSettingsManager.SetAudioConfiguration(new AudioConfiguration
{
MasterVolume = masterSlider.value,
MusicVolume = musicSlider.value,
FXVolume = fxSlider.value
});
这会将配置写入磁盘并立即将新值应用到混音器。启动时,AudioSettingsManager.Construct() 通过 Zenject 注入调用,并在下一帧应用保存的设置。
依赖注入设置
两个安装器都是添加到场景 SceneContext 的 Zenject ScriptableObjectInstaller:
- AudioInstaller — 绑定
IAudioManager(通过AudioManager.Instance)、IAudioLibrary(通过AudioLibrary资源)和AudioMixer。 - AudioSettingsInstaller — 将
AudioSettingsManager绑定为惰性单例,并注入AudioMixer。
SceneContext
+-- AudioInstaller (引用 AudioLibrary 资源和 AudioMixer)
+-- AudioSettingsInstaller(引用 AudioMixer)
在 CommandGraph 中使用音频
音频播放可以从 NPC 事件脚本(CommandGraph)触发。Sounds 类别中有这些节点:PlayMusic(把一首曲目作为世界音乐,叠加在地图自身音乐之上)、StopMusic(将其淡出)、SaveMusic 和 RestoreSceneMusic(在战斗之后保留一首曲目,或回到地图自身的音乐)、PlaySound、PlayMonsterCry 和 WaitForAudio(让图停住,直到一段短曲结束)。这些节点通过 AudioReference 字段引用剪辑,在运行时通过相同的 AudioLibrary 解析。若要用项目中已有的剪辑填充库,请使用 Tools > MCE > Audio > Populate Audio Library。
总结
| 想要... | 使用... |
|---|---|
| 播放音效 | AudioManager.Instance.PlayAudio(ref) |
| 循环播放背景音乐 | AudioManager.Instance.PlayAudio(ref, loop: true) |
| 交叉淡入淡出到新 BGM | 用 fadeTime 停止旧 BGM,用 fadeTime 播放新 BGM |
| 在场景加载时静音 | MuteAllAudios(fadeTime) / UnmuteAllAudios(fadeTime) |
| 注册新的音频剪辑 | 添加到 AudioLibrary 资源并分配混音器组 |
| 调整玩家音量 | AudioSettingsManager.SetAudioConfiguration(config) |
| 将滑块值转换为分贝 | AudioUtils.LinearVolumeToLogarithmicVolume(value) |