Phaser 恐怖游戏理智值系统:视野扰动、音画反馈和恢复边界

讲解恐怖游戏中的理智值、能见度、幻觉表现、音频压迫、恢复规则、无障碍选项和状态校验。

为什么这个系统值得单独设计

一款俯视角恐怖解谜游戏里,玩家拿着手电穿过废弃医院。停留在黑暗里越久,画面边缘开始收缩,远处传来低频噪声,墙上的影子似乎移动了。系统要制造压迫感,但不能让玩家误以为游戏坏了。

理智值不是一条扣血条。它连接光照、音频、可见度、输入反馈、幻觉事件、恢复道具和无障碍设置。若所有效果都直接读 sanity 数字,项目后期会很难调。需要把压力来源、状态计算和表现层分开。 本文按实际项目会遇到的问题来拆,不停留在“能跑”的 Demo 层。重点会放在数据边界、状态流、玩家反馈、调试方式和后续维护成本上。Phaser 很适合快速做出手感,但越是能快速表现,越需要把规则层写清楚。

核心架构

flowchart TD
  N1["StressSource"] --> N2["SanityModel"]
  N2["SanityModel"] --> N3["VisibilityProfile"]
  N2["SanityModel"] --> N4["HallucinationQueue"]
  N2["SanityModel"] --> N5["AudioPressure"]
  N6["RecoveryRule"] --> N2["SanityModel"]
  N7["AccessibilityGate"] --> N3["VisibilityProfile"]
  N7["AccessibilityGate"] --> N5["AudioPressure"]

这套结构的原则是单向流动:输入或场景事件进入 StressSource,核心模型完成计算,再由 Phaser 表现层消费结果。SanityModel、VisibilityProfile、HallucinationQueue、AudioPressure、RecoveryRule、AccessibilityGate 都应尽量保持可序列化、可测试、可回放。不要让某个 Tween 完成回调、某个 Sprite 是否可见、某个按钮是否高亮成为玩法事实。

压力来源要可解释

黑暗、低血量、追逐、剧情物件、诅咒区域都可能降低理智值。每个 StressSource 应有强度、范围、衰减和优先级。SanityModel 汇总这些来源,输出当前值和主要原因。调试面板显示来源贡献,策划才能知道玩家为什么在某个走廊掉得太快。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

表现层不要直接扣状态

画面扭曲、视野收缩、字幕抖动和音频压迫都只是 sanity 的表现。它们不能反过来修改 sanity。否则某个特效关闭后,规则也变了。SanityModel 只根据压力来源和恢复规则变化,VisibilityProfile 和 AudioPressure 订阅它。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

幻觉事件要排队

恐怖游戏常用一闪而过的影子、门把手声、墙面文字变化。不要每次 sanity 低就随机触发。HallucinationQueue 根据冷却、区域、剧情进度和强度排队,保证事件之间有间隔,也避免关键解谜时连续干扰。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

可见度变化要有底线

视野收缩能制造紧张,但不能让玩家完全看不清交互物。VisibilityProfile 应设置最低可见半径、关键物高亮保护和 UI 不受影响区域。低理智状态可以降低边缘清晰度,但主交互区仍应可读。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

恢复规则要防刷

站在灯下、使用药品、听到安全音乐都可以恢复理智。恢复不能瞬间清空压力,否则玩家会在门口反复进出刷状态。RecoveryRule 可以有延迟、上限和来源互斥:安全屋恢复快,普通灯光只减缓下降,道具提供一次性恢复。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

无障碍选项不是删体验

强烈闪烁、画面扭曲和低频声音可能让部分玩家不适。提供 reducedHorrorFX、disableFlash、audioComfort 等选项。开启后仍保留玩法信息,例如用颜色、图标或轻微暗角表达压力,而不是完全隐藏理智系统。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

存档恢复要避免突袭

读档后如果立刻恢复到低理智和高压音频,玩家可能还没准备好。可以在读档后的几秒内平滑恢复表现,但规则值保持不变。这样既不破坏难度,也避免加载完成的一瞬间产生不必要惊吓。

落地时可以先用最朴素的调试图形验证规则,再替换成正式美术。这个顺序很重要:如果规则层还没稳定就开始堆特效,后面每一次调参都会同时牵动动画、音效和 UI,问题会变得难以判断。

TypeScript 实现骨架

interface StressSource { id: string; strength: number; remainingMs: number; reason: string }
interface SanitySnapshot { value: number; dominantReason?: string; pressure: number }
class SanityModel {
  private value = 1;
  private sources = new Map<string, StressSource>();
  addSource(source: StressSource) { this.sources.set(source.id, source); }
  update(dt: number, recovery: number): SanitySnapshot {
    let pressure = 0;
    let dominant: StressSource | undefined;
    for (const source of this.sources.values()) {
      source.remainingMs -= dt;
      if (source.remainingMs <= 0) { this.sources.delete(source.id); continue; }
      pressure += source.strength;
      if (!dominant || source.strength > dominant.strength) dominant = source;
    }
    this.value = Phaser.Math.Clamp(this.value + recovery * dt / 1000 - pressure * dt / 1000, 0, 1);
    return { value: this.value, dominantReason: dominant?.reason, pressure };
  }
}
function visibilityRadius(sanity: number, reducedFx: boolean) {
  const min = reducedFx ? 210 : 150;
  return Phaser.Math.Linear(min, 420, sanity);
}

这段代码不是完整框架,而是把关键边界先立出来。实际项目里应继续补上配置加载、错误码、事件派发、性能统计和单元测试。只要骨架保持清楚,后续接入 Phaser 的 Graphics、Sprite、Matter、Tilemap 或 Sound 都不会污染规则层。

具体落地步骤

  1. 第一步,把 StressSource 和 SanityModel 从 Scene 中拆出来,写成可以直接用 TypeScript 调用的模型。这个模型只接收普通对象,不接收 Sprite、Camera 或 Tween。只要这一步做到,后面的测试、调试、存档和工具预览都会简单很多。
  2. 第二步,在 Phaser Scene 里建立很薄的适配层。输入事件、物理回调、计时器和资源加载都可以在适配层发生,但它们只提交意图,不直接改核心状态。核心系统产出快照后,适配层再更新显示对象、音效、粒子和 HUD。
  3. 第三步,给每个关键状态准备调试可视化。不要等 QA 报问题才补日志。开发模式下至少能看到当前状态、最近输入、失败原因、候选列表、耗时和重要阈值。对复杂玩法来说,能看见中间状态比多写一层封装更重要。
  4. 第四步,用三类样例保护系统:正常流程、边界流程、错误配置。正常流程证明体验能跑通,边界流程证明快速输入、暂停、切场景和重复触发不会破坏状态,错误配置证明系统会给出明确报告,而不是静默失败。

项目检查清单

  • 确认 StressSource 的输入输出能被 JSON 记录,便于复现玩家操作。
  • 确认 SanityModel 的配置有默认值、版本号和校验错误信息。
  • 确认快速点击、暂停、切后台、重开场景和读档不会重复提交关键状态。
  • 确认失败反馈比成功反馈更具体,玩家能理解自己为什么没有成功。
  • 确认低端机或高负载场景有降级策略,而不是等帧率下降后再猜瓶颈。
  • 确认调试面板能在不改代码的情况下打开,并能导出最近关键事件。

常见误区

第一类误区,是把 Phaser 的显示对象当成状态来源。显示对象适合表达结果,却不适合保存规则事实。它可能被对象池回收、被摄像机隐藏、被动画临时修改,也可能因为画质档变化而不存在。核心状态必须独立存在。

第二类误区,是只为当前关卡写逻辑。当前关卡对象少、节奏慢、输入简单,临时判断看起来没有问题。等到内容增加、节奏加快、平台变多,临时逻辑会互相覆盖。每个系统至少要提前考虑配置错误、重复触发和性能上限。

第三类误区,是没有把失败当成流程设计。复杂系统一定会失败:条件不满足、资源缺失、网络超时、玩家中断、配置非法。失败不应该只是 console 里的一行错误,而应该是玩家、QA 和内容团队都能理解的状态。

音画压迫要给玩家出口

恐怖体验不能一直把压力推到最高。SanityModel 可以把压力分成 build、peak、release 三段,表现层按阶段调暗角、噪声和音量。release 阶段不一定安全,但要让玩家感觉自己重新掌握了局面,例如手电恢复稳定、脚步声重新清楚、UI 抖动停止。没有出口的压迫只会让人疲劳,尤其在解谜关卡里,玩家需要安静地观察线索。

关键交互物要被保护

低理智状态下可以隐藏远处细节,却不能隐藏完成关卡所必需的信息。每个关键交互物可以带 criticalVisibility 标记,VisibilityProfile 在极低理智时仍给它保留最小对比度或轮廓提示。这不是降低恐怖感,而是避免玩家把视觉惩罚误解为设计不公平。恐怖游戏越强调氛围,越要保护规则信息的可读性。

结语

恐怖游戏理智值系统:视野扰动、音画反馈和恢复边界 的难点不在某个 API,而在边界。把数据、规则、表现和调试分开后,Phaser 的优势会更明显:你可以很快做出反馈,也可以放心迭代规则。反过来,如果所有逻辑都散落在 Scene 的回调里,第一版越快,后续越难维护。

额外实践建议

  • 理智系统要先做调试数值,再做画面压迫,否则很难判断恐怖感来自规则还是特效。
  • 所有强刺激表现都应该有配置开关,并在同一条 AccessibilityGate 下控制。
  • 低理智不应阻止玩家理解目标,压迫和可读性必须一起调。

继续阅读

探索更多技术文章

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

全部文章 返回首页