第14章 · 声音系统:MetaSounds 与声景设计

📑 目录

导语:做个实验:找一段你最怕的恐怖游戏实况,把音量关到零,再看一遍。Jump Scare 成了哑剧,走廊尽头那扇门就是一扇普通的门,手心的汗没了。把声音放回来,心率立刻回到一百二。混音师才是吓你的那个人——多边形只是他手里的工具。声音占了沉浸感预算的一半,分到的人力却往往不到项目一成——这一章,就是把这桩赔本买卖算清楚。

本章地图

mindmap
  root((第14章 声音系统))
    预算观
      静音实验
      三层声景
      dB 与听感疲劳
    资产层
      MetaSounds 图
      参数接口
      Quartz 时钟
    空间层
      衰减与聚焦锥
      空间化
      遮挡与混响
    结构层
      自适应音乐
      混音总线与 Ducking
    实战
      雨夜天台声景

关掉声音,恐怖片就成了哑剧

导语里的实验建议每个人都做一遍,因为它能把"恐怖"拆成具体成分。静音之后,Jump Scare 剩下的只有画面跳变——大脑对跳变的反应是"愣一下",不是"心跳骤停"。真正让心率上 120 的,是跳变前 0.2 秒那个音频预告:一声不自然的寂静,或一个逆行的低频滑音。混音师在画面动手之前,就先掐住了你的喉咙。

再看一笔账。一个中型 3A 项目的常见配置:美术团队 60 到 100 人,音频团队 3 到 8 人;音乐与音效按分钟外包计价,摊进项目总预算经常不到一成。可玩家评测里那些"氛围""压迫感""紧张得不敢喘气",一半以上要靠耳朵兑现。声音是沉浸感里被克扣的那一半预算——不是因为它不重要,恰恰因为它太便宜,便宜到没人愿意为它早做打算。

程序视角下,这笔账还有另一层:音频管线和你已经懂的渲染管线几乎同构。资产(SoundWave)进逻辑图(MetaSounds),实例化成播放组件(UAudioComponent),过衰减、空间化、遮挡这套"音频版视锥剔除",最后进 Submix 与 Sound Class 做"音频版后处理"。学完本章,你手里有一套完整管线——不再是零散的 wav 播放调用。

flowchart LR
    W[SoundWave 波形资产] --> G[MetaSoundSource 逻辑图]
    G --> C[UAudioComponent 播放实例]
    C --> A[SoundAttenuation 衰减/空间化/遮挡]
    A --> S[Submix 路由与效果器]
    S --> M[SoundClass / SoundMix 混音]
    M --> D[Audio Device 混音输出]
    D --> E((玩家耳朵))
    K[SoundConcurrency 并发规则] -. 限制同时发声数 .-> C
    O[Occlusion Trace] -. 挡则闷 .-> A

三个最常见的误判,先排掉:

  • "声音最后再放"——衰减半径、遮挡通道、并发上限全是关卡数据,最后放等于把关卡重摆一遍。
  • "声音就是播 wav"——播一个 wav 只要一行 API;让一百个 wav 不打架、不穿帮、会呼吸,是系统工程。
  • "响就行"——响度是最廉价的刺激,也是最快的疲劳源。下一节的 dB 账会算给你看。

还要认清音频债的偿还方式:它不在 code review 里暴露,在试玩会上暴露。玩家说"这段没感觉",查到最后,常常是雷声音量没错、但低通没跟上距离,或者音乐切换没卡拍。画面债用眼睛还,声音债用"感觉"还——而"感觉"最贵。

顺带说一句,程序员在音频侧其实是占便宜的一方:衰减半径是数值调参,自适应音乐是状态机,并发上限是资源预算,Ducking 是优先级仲裁。全是老本行,只是换了个单位。

补课·声景分三层,dB 是一把对数尺

补课:本节补音频基础——三层声景结构与 dB 尺度。音频出身的读者可直接跳到下一节。

声音设计的第一件事是把场景里的声音分类。行业惯用的分法正好三层,用程序员的概念翻译一遍:

  • 拟音(Foley):与动作一一绑定的离散事件——脚步、开门、换弹、拾取。事件驱动,必须配变体池:同一个 wav 连放五次,玩家立刻出戏。
  • 氛围(Ambience):持续运转的常驻服务——雨底、风声、机房嗡鸣、远处车流。循环播放,要求慢变化;一成不变超过 30 秒,它就成了"白色墙纸",被大脑过滤掉。
  • 拟态(Score):情绪状态机——战斗音乐、探索铺底、剧情 stinger。它不负责"现场有什么",负责"现在该怕什么"。

对照渲染就好记了:拟音是前景物件,氛围是天光,音乐是后处理调色。三层各管一段,互不抢戏;混为一层的项目,后期必然打架。

变体池的规模给个下限:短促音效 4–6 个变体起步;脚步这种每秒都在响的,6–8 个变体加随机音高才撑得住一分钟。样本做不够就用参数凑——音高抖动、起始偏移、滤波扫动,都是廉价变体。

第二件事是尺子。分贝(dB)是对数刻度,不是线性的:声压每翻一倍 +6 dB,而人耳主观响度翻一倍大约要 +10 dB。游戏内说的是 dBFS(以满幅为 0 的相对电平),0 dBFS 就是天花板,混音的全部手艺就是在天花板底下分配空间。

N 个同样响的声源叠在一起,总响度远不是 N 倍:

Lsum=L+10log10NL_{sum} = L + 10\log_{10} N

10 个雨点源只比 1 个响 10 dB。这个公式直接决定声景的搭法:氛围层靠"种类"取胜,不靠"数量",堆同种声源是最亏的买卖。

还有一条曲线必须认识:等响曲线(Equal-Loudness Contour)。人耳对 2–5 kHz 最敏感——语音清晰度全在这一段——对低频天生迟钝,而且整体音量越小,低频感知掉得越快。推论很实际:玩家深夜把音量拧小,你精心调的"压迫感低频"第一个消失。混音时留一手:小音量下仍听得清的声音,优先级永远高于大音量下才爽的声音。

游戏内声音相对电平参考备注
耳语 / 呼吸−30 ~ −24 dBFS恐怖游戏的常客
雨声底 / 氛围床−28 ~ −18 dBFS比对话低一整档
脚步−20 ~ −12 dBFS玩家的"第二视角"
对话−12 ~ −6 dBFS混音的锚点
枪声 / 爆炸−6 ~ 0 dBFS离天花板最近的位置

第三件事最容易被忽视:听感疲劳。恒定不变的声音会触发听觉适应——大脑的滤波器把它归档为"背景",30 秒后你就真的听不见机房嗡鸣了。持续高响度更糟,会造成暂时性阈移(TTS),等于把玩家的听力整体调暗一档。

推论很反直觉:一直响的警报等于没有警报。它先被适应机制过滤,再用掩蔽效应盖住真正重要的声音——脚步、换弹、队友语音。合格的警报设计只有三条:脉冲式而非长鸣;分级别,不同威胁不同音色;会自己闭嘴,响够次数自动降格为低频提示。这三条到混音总线那节还会回来。

掩蔽(Masking)是疲劳的帮凶:响的盖掉轻的,频段近的盖掉频段远的。警报和音乐铺底挤在同一频段,等于两个进程抢一把锁——谁响谁赢,不是谁优先级高谁赢,而玩家最后什么都没听清。设计任何一个声音之前先问一句:它活在哪个频段,邻居是谁。

最后警惕响度战争:所有东西都响,等于没有东西响——对比才是响度的意义。恐怖游戏最瘆人的瞬间往往是"突然安静",而安静只有在前面够热闹时才成立。

MetaSounds 接的是一份参数化的活

先回答"换掉的是什么"。Sound Cue 是 UE4 时代的音频图:随机容器、混合器、调制器三板斧,做"播放时随机挑一个 wav 再抖一下音高"这类事够用。它的天花板在于图是一次性的——触发完就跑完了。运行中想根据游戏状态连续改变声音,逻辑只能搬回蓝图:音频设计师看不懂,程序员嫌琐碎,两边都难受。

MetaSounds 把这件事换成了"可编程乐器"。它是一张样本级精度的 DSP 图:波形播放器、滤波器、包络、延迟线、随机源、数学运算,全在图内闭合运转。用第 8 章的语言讲:Sound Cue 之于 wav 相当于固定管线,MetaSounds 之于 wav 相当于材质系统之于贴图。

它值钱的地方有三个。

① 参数接口。MetaSoundSource 对外暴露输入参数,游戏侧通过 Audio Component 的 SetFloatParameter、SetBoolParameter、SetTriggerParameter 写入——战斗强度、积水深度、门窗开合,全是图内能直接消费的量。声音从"被触发"变成"被驱动"。

② Quartz 时钟。音乐切换要对齐小节边界才有"排练过"的感觉,否则就是硬切歌。Quartz 子系统提供样本级精度的音乐时钟,自适应音乐一节全靠它排队切换点;在 5.8 中对应官方文档的 Quartz 词条,细节以文档为准。

③ 图即数据。MetaSound 可以被当节点嵌进另一张图,能进版本控制、能 diff、能复用——对程序背景的团队,这是刚需。

代价也要说:MetaSounds 的图比 Sound Cue 深,而音频侧的剖析工具仍在追赶渲染侧(5.8 中先用 Unreal Insights 与 au.* 系列控制台命令,词条见延伸阅读)。旧工程的 Sound Cue 不必重写,两者同为 USoundBase,共存没有障碍;新资产直接上 MetaSounds。

图里能用的节点大致四类:音源(波形播放器、噪声源)、处理(滤波、延迟、包络、混合)、逻辑(MapRange、比较器、随机源)、触发(播放事件、定时器)。播放形态由接口约定:one-shot 走触发式接口,常驻循环走 Source 接口,对拍的音乐挂时钟接口——选接口就是选生命周期,5.8 的接口清单以官方 MetaSounds Reference 词条为准。

排查"参数没生效"按固定顺序来:组件是不是还持有这个声音的引用;参数名拼写对不对(TEXT 宏下没有编译期检查,写错静默失败);图的输入默认值是不是把写入顶掉了。这个顺序能省掉八成的玄学时间。

老工程迁移只换外壳不换内容:先把 Sound Cue 里的随机与混合逻辑搬进 MetaSounds 图,再逐个替换引用,按关卡批次走。别计划"一个周末全换完",那种计划的结果通常是两套系统并行到项目结束。

下面这段伪代码是一张典型的"强度驱动"图,它解决一个问题:同一个音乐循环,如何随战斗强度无级变"狠":

# 伪代码:MetaSounds 图「强度 → 滤波 + 空间感」
# 输入 Intensity ∈ [0,1],由战斗管理器用 SetFloatParameter 写入
inputs: Wave(loop), Intensity

cutoff = MapRange(Intensity, 0, 1, 400, 12000)   # Hz:闷罐 → 开盖
wet    = MapRange(Intensity, 0, 1, 0.0, 0.35)    # 延迟发送量
lfo    = Sine(frequency=0.1)                      # 缓慢呼吸,防死板

filter = StateVariableFilter(input=Wave, cutoff=cutoff + 200*lfo)
delay  = DelayLine(input=filter, time=0.23, feedback=0.3)
out    = Mix(dry=filter, wet=delay, alpha=wet)

OnPlay >> Wave.Start        # 触发线:只进不出
out    >> Output.Stereo     # 数据线与触发线是两条河

整张图没有一个分支,却实现了无级渐变——这是 MetaSounds 最典型的思维方式:别切,调。强度低时截止频率压在 400 Hz 附近,音乐发闷、有距离感;强度拉高,滤波"揭开盖子",高频全部放出来。要点注释:MapRange 是图的翻译官,所有游戏量都先过它;lfo 那 200 Hz 的抖动是为了让听感"活",静态参数是声景的死敌。

衰减、空间化与遮挡:声音也要过"视锥"

渲染管线靠视锥剔除决定画什么,音频管线靠衰减资产(Sound Attenuation)决定听见什么、听成什么样。一个衰减资产管四件事:距离、方向、遮挡、混响发送。

距离先说。三要素:衰减函数(Linear / Logarithmic / Inverse / NaturalSound)、内半径(InnerRadius,半径内不衰减)、衰减距离(FalloffDistance,到此归零)。选型背后是同一个物理:

L(r)=Lref20log10(rrref)L(r) = L_{ref} - 20\log_{10}\left(\frac{r}{r_{ref}}\right)

点声源的能量摊在半径 r 的球面上,强度随 1/r² 下降;换算成声压的对数刻度,就是每拉开一倍距离 −6 dB。NaturalSound 按这个模型走,适合脚步、雷这类"该像真的"的声源;Logarithmic 远处衰减平缓,综艺感强,适合提示音;Inverse 在远处掉得太狠,大世界里一转身声源就"唰"地没了,慎用于长距离事件。

距离衰减:每倍距离 −6 dB距离 r(对数刻度)dBInnerRadiusFalloffDistanceNaturalSound−6 dB / 倍距聚焦锥:声音也有朝向声源内锥:满响度外锥过渡带锥外:按系数衰减(喇叭/喊话)

方向由空间化与聚焦锥分管。bSpatialize 打开后,立体声声像随声源方位实时平移;再选 Binaural(HRTF 双耳渲染),耳机玩家能听出"左后上方"这种精确方位——代价是 CPU 按发声路数计费,且对音箱外放收益有限。聚焦锥(Focus)给声源一个朝向:锥内满响度,锥外按系数衰减,喇叭广播、NPC 面朝喊话都靠它。

听者(Listener)挂在哪里是个独立决策。引擎默认把耳朵挂在相机上:第三人称游戏里,角色在左下角、耳朵在镜头后——所以你能听见自己角色背后的脚步。第一人称则通常挂在 Pawn 上。「回响峡谷」是第三人称,保持默认;真要改,入口在 PlayerController 的听者朝向设置,5.8 中对应官方文档的 Audio Listener 词条。

遮挡(Occlusion)是沉浸感的隐形功臣。引擎从声源向听者拉一条 trace,被几何挡住就同时做两件事:音量按 OcclusionVolumeAttenuation 压掉,再过一个低通滤波(截止频率由 OcclusionLowPassFilterFrequency 控制)把声音闷掉。物理直觉很简单:高频波长短,撞墙就散;低频波长长,绕墙而过——所以隔壁传来的声音永远是"闷的"。开销上每个 3D 声源一条 trace,几十路同时跑才需要开始心疼 CPU,真到那一步,还有按距离给遮挡检测降频的招。

空气吸收(Air Absorption)是衰减资产里另一个物理参数:距离越远,高频衰减越多。远处的雷只剩低频隆隆,就是它在起作用。衰减资产给高低频各设一个衰减距离,远雷的"闷"不用单独做资产,两个参数就有了。

还有一个运动才有的小现象:多普勒频移(Doppler Shift)。声源与听者沿连线相对运动时,频率被压缩或拉伸:

f=fvs+vlvsvef' = f\,\frac{v_{s} + v_{l}}{v_{s} - v_{e}}

v_s 是声速(约 343 m/s),v_l、v_e 分别是听者与声源沿连线的速度分量。直觉版:物体冲你来,声音变尖;离你而去,声音变闷——警车呼啸而过的那声"咿——呜"就是它。引擎层在 5.8 中对应官方文档的 Doppler 词条;工程上更常见的做法是手动按相对速度缩放 PitchMultiplier,飞掠的子弹、擦肩的载具都这么干,可控性反而更高。

最后是混响,它能解释一个所有玩家都体验过、却说不清的现象:为什么走廊里的脚步声不一样。

耳朵判断空间大小,靠的是直达声占比。给一个简化模型:

D=EdirectEdirect+Eearly+ElateD = \frac{E_{direct}}{E_{direct} + E_{early} + E_{late}}

D 越接近 1,声音越"干",大脑判定为小空间或露天;D 降下来,声音发"糊",大脑判定为大而硬的空腔。混响尾巴的长度用 RT60(衰减 60 dB 所需时间)估算:

T600.161VAT_{60} \approx 0.161\,\frac{V}{A}

V 是空间体积,A 是等效吸声面积。走廊麻烦在:体积不大,可四面硬墙让 A 更小,RT60 反而比同体积的房间长——每一步后面都拖着一条几百毫秒的回声尾巴。大脑听到这条尾巴,不用看画面就知道"这是条空旷的硬走廊"。这就是声学版的"环境叙事":混响参数本身就是场景描述。

引擎里的落地路径:子混音上挂 Reverb 效果器,各声源通过衰减资产的 Reverb Send 决定送多少湿声;空间切换用 Audio Gameplay Volume(5.8 中对应官方文档的 Audio Gameplay Volumes 词条),进走廊湿声比例拉高,回露天收干。雨夜天台一节会把它用满。

下面这段 C++ 是拟音层的标准动作:在脚底位置播放一步带完整空间行为的音效。

// 在脚底播放一步带衰减/并发/随机音高的音效(如"湿混凝土"脚步)
void AEchoCharacter::PlayFootstep(const FVector& FootLocation)
{
    UWorld* World = GetWorld();
    if (!World || !FootstepSound) { return; }

    const float Pitch = FMath::RandRange(0.92f, 1.08f); // 防重复感
    UGameplayStatics::PlaySoundAtLocation(
        World, FootstepSound, FootLocation,
        /*VolumeMultiplier*/ 0.9f, Pitch,
        /*StartTime*/ 0.0f,
        FootstepAttenuation,   // USoundAttenuation*:半径+空间化+遮挡
        FootstepConcurrency);  // USoundConcurrency*:同帧最多 4 步
}

要点:衰减与并发做成资产而不是写死在代码里,改数值不用重编译;随机音高 ±8% 是防"机关枪脚步"的最低配置。临时性的 PlaySoundAtLocation 只管"放",常驻层(雨底、音乐)要用 UAudioComponent 持着,才有参数可写——下一节就用上了。

classDiagram
    USoundBase <|-- USoundWave
    USoundBase <|-- UMetaSoundSource
    USoundBase <|-- USoundCue : 旧系统·可共存
    UAudioComponent --> USoundBase : 播放
    UAudioComponent --> USoundAttenuation : 空间行为
    UAudioComponent --> USoundConcurrency : 并发约束
    USoundClass "1" o-- "*" USoundBase : 归类混音
    USoundMix ..> USoundClass : 快照压音
    UAudioComponent --> USoundSubmix : 路由
    USoundAttenuation : InnerRadius / FalloffDistance
    USoundAttenuation : bSpatialize / bEnableOcclusion
    USoundAttenuation : ReverbSend
    USoundConcurrency : MaxCount / ResolutionRule

自适应音乐:横向换曲,纵向加层

自适应音乐只有两种基本手法,混用是新人最常犯的错。

纵向分层(Vertical Layering):同一首曲子的若干条分轨(stem)同时循环,按强度逐层开关。鼓组进、弦乐退,一拍之内见效,听感是"音乐变厚了"。

横向重编排(Horizontal Re-sequencing):整段换曲子,A 段切 B 段。必须等小节边界,否则就是夜店 DJ 切歌切在半拍上。听感是"音乐翻篇了"。

判断标准:情绪渐变用纵向,叙事跳变用横向。实战里八成需求是纵向——战斗强度本身就是个连续量,硬把它切成"换歌",每一段都是事故。

维度纵向分层横向重编排
切换时机任意,一拍内必须等小节边界
听感变厚 / 变薄翻篇
素材成本一首曲 × N 条 stemN 首完整曲
适用战斗强度、潜行暴露度过场、Boss 阶段转换

状态机用一个数驱动:威胁值 Threat。规则可以很简单——敌人感知到玩家 +1,发生交火 +2,连续 5 秒无战斗事件 −1。聚合交给一个战斗管理器(单例子系统即可,GameState 那本账的记法见第 3 章),音乐层只做一件事:把 Threat 映射成强度参数写进 Audio Component。

stateDiagram-v2
    [*] --> 探索
    探索 --> 遭遇: Threat ≥ 1(敌人感知)
    遭遇 --> 激战: Threat ≥ 3 且持续 2s
    遭遇 --> 探索: 丢失目标 10s
    激战 --> 脱战: 敌全灭或拉开距离 5s
    脱战 --> 探索: 尾奏小节播完
    note right of 探索: 仅 Pad + 脉冲低音,强度层 1
    note right of 遭遇: 加打击乐铺底,强度层 2
    note right of 激战: 全编制 + 铜管断奏,强度层 3
    note right of 脱战: 退层 + 预制尾奏,禁止骤停
sequenceDiagram
    participant AI as 敌人AI感知
    participant CM as 战斗管理器
    participant AC as 音乐AudioComponent
    participant Q as Quartz时钟
    AI->>CM: 感知到玩家 Threat+1
    CM->>AC: SetFloatParameter Intensity=0.5
    AC->>Q: 预约下一拍点
    Q-->>AC: 拍点回调
    AC->>AC: 淡入打击乐铺底(1小节)
    AI->>CM: 交火 Threat+2
    CM->>AC: SetTriggerParameter EnterCombat
    AC->>Q: 预约最近小节边界
    Q-->>AC: 边界回调
    AC->>AC: 全编制切入 + 铜管断奏

时序上有一条铁律:纵向分层随时能动,但隆重进入(Boss 出场、阶段转换)值得等一拍——卡在拍点上的铜管,比立刻响起的铜管更有压迫感。Quartz 时钟就是干这个的:音乐组件发请求,它在下一个拍点或小节边界回调。

脱战是翻车重灾区。敌全灭、音乐骤停,玩家脑子里蹦出来的第一个词是"出 bug 了",而不是"安全了"。正确顺序:激战层先退到遭遇层,播放一个预制的尾奏小节,再让 Pad 接管渐弱。状态机里"脱战"这个中间态,就是为这段尾巴存在的。

素材账也要算。一首三分钟的双层曲目 = 每段至少两条 stem;立体声 48 kHz / 16 bit 未压缩约 10 MB/分钟,全部常驻内存是灾难。长音频一律开 Streaming,压缩交给平台编码格式(5.8 中对应官方文档的 Audio Compression 词条)。到了「远港 Online」,这套状态机的 Threat 输入会从单人感知换成团队仇恨汇总——状态机不变,数据源换一根管子,这是后话。

层数别贪。三层强度(探索/遭遇/激战)加一层 stinger,是中小团队的甜蜜点;四层往上,混音师要对每一层组合单独调平衡,工作量按组合数爆炸。stinger 是横向手里的轻武器:单发、不属于任何循环,用在爆头、开箱、剧情揭示——它不用等小节,因为它本来就不在小节里。

给作曲的交付要求要提前说清楚:所有 stem 同 BPM、同小节数、同起始点,分轨命名带层号。少任何一条,纵向分层在引擎里就是对不齐的一盘散沙,返工的是双方。

混音总线:枪声、对话、音乐、环境抢一条车道

混音的本质是仲裁:天花板只有 0 dBFS 一个,四类声音抢同一条车道。先给四类逻辑总线排优先级:

  1. 对话(Dialogue):最高优先。叙事与战斗信息的载体,混音的锚点。
  2. 关键 SFX(枪声、命中、低血警报):玩法反馈,慢一拍就糊。
  3. 音乐:情绪层,可以被压,不能被砍。
  4. 环境(Ambience):存在感靠"缺了才发现",优先级最低。

UE 里的落地是三件套,别混:Sound Class 管"谁跟谁一族、谁压谁"(层级结构 + 被动混音修饰);Submix 管路由与效果器链(混响、EQ、压缩挂在总线上);Control Bus(Audio Modulation 插件)管运行时的参数化调节。三者各司其职,才能把闪避做成"设计师可配、程序可触发"的数据,而不是写在某个蓝图角落里的魔法数。

闪避(Ducking)机制一句话:对话开口,音乐自动 −8 dB、环境 −4 dB,说完再缓回来。两个时间参数决定它是"电影感"还是"抽水泵":压下要快(0.1–0.2 秒,跟着台词起),恢复要慢(0.5–1 秒,等尾音走完)。压快放快就是 pumping,玩家会以为音响接触不良。侧链压缩(Sidechain Compression)是同一件事的自动化版本:对话总线的电平实时驱动音乐总线的压缩器;5.8 内置 Dynamics Processor 的侧链配置字段以官方文档词条为准,配不好就退回快照方案,不丢人。

flowchart TD
    GUN[枪声/命中 SFX] --> BX[Submix: SFX]
    STEP[脚步/拟音事件] --> BX
    DIA[对话] --> BD[Submix: Dialogue]
    MUS[自适应音乐] --> BM[Submix: Music]
    AMB[氛围床] --> BA[Submix: Ambience]
    BX --> MAS[Master Submix]
    BD --> MAS
    BM --> MAS
    BA --> MAS
    BD -. Ducking -8dB .-> BM
    BD -. Ducking -4dB .-> BA
    BX -. 激战快照 -2dB .-> BM
# 伪代码:对话触发的 Ducking 快照
# 载体三选一:SoundMix 快照 / Audio Modulation Patch / 侧链压缩
on dialogue_started(speaker):
    push_snapshot("Duck_ForDialogue")
    # 快照内容: Music 总线 −8 dB, Ambience −4 dB, SFX −2 dB
    # 淡入 0.15 s —— 跟着台词起,不能迟到

on dialogue_finished(speaker):
    schedule_pop("Duck_ForDialogue", delay=0.4)  # 尾音留 0.4 s
    # 淡出 0.6 s —— 恢复永远比压下慢,这是反抽吸的关键

混音工作流只有一条建议:先把对话响度定死当锚,其他三类向它对齐。反过来做——先嗨音乐再压对话——是新手项目最常见的混音事故。等进入「远港 Online」,这张总线表还要加一路语音聊天(VOIP),优先级直接插到对话同级,闪避逻辑原样复用。

响度标准也补一句:整局混音最终要过平台响度规范(主机平台普遍以约 −23 LUFS 为参考线,具体数值以各平台认证文档为准)。但混音阶段先别看表,先把对话锚定——表是最后一道工序,不是第一道。另外给爆点留头顶空间(headroom):爆炸瞬间要还能再响 3 dB,这段余量是戏剧性的存款。

总线之外还有一层保险:SoundBase 的 Priority 字段决定发声路数被挤爆时谁被挤掉。低血警报拉满,环境音垫底,其余按玩法反馈分级——这个字段平时没人看,挤爆那一次就是它说了算。

「回响峡谷」雨夜天台:一张能呼吸的声景清单

回到示例线。这场戏的任务:主角雨夜追人穿过天台,途中有檐下躲雨段、积水段,远处雷暴在逼近,风从破窗往里灌。第 10 章用光讲了孤独与紧张,这一节用声音把同一句话再说一遍。

三层拆完,清单如下(衰减距离单位:虚幻单位,100 = 1 米):

声音资产形态衰减空间化并发备注
雨声底氛围双声道循环,挂玩家组件无(随听者)无,宽床常驻 1 路0.1 Hz LFO 让响度缓慢呼吸
远处雷拟音one-shot,8–20 s 随机NaturalSound,内 2000 / 衰减 8000Binaural,方位天穹随机MaxCount 2,StopOldest空气吸收拉闷远雷
脚步(干/湿)拟音MetaSounds 双组样本NaturalSound,内 150 / 衰减 1200BinauralMaxCount 4,StopFarthestThenPreventNew积水深度参数 crossfade;湿步 30% 概率叠溅水高频层
风穿缝氛围(点源)循环 + 随机音高Logarithmic,内 80 / 衰减 900空间化 + 窄聚焦锥对准窗缝每缝 1 路,共 ≤3走过窗缝那一下"扫耳"是报酬
檐口滴水拟音one-shot,1–4 s 随机NaturalSound,内 50 / 衰减 600BinauralMaxCount 6檐下区域的"湿度证据"
城市低频底氛围双声道循环常驻 1 路−24 dBFS 上下;关掉才发现"世界死了"

檐下与露天的切换不靠音量,靠两条通道:Audio Gameplay Volume 框出檐下区域,进入时把雨声层的低通压下来(雨变成"隔了一层")、Reverb 湿声从 10% 提到 30%;回露天反向。玩家听到的是"我躲进来了",而不是"雨小了"。

雷暴逼近交给一个全局 Intensity:天气状态机(数据层天气的做法见第 16 章)把逼近程度写成 0–1,MetaSounds 图里它同时控制远雷的触发间隔(20 s 收紧到 6 s)与低通截止(闷雷变炸雷)。一条参数,管三个听感维度。

并发预算顺带记一笔:这张清单全开约 15 路发声(雨底 1 + 雷 ≤2 + 脚步 ≤4 + 风 ≤3 + 滴水 ≤6 + 低频底 1),离引擎默认发声上限还很远。但把同一套思路搬进「远港 Online」的主城,50 个 NPC 的脚步加叫卖瞬间顶到天花板——并发资产里的 ResolutionRule 之所以存在,就是因为大场景需要一根保险丝,它从来不是装饰。

// 常驻雨声层:按"檐下/露天"实时改写 MetaSounds 参数
void URainLayerComponent::SetSheltered(bool bSheltered)
{
    if (!RainComponent) { return; }

    // 5.8 中参数写入对应官方文档词条:
    // Audio Component → Set Bool / Float / Trigger Parameter
    RainComponent->SetBoolParameter(TEXT("bSheltered"), bSheltered);
    RainComponent->SetFloatParameter(TEXT("Intensity"),
        bSheltered ? 0.35f : 1.0f);
}

要点:常驻层必须用 UAudioComponent 持有,临时 one-shot 写不了参数;bSheltered 在图内驱动低通与混响发送,C++ 只管报状态,不管听感细节——听感归图管,状态归代码管,分工别乱。

最后做闭眼测试:闭眼只听,能不能说出空间多大、天气如何、檐口在哪、雷还有多远。听单有四条:一,雨底的 LFO 有没有被听出来——被听出来就是失败,呼吸感要停留在潜意识层;二,从露天走进檐下,像不像"推门进屋",而不是"有人拉了音量推子";三,连续两分钟,有没有哪个循环能被你数出拍子;四,闷雷与炸雷的距离感有没有拉开。哪条挂了,回去改清单,别动音量——声景的问题九成在结构,不在响度。

小结

声音是沉浸感里被克扣的一半预算:3 到 8 人的音频组扛玩家一半的氛围体验,越早进管线越省钱。声景三层各司其职:拟音是事件、氛围是常驻服务、音乐是状态机;混为一层,后期必然打架。dB 是对数尺:同种声源叠加收益递减,"一直响"等于"没有响"——警报要会闭嘴,氛围要会呼吸。MetaSounds 把声音从"被触发"变成"被驱动":参数接口加 Quartz 时钟,是自适应音乐的地基。混音先定对话为锚,Ducking 压快放慢;天花板只有一个,优先级表比音量推子重要。

上手任务

  1. 干湿脚步:给「回响峡谷」主角做干/湿两组脚步样本,积水深度参数 crossfade,随机音高 ±8%,每组不少于 6 个变体。验收:闭眼连走 30 秒,能明确分辨踩水与干地,且听不出样本重复。
  2. 两层自适应音乐:自定 Threat 聚合规则,实现探索/激战两层纵向切换。验收:Threat 拉满后 1 拍内鼓组进入;敌全灭后尾奏小节完整播完才回探索层,全程无骤停。
  3. 对话 Ducking:对白触发音乐 −8 dB、环境 −4 dB。验收:压下不超过 0.2 秒、恢复不短于 0.5 秒,录音波形上看不到台阶,连续 5 次对白无抽吸感。

下一章

声音把情绪讲完了,画面还差一个拿导筒的人。第 15 章 Sequencer 与镜头语言:把"播放动画"升级成"导演一场戏"。

延伸阅读

  • 官方文档:MetaSounds Overview / MetaSounds Reference(图、参数接口与节点库)
  • 官方文档:Sound Attenuation、Audio Gameplay Volumes、Audio Modulation、Quartz
  • 源码:Engine/Source/Runtime/Engine/Classes/Sound/SoundAttenuation.h——衰减资产全部字段与注释
  • 源码:Engine/Source/Runtime/Metasound——MetaSounds 运行时与节点实现
  • GDC Vault:Naughty Dog 关于《最后生还者》声景与混音的分享(方向:叙事驱动混音);Hellblade 团队关于双耳录音(Binaural Audio)的分享(方向:耳机玩家的空间感)