Phaser 潜行声音传播:噪声半径、材质衰减和敌人听觉

拆解潜行游戏中的声音事件、传播衰减、材质影响、敌人听觉状态、误导道具和调试可视化。

为什么值得单独做成系统

潜行关卡里,玩家踩过碎玻璃,走廊尽头的守卫转头;玩家扔出硬币,另一个房间的守卫去调查;雨声很大时,同样的脚步声不再暴露位置。声音成为玩法信息,而不只是音效。

声音潜行不能只给敌人一个固定半径。地面材质、门、墙、环境噪声、角色姿态和敌人警觉度都会影响听觉。系统要把声音事件数据化,敌人 AI 再根据听到的强度和可信度做反应。 本文会按一个可上线的小系统来拆,不追求炫技,而是把数据结构、状态流、玩家反馈、调试工具和发布检查说清楚。Phaser 的优势是让画面和交互快速成型,但越是快速,越需要把规则层和表现层分开。

核心架构

flowchart TD
  N1["SoundEvent"] --> N2["PropagationSolver"]
  N3["MaterialMap"] --> N2["PropagationSolver"]
  N2["PropagationSolver"] --> N4["ListenerProfile"]
  N4["ListenerProfile"] --> N5["SuspicionState"]
  N5["SuspicionState"] --> N6["InvestigationTask"]
  N2["PropagationSolver"] --> N7["DebugRings"]
  N6["InvestigationTask"] --> N8["EnemyAI"]

这张图的重点是单向流动。SoundEvent、MaterialMap、PropagationSolver、ListenerProfile、SuspicionState、InvestigationTask、DebugRings 不应该互相随意读写。输入或场景事件进入模型,模型输出快照或事件,Phaser 表现层再根据结果更新 Sprite、Graphics、Sound 和 UI。只要这条边界稳定,后续加内容、加难度、加存档或加多人同步,都不会把系统推倒重写。

声音事件要有语义

脚步、落地、开门、枪声、硬币、玻璃碎裂都不是同一种声音。SoundEvent 应记录位置、基础强度、类型、持续时间、是否可疑、是否可伪装。敌人听到枪声和硬币的反应不同:前者可能进入战斗,后者更像调查。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

材质衰减决定可信度

同样的脚步声,木地板、地毯、金属桥和积水地面强度不同。MaterialMap 可以给当前位置提供 noiseMultiplier。传播过程中经过墙或门也会衰减。第一版不必做真实声学,可以用导航网格距离加遮挡惩罚,得到足够可解释的结果。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

环境噪声是掩护

雨声、机器运转、警报和人群嘈杂可以提高听觉阈值。ListenerProfile 里记录当前环境噪声和敌人专注度,PropagationSolver 输出的强度低于阈值就不触发。这样玩家能利用环境,而不是永远被固定半径惩罚。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

敌人反应要分层

听到微弱声音可以转头,较强声音可以走过去调查,极强或危险声音进入警戒。SuspicionState 累积一段时间,而不是一次声音立刻满警戒。多个小声音连续出现,敌人会越来越怀疑;长时间没有新声音,怀疑逐渐下降。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

诱饵道具需要来源标记

玩家扔硬币是合法误导,但敌人不应该永远被骗。SoundEvent 可以带 source: decoy,并给敌人记忆。第一次硬币效果强,短时间内重复硬币效果下降。这样诱饵仍有用,但需要玩家思考时机。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

调试环要显示衰减

开发模式用圆环显示基础半径、实际传播范围、被墙衰减后的强度和每个敌人的听觉阈值。潜行声音 bug 很难靠耳朵判断,因为玩家听到的音效不等于敌人听到的事件。可视化能把问题拆清楚。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

音效播放和规则可分离

玩家耳朵听到的声音由 Sound Manager 播放,敌人听觉由 SoundEvent 系统处理。某些 UI 音效不应被敌人听到,某些规则声音可以不播放给玩家。把两者分开,才能避免换音效资源时改变潜行平衡。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

TypeScript 实现骨架

type SoundKind = "footstep" | "coin" | "glass" | "gunshot" | "door";
interface SoundEvent { kind: SoundKind; x: number; y: number; strength: number; suspicious: boolean }
interface Listener { id: string; x: number; y: number; threshold: number; alertness: number }
function heardStrength(event: SoundEvent, listener: Listener, materialPenalty: number) {
  const d = Phaser.Math.Distance.Between(event.x, event.y, listener.x, listener.y);
  const distanceLoss = d / 420;
  return Math.max(0, event.strength - distanceLoss - materialPenalty - listener.threshold);
}
function classifyReaction(value: number) {
  if (value > 0.75) return "alert";
  if (value > 0.35) return "investigate";
  if (value > 0.08) return "turn-head";
  return "ignore";
}
function updateSuspicion(current: number, heard: number, dt: number) {
  return Phaser.Math.Clamp(current + heard * dt / 1000 - 0.08 * dt / 1000, 0, 1);
}

这段代码只展示核心边界,不是完整项目代码。真实项目里还需要补配置加载、错误码、事件派发、对象池、性能采样和测试。关键是让核心规则能独立运行,Phaser 层只是把规则结果变成玩家能感知的反馈。

落地步骤

  1. 第一,先把 SoundEvent 和 MaterialMap 写成普通 TypeScript 模型。不要让它们依赖 Phaser Scene、Sprite 或 Camera。核心模型越普通,越容易写测试、做编辑器预览和复现玩家问题。
  2. 第二,Phaser 层只做适配:接收输入、播放动画、更新图形、触发音效。它可以很薄,但必须清楚。只要某段规则开始读取 Sprite 的 visible、alpha 或动画状态,就说明边界正在变脏。
  3. 第三,给 PropagationSolver 或同等复杂的中间结果做调试显示。开发模式里能看到状态、阈值、候选对象、失败原因和耗时,后续调参才不会靠猜。
  4. 第四,准备三组测试夹具:正常流程、边界流程、错误配置。正常流程验证体验,边界流程验证稳定性,错误配置验证系统会报出人能看懂的问题。

检查清单

  • 确认 SoundEvent 的状态可以序列化,能写入存档或调试日志。
  • 确认 MaterialMap 的配置有默认值、版本号和校验错误。
  • 确认快速点击、暂停、切后台、读档和切场景不会重复提交关键事件。
  • 确认失败反馈足够具体,玩家能知道是条件不足、输入中断、资源不够还是规则禁止。
  • 确认低端机有降级策略,尤其是粒子、音效、动态对象和调试图层。
  • 确认开发模式可以导出最近关键事件,方便复现玩家反馈。

常见误区

第一类误区,是把表现当成事实。动画播完、按钮亮着、Sprite 存在,都只能说明表现层当前长什么样,不能说明规则已经完成。规则事实应该存在于模型和事件里。

第二类误区,是只为了第一个关卡写逻辑。第一个关卡对象少、输入慢、节奏简单,临时判断很难暴露问题。等内容增加,重复触发、配置错误和性能峰值会一起出现,早期的边界会决定后期成本。

第三类误区,是没有设计失败路径。复杂系统一定会遇到失败,好的失败路径会告诉玩家和开发者发生了什么;坏的失败路径只会留下一句操作失败,甚至什么都不显示。

发布前验证

发布前至少跑一次规则级测试和一次运行时冒烟。规则级测试不需要启动浏览器,直接喂数据,断言状态和事件。运行时冒烟则在 Phaser 场景里验证输入、反馈、暂停、重开和边界情况。若系统涉及经济、存档或排行榜,还要记录 requestId 或事件 id,保证重复提交不会造成重复奖励或重复扣费。

额外实践建议

  • 规则声音和播放音效要分开。
  • 每种材质的噪声倍率要能在调试面板里显示。
  • 敌人听觉阈值要按状态变化,站岗、巡逻、搜索不能完全一样。

运行时观测与调参

声音潜行最怕玩家觉得“不公平”。所以每次敌人进入调查或警戒,最好记录触发它的 SoundEvent:声音类型、位置、最终强度、材质惩罚、环境噪声、敌人阈值和反应等级。开发包里可以在敌人头顶短暂显示“听到玻璃声 0.42”,正式版则只保留内部日志。若玩家反馈隔墙被听见,日志能说明是墙体没有配置衰减,还是敌人本来就在门口。调参时也不要只改全局半径,应该按材质、姿态和敌人状态分别调。潜行系统的可信度来自可解释,而不是永远降低难度。

还有一个容易忽略的点是声音优先级。敌人同一秒可能听到脚步、硬币和门声,AI 不应同时启动三个调查任务。可以按危险程度、距离和新鲜度选一个主事件,其余事件作为怀疑加成。这样敌人行为更像判断,而不是被每个声音来回拉扯。

结语

潜行声音传播:噪声半径、材质衰减和敌人听觉 的关键不是某个 API,而是把可解释的规则交给模型,把可感知的反馈交给 Phaser。只要这条线清楚,项目就能持续扩展;如果所有逻辑都塞进 Scene 回调,第一版越快,后面的维护压力越大。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页