Phaser 潜行视野系统:视野锥、遮挡检测和警戒状态要一起算

讲解 Phaser 潜行动作游戏中的敌人视野锥、遮挡检测、警戒状态机、灯光提示和调试工具,帮助实现可信的潜行体验。

潜行玩法最怕玩家觉得“不公平”

潜行动作游戏里,敌人发现玩家是一件非常敏感的事。玩家可以接受被发现,但必须知道为什么被发现:我站在灯下、走进视野锥、发出了声音、碰到了警戒线。如果敌人隔墙看到玩家,或者玩家明明在阴影里却瞬间暴露,潜行感会立刻崩。Phaser 做 2D 潜行原型并不难,难的是让视野、遮挡、灯光和警戒状态形成一致规则。

很多项目一开始只做一个距离判断:玩家离敌人小于 200 像素就被发现。后来加上朝向、墙、草丛、灯光、噪音,判断就变成一串互相打架的 if。更可靠的做法是把感知系统拆成多个输入:视觉、听觉、环境隐蔽值、剧情警戒等级。每个输入给出证据,警戒状态机根据证据累积怀疑度。这样玩家短暂露头不会立刻失败,敌人也不会像开透视。

视野锥只是第一层

视野锥通常由位置、朝向、角度和距离组成。玩家在扇形范围内,才有可能被看见。但“在视野锥内”不等于“已发现”。还要检查遮挡、光照、玩家姿态、移动速度和敌人警觉程度。比如玩家蹲在暗处,发现速度应该慢;玩家奔跑穿过灯光,发现速度很快;敌人处于搜寻状态时,同样条件下更容易发现。

Phaser 中可以用几何计算判断扇形范围,用 tilemap collision layer 或自定义阻挡线做射线遮挡。视野锥的可视化可以用 Graphics 绘制半透明多边形,开发模式显示真实判定范围,正式模式可以只显示敌人手电筒光束或地面提示。注意,表现范围和判定范围要尽量一致,否则玩家会认为被冤枉。

flowchart TD
  A["EnemySensor 每 tick 采样"] --> B["VisionCone:角度和距离检测"]
  B --> C["LineOfSight:墙体/门/遮挡检测"]
  C --> D["VisibilityModel:光照、草丛、姿态修正"]
  D --> E["SuspicionMeter:累积或衰减怀疑值"]
  E --> F{"状态切换"}
  F -- "低" --> G["Patrol 巡逻"]
  F -- "中" --> H["Investigate 搜寻"]
  F -- "高" --> I["Alert 追击/报警"]
  J["NoiseEvent 脚步/投掷物"] --> E

遮挡检测要保守

遮挡检测可以用射线从敌人眼睛位置打到玩家关键点。关键点不要只取玩家中心,因为半个身体露出墙角时应该被看到。可以取头部、身体中心、脚部或左右肩几个点,只要有足够点可见,就增加怀疑值。反过来,不要因为一个像素点露出就瞬间发现。潜行游戏需要“宽容但可信”的判断。

如果地图使用 Phaser Tilemap,可以预先把阻挡 tile 转成矩形或线段。每次检测时,只检查敌人附近的阻挡物,避免全图遍历。对于移动门、箱子、可破坏障碍,也要进入遮挡查询。开发模式下必须画出射线,显示哪些点被挡住。没有可视化,潜行视野 bug 很难排查。

怀疑值比瞬间发现更自然

多数潜行体验都需要怀疑值。玩家短暂穿过视野,敌人头上出现问号;玩家继续暴露,问号变成感叹号;玩家躲起来,怀疑值慢慢下降,敌人走到最后看到的位置搜索。这比一进视野就失败自然得多。怀疑值的增长速度由可见程度决定:距离越近、光照越强、玩家越吵、敌人越警觉,增长越快。

状态机可以分为 patrolsuspiciousinvestigatealertreturning。每个状态有不同感知倍率和行为。巡逻时敌人按路线走;怀疑时停下观察;搜寻时走向最后可疑位置;警戒时追击或呼叫同伴;返回时回到巡逻路线。状态机和传感器要解耦:传感器只提供证据,状态机决定行为。

一个视野检测函数

下面的代码展示基础视野判断。它只做角度、距离和遮挡接口,光照和怀疑值在上层处理。

interface Vec2 { x: number; y: number }

interface VisionConfig {
  distance: number;
  halfAngleRad: number;
}

export function canSeePoint(
  eye: Vec2,
  facingRad: number,
  target: Vec2,
  config: VisionConfig,
  blocked: (from: Vec2, to: Vec2) => boolean,
) {
  const dx = target.x - eye.x;
  const dy = target.y - eye.y;
  const dist = Math.hypot(dx, dy);
  if (dist > config.distance) return false;

  const targetAngle = Math.atan2(dy, dx);
  const diff = Math.atan2(Math.sin(targetAngle - facingRad), Math.cos(targetAngle - facingRad));
  if (Math.abs(diff) > config.halfAngleRad) return false;

  return !blocked(eye, target);
}

这里使用 atan2(sin, cos) 归一化角度差,避免朝向跨过 π 时出错。真实项目中,可以对玩家多个点调用这个函数,计算可见点比例。比例越高,怀疑值增长越快。

光照和阴影是修正项

潜行游戏常用光照表达安全与危险。Phaser 可以用 Light2D、RenderTexture 或简单区域标记实现。不要把光照只当作视觉效果。每个位置可以有一个 visibility multiplier:明亮区域为 1.2,普通区域为 1,阴影为 0.4,草丛为 0.2。敌人视觉证据乘以这个值后进入怀疑值。这样玩家会自然理解躲进暗处更安全。

但光照规则要清楚。阴影不能让玩家在敌人贴脸时完全隐身;草丛不能挡住奔跑噪音;强光下也不应隔墙发现。建议设置最低和最高修正边界。UI 上可以给玩家一个轻量隐蔽指示,比如角色脚下小图标或 HUD 上的曝光条。不要让玩家猜自己当前是否安全。

噪音系统补足视觉盲区

潜行不只有视觉。奔跑、翻滚、开门、投掷物、枪声都可以产生噪音事件。噪音事件包含位置、半径、强度和持续时间。敌人听到后不一定立刻发现玩家,而是进入搜寻或转头。噪音能制造玩法:玩家投掷瓶子引开守卫,故意触发机关制造空档。声音证据也进入同一个怀疑系统,只是来源不同。

噪音需要被墙体衰减。简单做法是按距离衰减,再用墙体数量或房间连通关系降低强度。不要让敌人隔着三堵墙听到轻微脚步。开发模式下可以画出噪音圆和实际影响到的敌人,方便调参。

警戒传播要有限制

一个敌人发现玩家后,是否全图报警?这取决于游戏设计。更可信的做法是警戒传播有范围、媒介和延迟。敌人需要呼喊、按警报器或跑去通知同伴。附近敌人进入搜寻,远处敌人保持巡逻。这样玩家还有补救空间。全图瞬间报警虽然简单,但会让潜行变成一次失误就重开。

Phaser 实现上,可以有一个 AlertService 处理警戒广播。敌人进入 Alert 时发出事件,服务根据距离、房间、通信设备把状态传播给其他敌人。剧情关卡可以配置特殊规则,比如军事基地警报器会全区报警,普通仓库只会局部警戒。

UI 提示要诚实但不剧透

敌人头上的问号、感叹号、视野锥颜色变化都能帮助玩家理解状态。提示要表达规则,但不要过度精确到破坏紧张感。比如怀疑值可以用图标填充表达,而不是显示 73%。视野锥在简单难度可以常显,在高难度可以只显示手电范围。玩家受环境隐蔽影响时,可以用轻微遮罩或角色轮廓变化提示。

被发现瞬间,要显示最后触发原因:被 3 号守卫看到、奔跑声惊动、触发摄像头。这个反馈对学习很重要。没有原因的失败会让玩家认为系统不公平。

上线前检查清单

确认视野检测包含距离、角度和遮挡;确认遮挡射线在开发模式可视化;确认玩家多个关键点参与检测;确认怀疑值有增长和衰减,不是瞬间发现;确认光照、草丛和姿态是修正项;确认噪音事件进入同一警戒系统;确认警戒传播有范围和规则;确认提示 UI 能解释被发现原因;确认正式表现范围和判定范围一致;确认低帧率下感知按固定 tick 或稳定 delta 更新。

潜行系统的目标不是让敌人聪明到完美,而是让玩家相信规则可学。Phaser 能很快画出视野锥和巡逻路线,但真正的潜行感来自遮挡、怀疑、光照和反馈之间的一致性。只要玩家能说出“我刚才为什么被发现”,他就愿意再试一次。

巡逻路线和视野节奏

敌人的巡逻路线不是路径点连线那么简单。每个路径点可以配置停留时间、朝向、观察角度和听觉倍率。比如守卫走到窗边会停 2 秒向外看,走到货架旁会转身检查阴影。这样视野变化有节奏,玩家可以观察并计划。若所有敌人匀速循环,潜行会变成机械背板;若敌人随机乱转,玩家会觉得不可预测。好的巡逻介于两者之间:核心节奏稳定,细节有轻微变化。

Phaser 中可以把巡逻点放在 Tilemap 对象层里,导出时带上 waitMslookDiralertness 等属性。敌人 AI 读取这些点,而不是在代码里写死路线。开发模式下显示路线、当前目标点、等待倒计时和下一次转身方向。关卡设计师看到这些信息,才能调整潜行路线的节奏。

摄像头和固定机关

潜行关卡不只有敌人。摄像头、激光、压力板、警报门都可以使用同一套感知模型,只是行为不同。摄像头没有听觉,但视野稳定;激光不累积怀疑值,而是触发机关;警报门只检测玩家是否带着禁物通过。统一抽象为 Sensor,能减少特殊代码。Sensor 输出证据或事件,AlertService 决定警戒传播。

固定机关的可读性更重要。摄像头旋转范围要清楚,激光开关节奏要可观察,警报门要有明显标识。不要让玩家在不知道规则的情况下被惩罚。第一次出现新机关时,可以降低惩罚或放在安全区域教学。潜行玩法的公平感来自“先展示规则,再考验执行”。

性能和采样频率

每个敌人每帧对玩家多个点做射线检测,在敌人数多时会有成本。可以按固定频率采样,比如每 100ms 更新一次感知;高警戒状态下提高频率,远离玩家的敌人降低频率。视觉表现仍然每帧更新,但真正的感知判断不必每帧跑。对玩家来说,100ms 的感知粒度通常足够自然。

还可以先做粗筛:距离过远直接跳过,角度不在范围内跳过,再做遮挡射线。多个敌人共享静态遮挡索引,避免重复构造数据。Phaser 项目常把性能注意力放在渲染上,但复杂潜行关卡里,AI 感知也可能成为瓶颈。

存档和重试的体验

潜行关卡通常需要重试。自动存档点应该放在安全区域,而不是玩家已经被发现的瞬间。若玩家失败后重来,敌人的警戒状态、巡逻位置、门锁状态、可拾取道具都要恢复到合理快照。不要只把玩家传回入口,却保留敌人全体警戒,否则重试会变成惩罚。关卡开始、进入关键区域、完成阶段目标时都可以保存检查点。

检查点还要记录随机因素。如果巡逻有轻微随机停顿,重试时可以保持同一随机种子,让玩家能学习路线;也可以重新随机,但必须保证不会出现无解组合。硬核潜行更适合同种子重试,休闲潜行可以给一些变化。无论选择哪种,都要写进设计规则。

与关卡美术的协作

潜行可读性很依赖美术。墙体、半墙、玻璃、草丛、阴影、可穿过装饰都要有明确视觉语言。程序上半墙可能挡移动但不挡视线,玻璃挡移动但不挡视线,草丛不挡视线但降低可见度。如果美术表现不区分这些类型,玩家会困惑。Tile 或对象数据里应有 blocksMovementblocksVisionvisibilityModifier 这类字段,关卡美术按规则放置。

发布前可以做“无美术模式”测试:只显示碰撞、视野、遮挡和隐蔽值。若在无美术模式下规则成立,再检查美术是否正确表达这些规则。这样能分清是系统 bug,还是视觉语言误导。

继续阅读

探索更多技术文章

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

全部文章 返回首页