音频
通过 Unity AudioMixer 播放音乐、音效与语音,配合池化音源和感知暂停的淡入淡出。
通过三条 AudioMixer 通道播放音乐、音效与语音 · 池化音源、自动淡入淡出、感知暂停的行为 · 空操作替代:
NullAudio
CGS 将游戏内的所有声音都经由 IAudioService 路由——即 Unity AudioMixer 上的三条专用通道:Music(循环播放的背景音乐)、Sfx(一次性音效,可为 2D 或定位于 3D 空间)和 Voice(对白与播报,一次只播放一段)。池化的 AudioSource 组件意味着音效密集触发时不会产生垃圾回收尖峰;淡入淡出与交叉过渡是内置能力;暂停行为也符合玩家的预期——音乐继续播放,游戏音效则停止。无网络调用、无第三方插件、IL2CPP 安全。
功能概览
- 自动淡入淡出的音乐。
PlayMusic(clip)会将曲目淡入;换一个 clip 再次调用时,服务会做交叉过渡而不是硬切。StopMusic()则淡出。 - 池化的音效。
PlaySfx(clip)从预分配的池中取出一个音源,施加轻微的随机音高变化(默认约 ±10%),让重复播放的音效听起来更自然,并在片段结束后把音源归还给池。当所有音源都在使用中时,会停掉播放时间最久的音效来腾出位置。 - 一次一条的语音。
PlayVoice(clip)会停止正在播放的语音并开始新的一条——对白永远不会与自己重叠。 - 混音器快照。 *快照(snapshot)*是保存下来的一组混音器音量配置。只需一次调用加一段平滑的过渡,即可在预设之间切换——正常游玩、对白时压低音乐、菜单打开时的闷化效果。
- 实时音量设置。 通过配置服务接入的音量滑块会实时作用于混音器。
获取服务
在 Awake 中解析(类上标注 [DefaultExecutionOrder(100)])并缓存:
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class SoundManager : MonoBehaviour
{
private IAudioService _audio;
private void Awake()
{
_audio = ServiceLocator.Resolve<IAudioService>();
}
}
不要在 Update() 里调用 Resolve——这个查找是一次字典检索。缓存一次即可。
API 参考
背景音乐
void PlayMusic(AudioClip clip, float fadeInSeconds = -1f, float crossfadeSeconds = -1f)
以淡入方式开始循环播放曲目。如果已有曲目在播放,服务会改为在 crossfadeSeconds 时间内交叉过渡到新曲目。传入 -1f 使用默认值(1.5 秒淡入、1.0 秒交叉过渡);传入 0f 则硬切。
void StopMusic(float fadeOutSeconds = -1f) // Fade out and stop (default 2.0 seconds)
bool IsMusicPlaying { get; } // true while a track is playing
音效(2D 与 3D)
bool PlaySfx(AudioClip clip, float pitchVariation = -1f)
bool PlaySfx(AudioClip clip, Vector3 worldPosition, float pitchVariation = -1f)
第一个重载播放平面 2D 音效;第二个在世界坐标位置播放带距离衰减的 3D 音效。两者成功时返回 true;当 clip 为 null、尚未加载完成或池拒绝了请求时返回 false。pitchVariation 是随机音高的半幅范围:-1f 使用默认值(约 ±10%),0f 关闭变化,超过 0.5f 的值会被钳制并记录一条警告。
int ActiveSfxCount { get; } // Number of effect sources currently playing
语音与对白
void PlayVoice(AudioClip clip) // Starts the clip; any prior voice clip stops immediately
void StopVoice() // Stops the current voice clip
bool IsVoicePlaying { get; } // true while voice is playing
混音器快照与全局停止
void SetSnapshot(AudioSnapshot snapshot, float transitionSeconds = -1f)
void SetSnapshot(string snapshotName, float transitionSeconds = -1f)
void StopAll()
枚举重载过渡到内置预设之一——Normal、DialogueDucked(对白期间音乐和音效降低约 6 dB)或 MenuOpen。字符串重载可指向你在自己的混音器中添加的任意快照。StopAll() 立即静默所有通道,不做淡出。
完整示例
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class GameAudio : MonoBehaviour
{
[SerializeField] private AudioClip _explosionClip;
private IAudioService _audio;
private void Awake()
{
_audio = ServiceLocator.Resolve<IAudioService>();
}
public void PlayBattleMusic(AudioClip clip)
{
// Fade in over 1.5 seconds; if music is already playing,
// the service crossfades to the new track automatically.
_audio.PlayMusic(clip, fadeInSeconds: 1.5f);
}
public void PlayExplosion(Vector3 position)
{
// 3D effect with the default pitch variation (about ±10%)
_audio.PlaySfx(_explosionClip, position);
}
public void DialogueStart()
{
// Duck music and effects while dialogue plays
_audio.SetSnapshot(AudioSnapshot.DialogueDucked);
}
public void PlayDialogue(AudioClip voiceClip)
{
_audio.PlayVoice(voiceClip);
}
public void DialogueEnd()
{
_audio.SetSnapshot(AudioSnapshot.Normal);
}
}
关闭该服务
ServiceLocator.Replace<IAudioService>(new NullAudio());
这一行代码就能让服务静音:每个方法都变成空操作,游戏代码无需任何改动。适用于测试、配音录制会话,或者当你使用自己的音频中间件时。AudioListener 不受影响——你的摄像机仍然需要一个,只是不会有任何声音经它播放。
常见陷阱
-
仅限主线程。 所有
PlayMusic、PlaySfx、PlayVoice和SetSnapshot调用都必须发生在主线程上。如果工作线程需要触发声音,先把调用调度回主线程——调度器可以替你完成这件事。 -
暂停行为是内置的。 音乐的淡入淡出运行在时间服务的 Background 时钟上,因此游戏暂停时音乐继续播放。音效计时器运行在 Gameplay 时钟上,因此音效会停止。“音乐继续、音效停止”无需你编写任何额外代码。
-
在繁忙场景前预热池。 默认情况下,启动时会预分配 8 个音效音源。如果你的第一个战斗场景一次触发 20 个爆炸,池会按需增长并可能造成一帧卡顿。在启动时调高
AudioOptions.sfxPoolPrewarmCount来预分配更多——用一点启动内存换取运行时零卡顿。 -
音量滑块需要 flush 才会生效。 配置服务会批量处理设置写入,因此仅调用
Set(new AudioSettings { ... })只是把变更排入队列——混音器此刻还听不到任何变化。要获得滑块的实时反馈,请在Set之后立即在解析到的IConfiguration上调用FlushPending<AudioSettings>()。一经 flush,混音器立即更新。 -
自定义 AudioMixer 参数名。 如果你通过
AudioOptions提供自己的AudioMixer,它必须暴露四个参数:MasterVolume、MusicVolume、SfxVolume和VoiceVolume(单位均为 dB)。如果你的命名不同,请通过AudioOptions.masterParam、musicParam、sfxParam和voiceParam将服务指向它们。 -
片段长度决定音效回收时机。 调用
PlaySfx(clip)之后,音源会在clip.length秒后自动归还池中,计时使用未缩放时间——慢动作不会推迟回收。无需任何手动记账。 -
自定义类型在 IL2CPP 下需要
link.xml。 如果你添加了自己的、承载音频设置的配置类型,请添加一条link.xml条目,防止 IL2CPP 裁剪它们:<assembly fullname="Assembly-CSharp"> <type fullname="YourGame.CustomAudioConfig" preserve="all" /> </assembly>