跳到主要内容

音频系统

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

选项界面的声音部分

三个相互独立的总线 —— 全局、音乐和音效 —— 全局总线是与另外两个相乘,而不是替换它们,这就是为什么把音乐静音并不会让战斗中的打击声消失。

架构​

类层用途
AudioManagerFoundation拥有 AudioSource 池的单例,负责播放/停止/淡入淡出音频剪辑
AudioLibraryFoundation将每个 AudioClip 映射到其 AudioMixerGroup 的 ScriptableObject
AudioReferenceFoundation按名称引用剪辑的轻量级可序列化 struct
AudioSettingsManagerCore读写玩家音量偏好并应用到混音器
AudioConfigurationCore持有 Master、Music 和 FX 音量级别的可序列化数据类
AudioInstallerFoundation绑定 IAudioManager 和 IAudioLibrary 的 Zenject ScriptableObjectInstaller
AudioSettingsInstallerCore绑定 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:

  1. AudioInstaller — 绑定 IAudioManager(通过 AudioManager.Instance)、IAudioLibrary(通过 AudioLibrary 资源)和 AudioMixer。
  2. 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)