模拟经营与城市建造(Management Simulation)游戏策划专题高级tech11 / 21 已发布

大规模对象的寻路与性能优化

空间分区 · 分层寻路 · 对象LOD降级 · 多线程模拟

· 14 分钟阅读
大规模对象的寻路与性能优化 Pathfinding at Scale & Performance Optimization — 模拟经营与城市建造(Management Simulation)游戏策划专题

大规模对象的寻路与性能优化 Pathfinding at Scale & Performance Optimization

为什么路径搜索密度是第一块基石

做经营游戏做到后期,你一定会撞上这堵墙:城市人口过了三千,每个小人都在实时寻路,CPU 使用率像坐火箭一样往上蹿。更难受的是,你明明只是想让面包师从厨房走到柜台,结果全城的人都卡在原地发抖——你以为你在做游戏,其实你在做交通调度中心。

这事我翻过车。做早期原型的时候,我用最朴素的寻路逻辑,每个角色每帧都重新计算路径。测试时一切正常,因为地图上只有十几个小人。等我把人口上限调到两百人,帧率直接掉到个位数。那天我盯着性能分析器,心里只有一个念头:这游戏不是被玩家玩死的,是被自己家里那两百个 NPC 活活卡死的。

所以这就是为什么这篇文章要聊寻路性能。你的经济系统设计得再精妙,你的员工 AI 再智能,如果一百个顾客同时想买面包时你的游戏变成了幻灯片,一切都白搭。性能不是后期优化的脏活,它决定了你能不能在游戏里兑现你的设计野心——你想让城市活起来,你首先得让它动得起来。

第一层:空间分区——先别急着寻路,先把地图切块

想象一下你要在一座城市里找一家咖啡店。你不会一条街一条街地扫过去,你会直接确认它在哪个区,再在那个区里找。空间分区就是把这个常识翻译给计算机。

网格划分是最老派也最可靠的做法:把地图切成等大的格子,每个格子记录自己区域里有哪些障碍物、哪些角色、哪些服务设施。角色需要寻路的时候,先确认自己所在格子,再确认目标所在格子,如果距离远,就直接在格子层面做粗粒度路径规划——这就是第一层过滤,把庞大的搜索空间砍掉一个数量级。

更高级一点是四叉树或者八叉树分区:地图大但人稀疏的地方用大格子,市中心人多物杂的地方自动切成小格子。代价是实现复杂度高一点,但收益很直接——你可以让 CPU 花在真正需要精细计算的地方。

实际落地的时候,有几个参数值得你反复调:

参数项建议区间调太高会怎样调太低会怎样
基础网格尺寸地图边长的 1/50 到 1/80分区太粗,粗粒度寻路失真格子太多,维护成本高
动态对象分桶密度每格 8-15 个角色格子内检索变慢格子数量膨胀,更新开销大
粗粒度寻路触发距离格子边长的 3-5 倍近距离寻路也走粗粒度,路线不自然粗粒度层形同虚设

说个真实案例。玩过《城市:天际线》的人都知道,这个游戏最著名的玩家吐槽就是“堵车”。但实际上天际线的寻路机制相当务实——它用分层道路网络做路径规划,主干道、次干道、支路分成不同层级,长距离出行优先走高层级道路,短距离才钻小巷。这个设计让几十万人口的城市场景还能保持可玩帧率。你可能会问,那为什么还会堵车?因为玩家自己把城市规划成了一条主干道通到底,所有车都挤上去——

你看,这里有个顶层设计反直觉的地方:寻路框架越高效,玩家越容易用极端的城市规划来榨干你的系统。所以性能优化不只是技术活,它也在跟玩家的创造力赛跑。

第二层:分层寻路——让远见的角色走大路,让眼前的任务走小路

分层寻路的思路很简单:不要把寻路想象成一张地图上的一次搜索,而是把它拆成两张图——一张是宏观的(城市级别的道路网络),一张是微观的(社区级别的步行小径)。角色要跨越大半个地图去干一件事,先在大图上找一条粗路线,然后逐段地在小图上细化每一条路段该怎么走。

这样做有两个直接好处。第一,计算量大幅下降:大图上搜索的节点数少,小图上每段搜索的范围也小。第二,路径质量反而更高:你可以在宏观层做交通流量预测,让角色避开拥堵的主干道,这在单层寻路里得靠额外的启发式算法才能实现。

有一个细节特别容易被忽略:分层切换的时机。角色走到路段的尽头,发现下一条路段堵死了,这时候应该重新规划全局吗?如果你每次都全局重算,那分层就没有意义了。但如果完全不管,角色就会傻乎乎地站在原地转圈。实用做法是设置一个“重规划冷却时间”:角色撞上拥堵后等待几秒,然后再重新算一次。这个等待时间让拥堵自然消散,又不会让玩家觉得角色是傻子。

关于分层寻路,可以做一张权衡表:

分层深度典型场景性能开销路径自然度
两层(宏观+微观)小型城镇规模地图低中等,可能出现“到了路口才想起要转弯”
三层(区域+道路+步行)中大型城市或殖民地图中高,长距离路线合理,短距离路径自然
单层全量搜索极小地图(如两三层建筑内)高(但地图小,可接受)最高,因为充分考虑了所有细节

《缺氧》就是分层寻路的好样本。它的地图本身是网格化的(一层层地基建成的网格),所以天然适合做分层:高优先级的“主干梯道”连接各个区域,次要梯道在区域内部分支,管道和输送带又有自己的寻路逻辑。最妙的是,缺氧的角色会在路径选择时综合考虑氧气管、水管的布局——你刚建好一条新走廊,小人就会自发地开始使用它,因为寻路器在宏观层面检测到了路径阻力下降。

这就是设计意图和性能框架握手的好例子:分层寻路不只是为了省 CPU,它本身就催生了玩家可以观察和利用的系统行为——你修好一条路,你就真的改变了城市的流动。如果系统只做单层全量寻路,你修路的反馈会微弱得多。

第三层:对象 LOD 降级——别让三百米外的老奶奶在精确计算怎么跨过一颗石子

你有没有注意过,现实生活里你不会每秒钟都决定下一步踩在哪块地砖上——你走着走着,就开启了自动驾驶,只有在碰到障碍物的时候才回过神来。游戏里的 LOD(细节层级)降级,就是模拟这种“自动驾驶”。

核心逻辑:角色的寻路计算量应该跟屏幕上的可见度挂钩。你不需要对每个角色都做每帧精确寻路。离镜头近的、正在交互的角色,走一步算一步,帧级响应;离镜头远的、在屏幕边缘溜达的角色,也许一两秒才更新一次路径就完全够用。

这听起来很合理,真正的工程难点在于判断“谁需要高精度”。我的经验是分几档:

LOD 层级更新频率路径类型适用条件
LOD-0(高精度)每帧更新全量动态避障玩家镜头附近、正在交互的角色
LOD-1(中精度)每 0.3-0.5 秒主路径 + 近似避障屏幕内但非焦点区域角色
LOD-2(低精度)每 1-2 秒粗粒度路径屏幕外或极小比例出现在屏幕上
LOD-3(估算)事件驱动仅目标点移动时更新地图边缘、资源节点附近的角色

有一个很猥琐但很有效的细节:你可以让角色在走长直道的时候切换到低 LOD,只在转角、路口这些“决策点”才把精度拉起来。你不需要让角色一路都像刚学会走路一样小心。

另外一个几乎是免费的优化是“路径平滑后处理”的降级——高精度角色用完整的多段路径平滑,让行走轨迹圆润自然;低精度角色直接用直线连接路点,视觉上可能有点生硬,但在远处根本没人看得见。这个差别可以用“视觉误差预算”来管理:允许低精度角色偏离理想路径最多半个身位,误差控制在这个范围内,玩家就不会发现。

《双点医院》是个很典型的案例,这个游戏里几十个病人在一张不大的地图上同时寻路找科室。双点医院的做法非常务实:大多数病人的路径不是每帧重算的,而是“走到某个路点后重新确认下一步”。于是你经常看到一群病人站在走廊交叉口短暂停顿,然后再呼啦啦地朝同一个方向走——这个停顿在玩家眼里是“医院里的日常”,但在性能分析器里,那是寻路系统在拼命刹车给它自己喘口气。它用微小的视觉瑕疵换来了平滑的帧率,值。

第四层:多线程模拟——把寻路交给别的大脑

如果你把寻路系统按好了分区、做好了分层、也上了 LOD,但你的城市仍然有三千个角色在午后高峰期同时涌向餐厅——你需要的最后一个手段,是把寻路丢给别的线程。

多线程的核心思路:把寻路从主循环里拆出来,交给工作线程异步处理。角色的行为逻辑照旧在主线程里跑,但“帮我算一条从面包店到家的路径”这个请求会排队给到专门的寻路线程池。几毫秒之后,线程把路径结果送回来,角色再沿着结果走。

这里最容易踩的坑是“数据竞争”。如果你让两个线程同时读写了同一块地图数据,轻则路径结果诡异,重则直接闪退崩溃。所以应遵循一个铁律:地图数据(障碍物、通行 cost 表)是只读的,任何想修改地图数据的请求(比如玩家放了一堵墙)都必须先把角色寻路线程挂起,改完再恢复。这个“暂停-修改-恢复”的逻辑,如果你处理不好,就会出现“角色穿墙”这种阴阳BUG。

多线程的收益分配很有讲究。一个较稳的拆分方式:

线程职责优先级忠告
主线程:角色行为、状态机、经济逻辑最高不能因为寻路线程忙碌而卡顿
寻路线程池(4-8 线程)高按区域分配任务,避免多线程同时搜索同一区域造成数据热点
动态网格更新线程中玩家的建筑操作触发地图变更,由这个线程负责合并改动
后台分析线程(流量统计等)低不阻碍游戏运行,慢慢算,算完展示

给你看一个真实的坑:《监狱建筑师》早期版本里,数百名囚犯同时在一条走廊寻路去食堂吃饭,寻路线程被排队的请求塞满了,导致所有囚犯都僵在原地,游戏看起来像冻结了。后来开发组用了“批处理”策略:每一帧只处理最多 64 个寻路请求,其余排队等到下一帧。这个“帧预算”的思路极其关键——不是为了把请求全算完,而是保证每一帧主线程都能稳定推进,哪怕有些角色延迟一两帧才出发。玩家感知不到这一两帧的延迟,但游戏的稳定性就保住了。

多线程的事,最后说一句真话:它不能拯救一个烂设计。如果你的寻路系统本身每次请求都算得很慢,丢给再多线程也只是推迟爆炸。先把前三层做好,多线程是锦上添花,不是雪中送炭。

调参工具箱——一张能直接抄的配置表示意图

理论讲了这么多,直接给一套能用的参数起点。以下是在我的项目里跑过的配置,你拿去当基线再根据自己的游戏调整。

系统参数基线值说明
空间分区基础格边长与地图宽度比1/60太粗则粗粒度寻路失真,太细则维护开销大
空间分区每格最大动态对象数12超过则触发集群迁移/重新划分逻辑
分层寻路粗粒度路径更新间隔0.8 秒长距离路径保持稳定,短距离则每帧微调
分层寻路拥堵重新规划冷却时间2.5 秒避免全员因同一点拥堵而集体重算
对象 LODLOD-0 的检测半径(以屏幕中央为基准)屏幕宽度的 1/3以肉眼可观察为主,避免浪费计算
对象 LOD视觉误差预算半个角色身位超出则强制提升 LOD,防止“穿模”感知
多线程每帧寻路请求上限64超出排队,每帧最多处理 64 个,保持主线程稳定
多线程寻路线程数硬件逻辑核心数 × 0.5留一半核心给渲染和其他模拟

再给三个压箱底的调参经验:

一,所有参数都要有运行时可视化。把寻路请求队列长度、单次寻路平均耗时、LOD 分布比例打成 on-screen 调试面板,一边看游戏一边调,比事后看日志高效十倍。

二,别让玩家的“小动作”触发全局寻路风暴。比如玩家拖动城墙画一道线——如果每一段墙都触发一次全城寻路重算,你的 CPU 直接冒烟。正确做法:把修改操作合并成一次“地图变更事件”,延迟 0.5 秒统一更新,让所有角色在下一轮寻路请求时自然感知到新地图。

三,设置“寻路压力测试”的内置作弊码。开发时一键生成 1000 个随机角色冲向地图中心,看看帧率跌成什么样。如果没有这个测试脚本,你永远只能在玩家论坛上知道自己游戏的性能上限。

初级用户速览 vs 进阶深入

初级用户:先跑起来,别管那么多

如果你的游戏还没达到几千个角色的规模,别折腾多线程,别搞四叉树。你只需要做三件事:一,把地图分成均匀网格,每个角色只在自己的网格以及相邻网格内检测障碍物;二,角色只在碰到障碍物或者到达路点时才重新寻路,不要每帧都算;三,离镜头远的角色每半秒更新一次路径。这三招能让你撑到几千人规模,而代码量不超过一天的工作量。

把基础跑起来之后,再去研究 LOD 降级和分层寻路。你会发现你已经有了一个能玩的版本,性能虽然有个上限,但足够你验证游戏核心乐趣。别一开始就上复杂架构,否则你会在性能优化上花掉两倍于做游戏玩法的时间——你没那么多时间。

进阶用户:用数据驱动优化

如果你的地图真的很大、角色密度真的很高(比如上万),那就需要更系统的思路。核心建议是:做一个“寻路成本热力图”的调试工具,把每个格子在最近一秒内的寻路请求数量渲染成颜色叠加层。你会发现拥堵不是均匀分布的——永远有几个热点格点(比如市中心广场、唯一一座桥的桥头)吸收了大部分请求。

这时候你就有两个选择:要么在热点周围增加“虚拟成本”(让寻路器绕开那些格点),要么修改地图设计(加桥、加路)。我的建议是:先调虚拟成本,观察玩家的行为有没有变得“反常”。如果玩家觉得“我的市民为什么绕着家门口的广场走”,那你可能改过头了,回退到地图改造,加一条侧路。用户反馈是检验性能优化的终极标尺——你再怎么自认为很懂寻路,也不如玩家的一句“我的人好像变蠢了”来得警醒。

别碰这些雷区:寻路优化的真实代价

踩过坑的人都知道,寻路优化有时候是“负优化”。我总结几个最经典的困境:

困境一:路径很少“恰好最优”。你做了分层寻路,宏观找了一条大路径,微观在局部修正,但合并起来的路线可能比最优路线长 20%。玩家通过观察,可能会发现市民绕远路了,然后质疑 AI 的智力。解决思路是:把“微距修正”的触发条件放宽——只有当成本差距超过 15% 时才修正。这样既能保证大部分路径看起来自然,又给了系统一点容错空间。

困境二:LOD 降级让角色显得“呆”。低精度角色可能站在路口无所事事好几秒才重新寻路,玩家如果恰好把镜头拉近,就会觉得这傻子卡住了。这里没有完美的解法,但我发现一个比较有效的缓解:让低精度角色在等待时播放“思考”或“整理衣服”之类的待机动画,把技术上的等待转化成有生活气息的行为。玩家会以为这人在发呆,而不会觉得游戏坏了。

困境三:多线程的隐性成本。你可能以为加了多线程就万事大吉。但线程间同步也要花时间。如果你的地图特别小、角色只有几百个,多线程的开销可能比单线程还慢——数据要复制到线程池,算完再拷贝回来,一来一回比直接算更耗时。所以请记住:多线程适用于“大计算量”,而不是“中等计算量”。

还有那些战斗民族般的挑战:有玩家会在你的沙盒里用两千头牛把市中心填满,然后截图发到社区说你寻路系统 bug 了。这时候除了苦笑,你可选的兜底方案只有一个:给“极端角色密度”设个物理上限——比如某个区域内角色数超过阈值时,自动把远处角色的 AI 切换成“离线模式”(直接瞬移到目标点,不做实时寻路)。虽然这看起来作弊,但你想清楚:玩家要的不是精准寻路,而是不要卡成 PPT。有时候放弃一部分物理正确性,换来的是流畅的体验。

FAQ

Q1:我的城市一到 2000 人口就卡成 PPT,应该先优化哪个系统?

先做性能分析器,看看瓶颈在寻路、渲染还是游戏逻辑(比如经济模拟)。如果是寻路,大概率是因为你全地图的角色都是每帧全量寻路。按文章里的三层优化跑一遍:空间分区(把搜索范围切小)+ LOD 降级(远处角色降低更新频率)+ 帧预算(每帧最多处理 64 个请求)。2000 人口的规模,这三板斧够你撑到发布。

Q2:我的角色走到了目的地门口,但门被另一角色堵住了,怎么处理?

给每个门/通道加“占用状态”,寻路器在计算最后一段路径时检查占用状态。如果门被堵了超过两秒,先让角色在门口小范围随机徘徊(视觉上像在等待),同时排队的角色自动走另一条路。这就是用“排队系统”替代“实时路径重算”的轻量方案。

Q3:LOD 降级会不会让角色看起来很蠢?

会有风险。但你把镜头拉近到某个低精度角色时,触发一次即时升级(强制它立刻重新寻路),这样可以保证近距离观察时都是高精度模式。玩家镜头是性能优化最好的触发器。

Q4:有没有必要为了寻路性能给玩家限制人口上限?

这是个产品决策,不是纯技术决策。如果你把寻路优化做好了,你可以给更高的上限,让玩家自由建造。但如果你给玩家 5000 人口上限却让游戏在 3000 人口时卡爆,那才是真灾难。技术作为底裤——上限可以高,但你要保证在接近上限时也能跑得动。开发者版做压力测试,比玩家替你找性能上限好一万倍。

Q5:如果我想做超大地图(比如 1024×1024 的格子),第一层的粗粒度寻路还有意义吗?

意义更大。超大地图最怕的是把每个格子都算一遍。空间分区 + 分层寻路是唯一能让超大地图跑起来的方式。唯一要注意的是:当你做大尺度规划时,粗粒度路径的“层数”要相应增加(比如从两层加到三层甚至四层),否则你会看到角色绕远路绕得离谱。

文章标签
模拟经营策划城市建设游戏设计经济循环顾客满意度模型员工AI系统供应链物流经营节奏寻路人流模拟程序化设施生成经营数据可视化DLC节奏经济Mod共创生态
更多专题全部专题
觉得有价值?点赞或收藏支持内容持续产出。
← 返回专题:模拟经营与城市建造(Management Simulation)游戏策划专题