Phaser 餐厅座位与寻路:排队、入座、服务路径和拥堵处理

拆解餐厅经营游戏里的顾客排队、桌位分配、服务员寻路、拥堵处理、耐心值和布局调试。

为什么这个玩法不能只写成演示

餐厅经营游戏中,顾客进门排队,领位员安排桌位,服务员端菜穿过走道。玩家摆放桌椅后,餐厅可能变得拥挤,某张桌子看似空着,却因为路被堵住无法服务。

餐厅座位系统连接布局、寻路、队列、顾客耐心和评分。它不能只找一个空桌子,还要确认顾客能走到、服务员能服务、离厨房路径合理。Phaser 的表现层要清楚展示这些规则。 本文按一个可上线的小系统拆解,重点不是罗列 Phaser API,而是把输入、规则、表现、调试和内容配置的边界说明白。只要这些边界清楚,后续加关卡、加活动、加存档或加移动端适配,都不会反复推倒。

核心架构

flowchart TD
  N1["GuestQueue"] --> N2["SeatAllocator"]
  N3["TableRegistry"] --> N2["SeatAllocator"]
  N4["WalkGraph"] --> N2["SeatAllocator"]
  N2["SeatAllocator"] --> N5["ServerRoute"]
  N5["ServerRoute"] --> N6["CongestionMonitor"]
  N6["CongestionMonitor"] --> N7["ServiceFeedback"]
  N7["ServiceFeedback"] --> N8["HUD"]

这套结构的关键是让 GuestQueue、TableRegistry、SeatAllocator、WalkGraph、ServerRoute、CongestionMonitor、ServiceFeedback 各司其职。输入层提交意图,规则层产出确定结果,Phaser 层负责把结果演出来。不要让 Tween 完成回调、Sprite 是否可见或某个音效是否播放成为规则事实。规则事实必须能被序列化、测试和回放。

桌位不是空格子

TableRegistry 记录桌子 id、座位数、入口点、服务点、是否清理、是否预订。顾客人数要匹配座位数,儿童椅或包间也可以作为条件。只看桌子是否空闲,会导致四人组被安排到两人桌。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

入座前先验证路径

SeatAllocator 选择桌子前,先用 WalkGraph 检查入口到座位是否可达,再检查厨房到服务点是否可达。若顾客能坐下但服务员无法上菜,体验会很糟。不可达桌子要在编辑模式高亮。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

排队耐心影响评分

GuestQueue 保存每组顾客的到达时间、人数、耐心和偏好。等待越久评分越低,超过耐心会离开。玩家扩建座位或调整动线后,队列指标应明显改善。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

服务路径要避开顾客

服务员和顾客都在走道移动,容易拥堵。ServerRoute 可以给服务员较高优先级,顾客在等待区域停留。狭窄走道可设置单向或降低速度。不要让所有 NPC 用同一条最短路硬挤。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

桌面状态要清楚

桌子可能处于空闲、已安排、点餐中、等待上菜、用餐、待清理。UI 上用小图标或颜色表达状态。玩家需要知道为什么某张桌子还不能接待下一组顾客。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

布局编辑要即时反馈

玩家移动桌椅时,系统应实时显示哪些桌子不可达、哪些服务点被堵、哪些走道太窄。不要等开始营业后才发现服务员卡住。编辑模式可以用绿色/红色路径线提示。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

拥堵监控用于平衡

CongestionMonitor 记录每个走道格子的等待时间和通过频率。若厨房门口长期拥堵,玩家可以通过扩路解决;如果再怎么扩都堵,说明服务员 AI 或桌位规则需要调整。

实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。

TypeScript 实现骨架

interface TableInfo { id: string; seats: number; entry: string; service: string; clean: boolean; occupied: boolean }
interface GuestGroup { id: string; size: number; patienceMs: number; arrivedAt: number }
function canSeat(group: GuestGroup, table: TableInfo, reachable: Set<string>) {
  return !table.occupied && table.clean && table.seats >= group.size && reachable.has(table.entry) && reachable.has(table.service);
}
function chooseTable(group: GuestGroup, tables: TableInfo[], reachable: Set<string>) {
  return tables.filter(t => canSeat(group, t, reachable)).sort((a, b) => a.seats - b.seats)[0];
}
function patience(group: GuestGroup, now: number) {
  return Phaser.Math.Clamp(1 - (now - group.arrivedAt) / group.patienceMs, 0, 1);
}
function serviceScore(waitRatio: number, routeCost: number) { return Math.round(waitRatio * 80 - routeCost * 0.05 + 20); }

这段代码只展示核心边界。真实项目里还需要配置加载、错误码、事件总线、对象池、存档字段和测试夹具。原则是核心系统不依赖 Scene,Scene 只把玩家输入和系统结果连接到 Phaser 的显示对象。

落地步骤

  1. 第一步,先把 GuestQueue 和 TableRegistry 写成纯数据模型,准备两三个最小样例。
  2. 第二步,给 SeatAllocator 增加调试可视化,确保中间状态能被看见。
  3. 第三步,把 Phaser 动画接到规则结果上,而不是让动画反过来提交规则。
  4. 第四步,补齐失败原因、暂停恢复、重复点击保护和读档恢复。
  5. 第五步,用正常流程、边界流程、错误配置三类夹具做校验。

常见坑

  • 把画面当作状态来源。显示对象可能被对象池回收、被镜头隐藏或被动画临时改值,不能作为规则真相。
  • 只为第一关写逻辑。第一关对象少、节奏慢,很多问题不会暴露;内容扩张后,重复触发和配置错误会一起出现。
  • 失败反馈太笼统。玩家需要知道是条件不满足、资源不足、路径不可达、输入太晚,还是系统正在等待确认。
  • 调试面板缺失。复杂玩法没有中间状态可视化,后期只能靠录屏和猜测定位。

运行时观测

记录顾客离队率、平均等位时间、不可达桌位数量、服务员路径长度和拥堵热区。若顾客流失高但空桌多,通常是路径或清理状态没有被玩家看见。 这些指标不一定都要上报到线上,但至少应该在开发版能导出。玩法系统越依赖手感和解释,越需要用数据区分规则问题、表现问题和关卡配置问题。

边界测试与移动端验证

餐厅座位系统 在桌面浏览器里跑通,只能说明主流程成立,还不能说明它适合发布。建议为它准备一组专门的边界测试:空桌不可达、服务员和顾客在窄路相遇、桌子待清理但被再次分配、玩家编辑布局后旧路径缓存未刷新、两名服务员抢同一任务。这些测试不用都做成复杂自动化,至少要有可重复的调试入口,让开发、策划和 QA 能在同一状态下观察同一个问题。每次测试都要记录 tableId、WalkGraph 版本、任务锁和拥堵热区,否则失败只会变成“刚才好像不对”。如果玩法会出现在移动端,还要额外检查触控误差、浏览器切后台、低电量降频、横竖屏切换和音频恢复。很多 Phaser 小游戏不是输在核心规则,而是输在这些边界恢复上。

移动端验证还要关注触摸反馈和文字密度。按钮按下后要有立即响应,即使规则结果需要等待;长文本提示要能在窄屏换行;关键数值不能只靠颜色表达。若系统在低端机关闭粒子、阴影或轨迹后仍能保持同样的规则结果,就说明表现层和规则层边界清楚。发布前把这些用例整理成清单,后续每次改配置、换美术或加活动,都可以快速回归。

发布前检查

发布前至少确认四件事:第一,所有配置引用的 id 都存在;第二,核心状态能存档并恢复;第三,快速输入和跳过动画不会重复结算;第四,低端机可以关闭高成本表现但不改变规则。若系统涉及奖励、货币或排行榜,还要确认事件 id 幂等,避免重复发放或重复扣除。

布局评分和玩家教育

餐厅经营的布局问题不能只在失败时出现。编辑模式可以给布局一个即时评分:可达桌位比例、厨房到桌子的平均距离、瓶颈走道数量、入口到等位区距离。评分不需要替玩家自动优化,但能告诉玩家哪里可能出问题。新手阶段可以用一条幽灵路径展示服务员如何从厨房走到桌边,玩家自然会理解为什么某些摆法低效。

服务员任务队列

服务员不应该每次只看最近任务。上菜、清桌、点单和收银都有优先级,也有路线成本。任务队列可以按紧急程度和顺路程度排序。若两个服务员抢同一张桌,系统要有任务锁。否则玩家看到两个服务员跑向同一桌,而另一桌没人服务,会觉得 AI 很笨。

餐厅系统还要验证存档恢复。营业中保存后再进入,顾客队列、桌面状态、服务员任务和正在路上的菜都要能恢复。若只保存桌椅布局,读档后会出现空桌、丢单或服务员不知道去哪的问题。

验收标准

这个系统的第一版不需要覆盖所有商业化变化,但至少要能回答三类问题:玩家为什么成功,玩家为什么失败,内容配置为什么非法。验收时可以让一名没有参与开发的人按测试清单操作,如果他能从画面反馈和调试面板里解释当前状态,就说明系统边界基本成立。若每次都需要开发者口头说明,说明 UI、日志或规则命名还不够清楚。

结语

餐厅座位与寻路:排队、入座、服务路径和拥堵处理 的价值在于可解释。Phaser 可以把反馈做得很快,但真正决定项目能不能持续扩展的,是规则层是否稳定、表现层是否服从结果、调试层是否能讲清楚每一次失败。把这些边界立住,玩法才能从一个好看的 Demo 变成可维护的系统。

继续阅读

探索更多技术文章

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

全部文章 返回首页